次のスレッドを参照: Java App:iso-8859-1エンコードファイルを正しく読み込めません
入力ストリーム/ファイルの正しい文字セットエンコーディングをプログラムで決定する最良の方法は何ですか?
私は次を使用してみました:
File in = new File(args[0]);
InputStreamReader r = new InputStreamReader(new FileInputStream(in));
System.out.println(r.getEncoding());
しかし、ISO8859_1でエンコードされていることがわかっているファイルでは、上記のコードはASCIIを生成しますが、これは正しくなく、ファイルの内容をコンソールに正しくレンダリングできません。
Javaでのエンコードを検出するためにjchardetに似たこのライブラリを使用しました: http://code.google.com/p/juniversalchardet/
任意のバイトストリームのエンコーディングを決定することはできません。これがエンコーディングの性質です。エンコードとは、バイト値とその表現の間のマッピングを意味します。そのため、すべてのエンコーディングが正しい可能性があります。
getEncoding() メソッドは、ストリームに設定された( JavaDoc を読んで)エンコーディングを返します。エンコーディングは推測されません。
一部のストリームでは、作成に使用されたエンコードがXML、HTMLで示されます。しかし、任意のバイトストリームではありません。
とにかく、必要に応じて、自分でエンコーディングを推測してみることができます。すべての言語には、すべての文字に共通の頻度があります。英語ではchar eは非常に頻繁に表示されますが、êはほとんど表示されません。 ISO-8859-1ストリームでは、通常0x00文字はありません。しかし、UTF-16ストリームには多くのものがあります。
または:ユーザーに尋ねることができます。さまざまなエンコーディングでファイルのスニペットを表示し、「正しい」ものを選択するように要求するアプリケーションを見てきました。
これを確認してください: http://site.icu-project.org/ (icu4j)彼らはIOStreamから文字セットを検出するためのライブラリを持っています。
BufferedInputStream bis = new BufferedInputStream(input);
CharsetDetector cd = new CharsetDetector();
cd.setText(bis);
CharsetMatch cm = cd.detect();
if (cm != null) {
reader = cm.getReader();
charset = cm.getName();
}else {
throw new UnsupportedCharsetException()
}
私のお気に入りは次のとおりです。
依存:
<dependency>
<groupId>org.Apache.any23</groupId>
<artifactId>Apache-any23-encoding</artifactId>
<version>1.1</version>
</dependency>
サンプル:
public static Charset guessCharset(InputStream is) throws IOException {
return Charset.forName(new TikaEncodingDetector().guessEncoding(is));
}
依存:
<dependency>
<groupId>org.codehaus.guessencoding</groupId>
<artifactId>guessencoding</artifactId>
<version>1.4</version>
<type>jar</type>
</dependency>
サンプル:
public static Charset guessCharset2(File file) throws IOException {
return CharsetToolkit.guessEncoding(file, 4096, StandardCharsets.UTF_8);
}
確かにvalidate特定の文字セットのファイルを decoding と CharsetDecoder
で監視することができます「不正な入力」または「マップ不可の文字」エラーの場合は出力されます。もちろん、これは文字セットが間違っている場合にのみ通知します。正しいかどうかはわかりません。そのためには、デコード結果を評価するための比較の基礎が必要です。文字が一部のサブセットに制限されているかどうか、またはテキストが何らかの厳密な形式に準拠しているかどうかを事前に知っていますか?一番下の行は、文字セットの検出は、保証なしの当て推量です。
この記事を書いている時点で、3つのライブラリが出現しています。
Apache Any2 は含めません。これは、内部でICU4j 3.4を使用するためです。
上記の各ライブラリによって検出された文字セットを認証することは不可能です。ただし、順番に質問して、返された応答をスコアリングすることは可能です。
各応答には1ポイントを割り当てることができます。応答のポイントが多いほど、検出された文字セットの信頼度が高くなります。これは単純なスコアリング方法です。他の人を詳しく説明できます。
以下は、前の行で説明した戦略を実装した完全なスニペットです。
public static String guessEncoding(InputStream input) throws IOException {
// Load input data
long count = 0;
int n = 0, EOF = -1;
byte[] buffer = new byte[4096];
ByteArrayOutputStream output = new ByteArrayOutputStream();
while ((EOF != (n = input.read(buffer))) && (count <= Integer.MAX_VALUE)) {
output.write(buffer, 0, n);
count += n;
}
if (count > Integer.MAX_VALUE) {
throw new RuntimeException("Inputstream too large.");
}
byte[] data = output.toByteArray();
// Detect encoding
Map<String, int[]> encodingsScores = new HashMap<>();
// * GuessEncoding
updateEncodingsScores(encodingsScores, new CharsetToolkit(data).guessEncoding().displayName());
// * ICU4j
CharsetDetector charsetDetector = new CharsetDetector();
charsetDetector.setText(data);
charsetDetector.enableInputFilter(true);
CharsetMatch cm = charsetDetector.detect();
if (cm != null) {
updateEncodingsScores(encodingsScores, cm.getName());
}
// * juniversalchardset
UniversalDetector universalDetector = new UniversalDetector(null);
universalDetector.handleData(data, 0, data.length);
universalDetector.dataEnd();
String encodingName = universalDetector.getDetectedCharset();
if (encodingName != null) {
updateEncodingsScores(encodingsScores, encodingName);
}
// Find winning encoding
Map.Entry<String, int[]> maxEntry = null;
for (Map.Entry<String, int[]> e : encodingsScores.entrySet()) {
if (maxEntry == null || (e.getValue()[0] > maxEntry.getValue()[0])) {
maxEntry = e;
}
}
String winningEncoding = maxEntry.getKey();
//dumpEncodingsScores(encodingsScores);
return winningEncoding;
}
private static void updateEncodingsScores(Map<String, int[]> encodingsScores, String encoding) {
String encodingName = encoding.toLowerCase();
int[] encodingScore = encodingsScores.get(encodingName);
if (encodingScore == null) {
encodingsScores.put(encodingName, new int[] { 1 });
} else {
encodingScore[0]++;
}
}
private static void dumpEncodingsScores(Map<String, int[]> encodingsScores) {
System.out.println(toString(encodingsScores));
}
private static String toString(Map<String, int[]> encodingsScores) {
String GLUE = ", ";
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, int[]> e : encodingsScores.entrySet()) {
sb.append(e.getKey() + ":" + e.getValue()[0] + GLUE);
}
int len = sb.length();
sb.delete(len - GLUE.length(), len);
return "{ " + sb.toString() + " }";
}
改善:guessEncoding
メソッドは、入力ストリームを完全に読み取ります。大きな入力ストリームの場合、これは懸念事項です。これらのライブラリはすべて、入力ストリーム全体を読み取ります。これは、文字セットの検出に多大な時間を消費することを意味します。
初期データのロードを数バイトに制限し、それらの数バイトのみで文字セット検出を実行することができます。
上記のライブラリは単純なBOMディテクタであり、ファイルの先頭にBOMがある場合にのみ機能します。テキストをスキャンする http://jchardet.sourceforge.net/ を見てください
ICU4Jを使用する場合( http://icu-project.org/apiref/icu4j/ )
ここに私のコードがあります:
String charset = "ISO-8859-1"; //Default chartset, put whatever you want
byte[] fileContent = null;
FileInputStream fin = null;
//create FileInputStream object
fin = new FileInputStream(file.getPath());
/*
* Create byte array large enough to hold the content of the file.
* Use File.length to determine size of the file in bytes.
*/
fileContent = new byte[(int) file.length()];
/*
* To read content of the file in byte array, use
* int read(byte[] byteArray) method of Java FileInputStream class.
*
*/
fin.read(fileContent);
byte[] data = fileContent;
CharsetDetector detector = new CharsetDetector();
detector.setText(data);
CharsetMatch cm = detector.detect();
if (cm != null) {
int confidence = cm.getConfidence();
System.out.println("Encoding: " + cm.getName() + " - Confidence: " + confidence + "%");
//Here you have the encode name and the confidence
//In my case if the confidence is > 50 I return the encode, else I return the default value
if (confidence > 50) {
charset = cm.getName();
}
}
すべてのtry catchがそれを必要とすることを忘れないでください。
これがお役に立てば幸いです。
実際のエンコードを検出できるニースのサードパーティライブラリを見つけました: http://glaforge.free.fr/wiki/index.php?wiki=GuessEncoding
私はそれを広範囲にテストしませんでしたが、うまくいくようです。
私の知る限り、このコンテキストではすべてのタイプの問題に適した一般的なライブラリはありません。そのため、問題ごとに既存のライブラリをテストし、問題の制約を満たす最適なライブラリを選択する必要がありますが、多くの場合、適切ではありません。これらの場合、独自のエンコーディング検出器を作成できます!私が書いたように...
IBM ICU4jとMozilla JCharDetを組み込みコンポーネントとして使用して、HTML Webページの文字セットエンコーディングを検出するメタJavaツールを作成しました。 ここ あなたは私のツールを見つけることができます。他の何かの前にREADMEセクションを読んでください。また、この問題のいくつかの基本的な概念は、私の paper およびそのリファレンスで見つけることができます。
私は私の仕事で経験したいくつかの有用なコメントを提供しました:
ISO8859_1ファイルの場合、ASCIIと区別する簡単な方法はありません。ただし、Unicodeファイルの場合、通常はファイルの最初の数バイトに基づいてこれを検出できます。
UTF-8およびUTF-16ファイルには、ファイルの先頭に バイトオーダーマーク (BOM)が含まれています。 BOMは、幅がゼロの改行なしスペースです。
残念ながら、歴史的な理由により、Javaはこれを自動的に検出しません。メモ帳などのプログラムは、BOMをチェックし、適切なエンコードを使用します。 UNIXまたはCygwinを使用すると、fileコマンドでBOMを確認できます。例えば:
$ file sample2.sql
sample2.sql: Unicode text, UTF-16, big-endian
Javaの場合、一般的なファイル形式を検出し、正しいエンコーディングを選択するこのコードをチェックアウトすることをお勧めします。 ファイルを読み取り、正しいエンコーディングを自動的に指定する方法
TikaEncodingDetectorの代替手段は、 Tika AutoDetectReader を使用することです。
Charset charset = new AutoDetectReader(new FileInputStream(file)).getCharset();
プレーンJavaの場合:
final String[] encodings = { "US-ASCII", "ISO-8859-1", "UTF-8", "UTF-16BE", "UTF-16LE", "UTF-16" };
List<String> lines;
for (String encoding : encodings) {
try {
lines = Files.readAllLines(path, Charset.forName(encoding));
for (String line : lines) {
// do something...
}
break;
} catch (IOException ioe) {
System.out.println(encoding + " failed, trying next.");
}
}
このアプローチでは、エンコーディングが1つ機能するか、使い果たされるまで1つずつ試行します。 (ところで、私のエンコーディングリストには、すべてのJavaプラットフォームで必要な文字セット実装であるため、これらの項目のみがあります。 https://docs.Oracle.com/javase/9/docs/api/Java /nio/charset/Charset.html )