152

次のスレッドを参照してください 。Javaアプリ:iso-8859-1でエンコードされたファイルを正しく読み取ることができません

入力ストリーム/ファイルの正しい文字セットエンコーディングをプログラムで決定するための最良の方法は何ですか?

私は以下を使用してみました:

File in =  new File(args[0]);
InputStreamReader r = new InputStreamReader(new FileInputStream(in));
System.out.println(r.getEncoding());

しかし、ISO8859_1でエンコードされていることがわかっているファイルでは、上記のコードはASCIIを生成しますが、これは正しくなく、ファイルの内容をコンソールに正しくレンダリングすることはできません。

4

16 に答える 16

105

任意のバイトストリームのエンコーディングを決定することはできません。これがエンコーディングの性質です。エンコーディングとは、バイト値とその表現の間のマッピングを意味します。したがって、すべてのエンコーディングが正しい可能性があります。

getEncoding()メソッドは、ストリーム用に設定された( JavaDocを読み取る)エンコーディングを返します。エンコーディングを推測することはありません。

一部のストリームは、それらの作成に使用されたエンコーディング(XML、HTML)を示します。しかし、任意のバイトストリームではありません。

とにかく、必要に応じて、自分でエンコーディングを推測してみることができます。すべての言語には、すべての文字に共通の頻度があります。英語では、char eは非常に頻繁に表示されますが、êはほとんど表示されません。ISO-8859-1ストリームでは、通常、0x00文字はありません。しかし、UTF-16ストリームにはそれらがたくさんあります。

または:ユーザーに尋ねることができます。さまざまなエンコーディングでファイルのスニペットを表示し、「正しい」ものを選択するように求めるアプリケーションをすでに見ました。

于 2009-01-31T15:44:22.563 に答える
77

Javaでエンコーディングを検出するためにjchardetに似たこのライブラリを使用しました: https://github.com/albfernandez/juniversalchardet

于 2011-01-19T13:44:36.570 に答える
40

これをチェックしてください: 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()
}
于 2010-10-25T10:11:42.677 に答える
31

ここに私のお気に入りがあります:

TikaEncodingDetector

依存:

<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));    
}

GuessEncoding

依存:

<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);
  }
于 2014-11-30T12:48:37.557 に答える
14

ファイルを でデコードし、「malformed-input」または「unmappable-character」エラーに注意することで、特定の文字セットのファイルを確実に検証できます。もちろん、これは文字セットが間違っているかどうかだけを教えてくれます。それが正しいかどうかはわかりません。そのためには、デコードされた結果を評価するための比較の基準が必要です。たとえば、文字が特定のサブセットに制限されているかどうか、またはテキストが特定の厳密な形式に準拠しているかどうかを事前に知っていますか? 要するに、文字セットの検出は当て推量であり、何の保証もありません。CharsetDecoder

于 2009-02-01T07:33:32.163 に答える
14

どのライブラリを使用しますか?

これを書いている時点で、それらは出現する 3 つのライブラリです。

内部で ICU4j 3.4 を使用しているため、 Apache Any23は含めません。

どちらが正しい文字セットを検出したか (またはできるだけ近い文字セットを検出したか)を確認するにはどうすればよいですか?

上記の各ライブラリが検出する文字セットを証明することはできません。ただし、順番に質問して、返された応答をスコアリングすることは可能です。

返された応答をスコアリングする方法は?

各応答には 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は入力ストリームを完全に読み取ります。大きな入力ストリームの場合、これは問題になる可能性があります。これらのライブラリはすべて、入力ストリーム全体を読み取ります。これは、文字セットの検出に多くの時間がかかることを意味します。

最初のデータ読み込みを数バイトに制限し、それらの数バイトのみで文字セット検出を実行することができます。

于 2015-09-03T10:38:00.120 に答える
9

私の知る限り、このコンテキストであらゆる種類の問題に適した一般的なライブラリはありません。そのため、問題ごとに既存のライブラリをテストし、問題の制約を満たす最適なライブラリを選択する必要がありますが、多くの場合、どれも適切ではありません。このような場合、独自の Encoding Detector を作成できます。私が書いたように...

