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()
}
내가 가장 좋아하는 것은 다음과 같습니다.
의존:
<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
방법은 입력 스트림을 완전히 읽습니다. 큰 입력 스트림의 경우 이것이 문제가 될 수 있습니다. 이 모든 라이브러리는 전체 입력 스트림을 읽습니다. 이는 문자셋을 탐지하는 데 많은 시간이 소요됨을 의미합니다.
It's possible to limit the initial data loading to a few bytes and perform the charset detection on those few bytes only.
The libs above are simple BOM detectors which of course only work if there is a BOM in the beginning of the file. Take a look at http://jchardet.sourceforge.net/ which does scans the text
I found a nice third party library which can detect actual encoding: http://glaforge.free.fr/wiki/index.php?wiki=GuessEncoding
I didn't test it extensively but it seems to work.
If you use ICU4J (http://icu-project.org/apiref/icu4j/)
Here is my code:
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();
}
}
Remember to put all the try catch need it.
I hope this works for you.
As far as I know, there is no general library in this context to be suitable for all types of problems. So, for each problem you should test the existing libraries and select the best one which satisfies your problem’s constraints, but often none of them is appropriate. In these cases you can write your own Encoding Detector! As I have wrote ...
I’ve wrote a meta java tool for detecting charset encoding of HTML Web pages, using IBM ICU4j and Mozilla JCharDet as the built-in components. Here you can find my tool, please read the README section before anything else. Also, you can find some basic concepts of this problem in my paper and in its references.
Bellow I provided some helpful comments which I’ve experienced in my work:
- Charset detection is not a foolproof process, because it is essentially based on statistical data and what actually happens is guessing not detecting
- icu4j is the main tool in this context by IBM, imho
- Both TikaEncodingDetector and Lucene-ICU4j are using icu4j and their accuracy had not a meaningful difference from which the icu4j in my tests (at most %1, as I remember)
- icu4j is much more general than jchardet, icu4j is just a bit biased to IBM family encodings while jchardet is strongly biased to utf-8
- Due to the widespread use of UTF-8 in HTML-world; jchardet is a better choice than icu4j in overall, but is not the best choice!
- icu4j is great for East Asian specific encodings like EUC-KR, EUC-JP, SHIFT_JIS, BIG5 and the GB family encodings
- Both icu4j and jchardet are debacle in dealing with HTML pages with Windows-1251 and Windows-1256 encodings. Windows-1251 aka cp1251 is widely used for Cyrillic-based languages like Russian and Windows-1256 aka cp1256 is widely used for Arabic
- Almost all encoding detection tools are using statistical methods, so the accuracy of output strongly depends on the size and the contents of the input
- Some encodings are essentially the same just with a partial differences, so in some cases the guessed or detected encoding may be false but at the same time be true! As about Windows-1252 and ISO-8859-1. (refer to the last paragraph under the 5.2 section of my paper)
If you don't know the encoding of your data, it is not so easy to determine, but you could try to use a library to guess it. Also, there is a similar question.
For ISO8859_1 files, there is not an easy way to distinguish them from ASCII. For Unicode files however one can generally detect this based on the first few bytes of the file.
UTF-8 and UTF-16 files include a Byte Order Mark (BOM) at the very beginning of the file. The BOM is a zero-width non-breaking space.
Unfortunately, for historical reasons, Java does not detect this automatically. Programs like Notepad will check the BOM and use the appropriate encoding. Using unix or Cygwin, you can check the BOM with the file command. For example:
$ file sample2.sql
sample2.sql: Unicode text, UTF-16, big-endian
For Java, I suggest you check out this code, which will detect the common file formats and select the correct encoding: How to read a file and automatically specify the correct encoding
An alternative to TikaEncodingDetector is to use Tika AutoDetectReader.
Charset charset = new AutoDetectReader(new FileInputStream(file)).getCharset();
In plain 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.");
}
}
This approach will try the encodings one by one until one works or we run out of them. (BTW my encodings list has only those items because they are the charsets implementations required on every Java platform, https://docs.oracle.com/javase/9/docs/api/java/nio/charset/Charset.html)
Can you pick the appropriate char set in the Constructor:
new InputStreamReader(new FileInputStream(in), "ISO8859_1");
'Programming' 카테고리의 다른 글
Visual Studio에서 만든 Windows 서비스 설치 (0) | 2020.06.27 |
---|---|
CSS로 전체 웹 페이지를 확장하려면 어떻게해야합니까? (0) | 2020.06.27 |
개인 순수 가상 기능의 요점은 무엇입니까? (0) | 2020.06.27 |
초를 표시하지 않고 .toLocaleTimeString ()을 어떻게 사용합니까? (0) | 2020.06.27 |
java.lang.IllegalStateException : 단편이 활동에 첨부되지 않음 (0) | 2020.06.27 |