programing

Java : 스트림의 올바른 문자 집합 인코딩을 확인하는 방법

newsource 2022. 8. 13. 12:18

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를 출력합니다.ASCII는 올바르지 않습니다.또, 파일의 내용을 콘솔에 올바르게 렌더링 할 수 없습니다.

임의의 바이트 스트림의 부호화는 판별할 수 없습니다.이것이 인코딩의 본질입니다.부호화는 바이트 값과 그 표현 간의 매핑을 의미한다.따라서 모든 인코딩이 "올바른" 것이 될 수 있습니다.

getEncoding() 메서드는 스트림에 대해 설정된 인코딩(JavaDoc 읽기)을 반환합니다.부호화는 추측되지 않습니다.

스트림에 따라 XML, HTML 등 어떤 인코딩이 사용되었는지 알 수 있지만 임의의 바이트 스트림은 알 수 없습니다.

어쨌든, 필요하다면, 스스로 부호화를 추측해 볼 수도 있습니다.모든 언어는 모든 문자에 공통 주파수를 가지고 있다.영어에서 문자 e는 매우 자주 나타나지만 문자 e는 매우 드물게 나타난다.ISO-8859-1 스트림에는 보통 0x00 문자가 없습니다.하지만 UTF-16 스트림에는 그것들이 많이 있습니다.

또는 사용자에게 질문할 수 있습니다.다른 인코딩으로 파일 일부를 표시하고 "올바른" 파일을 선택하도록 요청하는 애플리케이션을 이미 봤습니다.

자바 인코딩을 검출하기 위해 jchardet과 유사한 라이브러리를 사용하고 있습니다.https://github.com/albfernandez/juniversalchardet

http://site.icu-project.org/(icu4j)에는 IOStream에서 charset을 검출하기 위한 라이브러리가 있습니다.이것은 다음과 같습니다.

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

파일을 로 디코딩하고 "부정 입력" 또는 "비동기" 에러를 감시함으로써 특정 문자셋의 파일을 확실하게 검증할 수 있습니다.물론 이것은 문자 집합이 틀렸는지 여부만 알려 줄 뿐 정확한지는 알려주지 않습니다.그러기 위해서는 디코딩된 결과를 평가하기 위한 비교의 기초가 필요합니다.예를 들어 문자가 일부 서브셋으로 제한되어 있는지, 텍스트가 엄격한 형식을 따르고 있는지 등을 사전에 알 수 있습니다.결론은 문자 집합 검출은 아무런 보장도 없는 추측이라는 것입니다.

사용할 라이브러리를 선택하십시오.

이 문서에서는 다음 3개의 라이브러리가 나타납니다.

Apache Any23은 후드에서 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을 완전히.대규모 입력 스트림의 경우 이는 문제가 될 수 있습니다.이 모든 라이브러리는 입력 스트림 전체를 읽을 수 있습니다.이는 문자 집합을 검출하는 데 많은 시간이 소요됨을 의미합니다.

초기 데이터 로드를 몇 바이트로 제한하고 해당 몇 바이트에 대해서만 문자 집합 탐지를 수행할 수 있습니다.

내가 아는 한, 이 맥락에서 모든 종류의 문제에 적합한 일반 도서관은 없다.따라서 각 문제에 대해 기존 라이브러리를 테스트하고 문제의 제약을 충족하는 최적의 라이브러리를 선택해야 합니다. 그러나 대부분의 경우 이러한 라이브러리는 적절하지 않습니다.이러한 경우 자체 인코딩 디텍터를 작성할 수 있습니다.제가 쓴 것처럼...

내장 컴포넌트로 IBM ICU4j와 Mozilla JCharDet를 사용하여 HTML Web 페이지의 charset 인코딩을 검출하는 메타 자바 툴을 작성했습니다.툴은 이쪽에서 찾을 수 있습니다.README 섹션을 먼저 읽어주세요.또한 이 문제에 대한 몇 가지 기본적인 개념은 제 논문과 참고 자료에서 찾을 수 있습니다.

Bellow 저는 제 작업에서 경험한 몇 가지 유용한 의견을 제시했습니다.

  • Charset 검출은 기본적으로 통계 데이터에 기초하고 있으며 실제로는 검출되지 않은 으로 추측하기 때문에 오류 방지 프로세스가 아닙니다.
  • icu4j는 IBM에 의한 이 컨텍스트의 주요 도구입니다.imho
  • TikaEncodingDetector와 Lucene-ICU4j는 모두 icu4j를 사용하고 있으며, 그 정확도는 테스트에서 icu4j와 큰 차이가 없었다(기억에 의하면 최대 %1).
  • icu4j는 jchardet보다 훨씬 일반적이며, icu4j는 IBM 패밀리 인코딩에 조금 치우친 반면 jchardet은 utf-8에 강하게 치우쳐 있습니다.
  • HTML 세계에서는 UTF-8이 널리 사용되고 있기 때문에 전체적으로 icu4j보다 jchardet이 더 나은 선택이지만 최선의 선택은 아닙니다.
  • 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 섹션의 마지막 단락 참조)

위의 libs는 단순한 BOM 디텍터이며, 물론 파일 선두에 BOM이 있는 경우에만 작동합니다.http://jchardet.sourceforge.net/ 에서 텍스트를 스캔합니다.

실제 인코딩을 검출할 수 있는 서드파티 라이브러리를 찾았습니다.http://glaforge.free.fr/wiki/index.php?wiki=GuessEncoding

광범위하게 테스트한 것은 아니지만, 효과가 있는 것 같습니다.

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

꼭 필요한 트라이캐치를 모두 넣으세요.

이게 너한테 효과가 있길 바라.

데이터의 인코딩을 모르는 경우 쉽게 판별할 수 없지만 라이브러리를 사용하여 추측해 볼 수 있습니다.그리고 비슷한 질문이 있어요.

ISO8859_1 파일의 경우 ASCII와 구별하기가 쉽지 않습니다.단, Unicode 파일의 경우 일반적으로 파일의 처음 몇 바이트를 기준으로 이를 검출할 수 있습니다.

UTF-8 및 UTF-16 파일에는 파일의 맨 앞에 Byte Order Mark(BOM; 바이트 순서 마크)가 포함되어 있습니다.BOM은 폭 0의 중단 없는 공간입니다.

유감스럽게도 과거의 이유로 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();

이 문제를 해결하는 좋은 방법은 입력 문자 집합을 자동으로 검출하는 것입니다.

org.xml.sax를 사용합니다.Java 11의 InputSource를 사용하여 해결:

...    
import org.xml.sax.InputSource;
...

InputSource inputSource = new InputSource(inputStream);
inputStreamReader = new InputStreamReader(
    inputSource.getByteStream(), inputSource.getEncoding()
  );

입력 샘플:

<?xml version="1.0" encoding="utf-16"?>
<rss xmlns:dc="https://purl.org/dc/elements/1.1/" version="2.0">
<channel>
...**strong text**

플레인 자바:

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)

생성자에서 적절한 문자 집합을 선택할 수 있습니까?

new InputStreamReader(new FileInputStream(in), "ISO8859_1");

언급URL : https://stackoverflow.com/questions/499010/java-how-to-determine-the-correct-charset-encoding-of-a-stream