[java] Java : 스트림의 올바른 문자 세트 인코딩을 결정하는 방법

다음 스레드를 참조하십시오.
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)을 알려줍니다. 그러나 임의의 바이트 스트림은 아닙니다.

어쨌든 필요한 경우 직접 인코딩을 추측 할 수 있습니다. 모든 언어는 모든 문자마다 공통된 빈도를 갖습니다. 영어에서는 문자 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()
}


답변

내가 가장 좋아하는 것은 다음과 같습니다.

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

추측 인코딩

의존:

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


답변

파일을 디코딩 하고 “잘못된 입력”또는 “매핑 불가능한 문자”오류를 관찰 하여 특정 문자 세트에 대해 파일의 유효성 을 확실히 검증 할 수 있습니다 . 물론, 이것은 문자셋이 잘못되었는지 알려줍니다. 그것이 정확한지 알려주지 않습니다. 이를 위해서는 디코딩 된 결과를 평가하기위한 비교 기준이 필요합니다. 예를 들어 문자가 일부 하위 세트로 제한되는지 또는 텍스트가 엄격한 형식을 준수하는지 여부를 미리 알고 있습니까? 결론은 문자셋 탐지가 보장없이 추측 할 수 있다는 것입니다.CharsetDecoder


답변

어떤 라이브러리를 사용해야합니까?

이 글을 쓰는 시점에서 다음과 같은 세 가지 라이브러리가 있습니다.

Apache Any23 은 ICU4j 3.4를 사용하기 때문에 포함하지 않습니다 .

어느 것이 올바른 문자 세트를 감지했는지 (또는 가능한 한 가깝게) 말하는지?

위의 각 라이브러리에서 감지 한 문자 세트를 인증하는 것은 불가능합니다. 그러나 차례로 요청하고 반환 된 응답의 점수를 매길 수 있습니다.

반환 된 응답의 점수를 매기는 방법?

각 응답에는 한 지점이 할당 될 수 있습니다. 응답이 많을수록 탐지 된 문자 집합의 신뢰도가 높아집니다. 이것은 간단한 채점 방법입니다. 다른 사람들을 정교하게 만들 수 있습니다.

샘플 코드가 있습니까?

다음은 이전 행에서 설명한 전략을 구현하는 전체 스 니펫입니다.

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방법은 입력 스트림을 완전히 읽습니다. 큰 입력 스트림의 경우 이것이 문제가 될 수 있습니다. 이 모든 라이브러리는 전체 입력 스트림을 읽습니다. 이는 문자셋을 탐지하는 데 많은 시간이 소요됨을 의미합니다.

초기 데이터로드를 몇 바이트로 제한하고 그 몇 바이트에서만 문자 세트 감지를 수행 할 수 있습니다.


답변

위의 libs는 파일의 시작 부분에 BOM이있는 경우에만 작동하는 간단한 BOM 검출기입니다. 텍스트를 스캔하는 http://jchardet.sourceforge.net/ 을 살펴보십시오.