IBM ICU4j と Mozilla JCharDet を組み込みコンポーネントとして使用して、HTML Web ページの文字セット エンコーディングを検出するメタ Java ツールを作成しました。ここで私のツールを見つけることができます。何よりも先に README セクションを読んでください。また、この問題のいくつかの基本的な概念は、私の論文とその参考文献で 見つけることができます。

以下に、私が仕事で経験したいくつかの役立つコメントを提供しました。

  • 文字セットの検出は、基本的に統計データに基づいており、実際には検出ではなく推測であるため、絶対確実なプロセスではありません。
  • icu4j は、IBM によるこのコンテキストでの主要なツールです。
  • TikaEncodingDetector と Lucene-ICU4j の両方が icu4j を使用しており、それらの精度は私のテストでは icu4j と有意な差はありませんでした (私が覚えているように、最大​​で %1 です)。
  • icu4j は jchardet よりもはるかに一般的です。icu4j は IBM ファミリーのエンコーディングに少し偏っているだけですが、jchardet は utf-8 に強く偏っています。
  • HTML の世界では UTF-8 が広く使用されているため。全体的に jchardet は icu4j よりも良い選択ですが、最良の選択ではありません!
  • icu4j は、EUC-KR、EUC-JP、SHIFT_JIS、BIG5、および GB ファミリーのエンコーディングなどの東アジア固有のエンコーディングに最適です。
  • icu4j と jchardet はどちらも、Windows-1251 および Windows-1256 エンコーディングを使用した HTML ページの処理に失敗します。Windows-1251 (別名 cp1251) はロシア語などのキリル文字ベースの言語に広く使用され、Windows-1256 (別名 cp1256) はアラビア語に広く使用されています。
  • ほとんどすべてのエンコード検出ツールは統計的手法を使用しているため、出力の精度は入力のサイズと内容に大きく依存します。
  • 一部のエンコーディングは本質的に同じで部分的な違いがあるだけです。したがって、場合によっては、推測または検出されたエンコーディングが間違っていても、同時に正しいこともあります。Windows-1252 と ISO-8859-1 について。(私の論文の 5.2 セクションの最後の段落を参照してください)
于 2016-05-12T20:14:41.053 に答える
8

上記のライブラリは単純な BOM 検出器であり、もちろんファイルの先頭に BOM がある場合にのみ機能します。テキストをスキャンするhttp://jchardet.sourceforge.net/を見てください

于 2010-02-15T11:53:01.363 に答える
5

実際のエンコーディングを検出できる優れたサードパーティライブラリを見つけました:http: //glaforge.free.fr/wiki/index.php? wiki = GuessEncoding

私はそれを広範囲にテストしませんでしたが、それはうまくいくようです。

于 2010-01-07T09:04:04.057 に答える
5

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 が必要であることを忘れないでください。

これがうまくいくことを願っています。

于 2013-04-04T21:01:42.790 に答える
4

データのエンコーディングがわからない場合は、判断するのはそれほど簡単ではありませんが、ライブラリを使用して推測することはできます。また、同様の質問があります。

于 2009-01-31T15:46:01.810 に答える
2

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の場合は、このコードを確認することをお勧めします。このコードは、一般的なファイル形式を検出し、正しいエンコーディングを選択します。 ファイルを読み取り、正しいエンコーディングを自動的に指定する方法

于 2009-05-26T07:20:38.257 に答える
1

TikaEncodingDetector の代わりに、Tika AutoDetectReaderを使用できます。

Charset charset = new AutoDetectReader(new FileInputStream(file)).getCharset();
于 2015-05-11T13:04:14.007 に答える
-1

プレーン 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 つずつ試します。(ところで、私のエンコーディング リストにはこれらの項目しかありません。これは、すべての Java プラットフォームで必要な文字セットの実装であるhttps://docs.oracle.com/javase/9​​/docs/api/java/nio/charset/Charset.html )

于 2018-07-28T16:59:04.497 に答える
-12

コンストラクターで適切な文字セットを選択できますか?

new InputStreamReader(new FileInputStream(in), "ISO8859_1");
于 2009-01-31T15:44:08.880 に答える