特定フォルダに入っているテキストファイルを1つずつ読み込むときにFiles.readString()を使用したのですが以下のようなエラーが出ました。
少し悩んだのでメモメモ。
java.nio.charset.MalformedInputException: Input length = 1
at java.base/java.lang.StringCoding.throwMalformed(StringCoding.java:685)
at java.base/java.lang.StringCoding.decodeUTF8_0(StringCoding.java:841)
at java.base/java.lang.StringCoding.newStringNoRepl1(StringCoding.java:1005)
at java.base/java.lang.StringCoding.newStringNoRepl(StringCoding.java:990)
at java.base/java.lang.System$2.newStringNoRepl(System.java:2189)
at java.base/java.nio.file.Files.readString(Files.java:3284)
java.nio.charset.MalformedInputException
MalformedInputExceptionはFiles.readString()で指定した文字コードと読み込みたいファイルの文字コードが異なる場合に発生します。
なので、基本的にはソースとファイルの文字コードを統一すれば治りますが、今回はちょっと別の箇所が原因でした。
ファイル読み込みのロジック
ファイルを読み込むロジックがどうだったかと言うとこんな感じです。
- 指定したフォルダに格納されているファイルの一覧を取得
- ファイルの更新日時で降順ソート
- ソート後にファイル読み込み
ソースはこんな感じです。
public void readFiles() {
File[] files = new File(path).listFiles();
Arrays.sort(files, Comparator.comparingLong(File::lastModified).reversed());
for (int i = 0; i < files.length; i++) {
String str = readText(files[i].getAbsolutePath());
// 処理
}
}
private String readText(String path) {
try {
String text = Files.readString(Paths.get(path), StandardCharsets.UTF_8);
return text;
} catch (IOException e) {
log.warn("notice file read error", e);
return "";
}
}
MalformedInputExceptionが発生した原因
上記ソースのここでエラーが発生したわけですが、フォルダに格納されているファイルはもちろんUTF-8です。
Files.readString(Paths.get(path), StandardCharsets.UTF_8);
何がダメだったかと言うと
File[] files = new File(path).listFiles();
ここです。
Linuxだとフォルダ作成したら隠しファイルが勝手に出来ますが、new File(path).listFiles()だと隠しファイル、フォルダも一覧として取得されます。
その為、以下の2ファイル(フォルダ)もUTF-8のテキストファイルとして読み込もうとしてMalformedInputExceptionが発生していたわけです。
drwxr-xr-x 2 tomcat tomcat 4096 Dec 16 16:22 .
drwxr-xr-x 7 tomcat tomcat 4096 Dec 4 01:48 ..
対策
今回の対応としてはisHidden()を使用して隠しファイル、フォルダを判定、ついでにこのパスに普通のフォルダでも作ってしまったら同じ事象になるのでファイルではないものも除外するといロジックに変更しました。
修正したソースは以下。
public void readFiles() {
Arrays.stream(files)
.filter(f -> !f.isHidden() && f.isFile()).sorted(Comparator.comparingLong(File::lastModified).reversed())
.forEach(f -> {
String noticeStr = readText(f.getAbsolutePath());
if (StringUtils.hasText(noticeStr)) {
// 処理
}
});
}
以上です。
コメント