【问题标题】:Refactoring auto-detect file's encoding重构自动检测文件的编码
【发布时间】:2013-03-13 04:42:09
【问题描述】:

我需要检查编码文件。此代码有效,但有点长。如何能够对这个逻辑进行任何重构。 也许可以为此目标使用其他变体?

代码:

class CharsetDetector implements Checker {

    Charset detectCharset(File currentFile, String[] charsets) {
        Charset charset = null;

        for (String charsetName : charsets) {
            charset = detectCharset(currentFile, Charset.forName(charsetName));
            if (charset != null) {
                break;
            }
        }

        return charset;
    }

    private Charset detectCharset(File currentFile, Charset charset) {
        try {
            BufferedInputStream input = new BufferedInputStream(
                    new FileInputStream(currentFile));

            CharsetDecoder decoder = charset.newDecoder();
            decoder.reset();

            byte[] buffer = new byte[512];
            boolean identified = false;
            while ((input.read(buffer) != -1) && (!identified)) {
                identified = identify(buffer, decoder);
            }

            input.close();

            if (identified) {
                return charset;
            } else {
                return null;
            }

        } catch (Exception e) {
            return null;
        }
    }

    private boolean identify(byte[] bytes, CharsetDecoder decoder) {
        try {
            decoder.decode(ByteBuffer.wrap(bytes));
        } catch (CharacterCodingException e) {
            return false;
        }
        return true;
    }

    @Override
    public boolean check(File fileChack) {
        if (charsetDetector(fileChack)) {
            return true;
        }
        return false;
    }

    private boolean charsetDetector(File currentFile) {
        String[] charsetsToBeTested = { "UTF-8", "windows-1253", "ISO-8859-7" };

        CharsetDetector charsetDetector = new CharsetDetector();
        Charset charset = charsetDetector.detectCharset(currentFile,
                charsetsToBeTested);

        if (charset != null) {
            try {
                InputStreamReader reader = new InputStreamReader(
                        new FileInputStream(currentFile), charset);

                @SuppressWarnings("unused")
                int valueReaders = 0;
                while ((valueReaders = reader.read()) != -1) {
                    return true;
                }

                reader.close();
            } catch (FileNotFoundException exc) {
                System.out.println("File not found!");
                exc.printStackTrace();
            } catch (IOException exc) {
                exc.printStackTrace();
            }
        } else {
            System.out.println("Unrecognized charset.");
            return false;
        }

        return true;
    }
}

问题:

  • 这个程序逻辑如何重构?
  • 还有哪些检测编码的方法(如 UTF-16 序列等)?

【问题讨论】:

  • 你检查过this post吗?
  • 此代码不正确。它不会检查整个文件。

标签: java encoding refactoring


【解决方案1】:

重构此代码的最佳方法是引入一个为您进行字符检测的第三方库,因为他们可能会做得更好,并且会使您的代码更小。 请参阅this question 了解一些替代方案

【讨论】:

  • “第三方库” - 这个库是什么?一般做什么?
  • @nazar_art - “库”是指包含您可以使用的代码的 *.jar 文件。很多人以前用 java 写过字符检测代码并开源 - 使用他们的代码。
【解决方案2】:

正如已经指出的那样,您无法“知道”或“检测”文件的编码。完全准确需要告诉您,因为几乎总是有一个字节序列在几个字符编码方面是模棱两可的。

您将在 SO question. 中找到更多关于检测 UTF-8 与 ISO8859-1 的讨论。重要的 答案是检查文件中的每个字节序列以验证其兼容性 与预期的编码。 UTF-8 字节编码规则见http://en.wikipedia.org/wiki/UTF-8

特别是,有一篇关于检测字符编码/集的非常有趣的论文 http://www-archive.mozilla.org/projects/intl/UniversalCharsetDetection.html 他们声称他们具有极高的准确性(猜测!)。价格是一个非常复杂的检测系统,包含有关不同语言中字符频率的知识,不适合 OP 暗示的 30 行代码大小。显然检测算法是内置在 Mozilla 中的,因此您很可能可以找到并提取它。

我们选择了一个更简单的方案:a)相信你被告知的字符集,如果你被告知 b)如果没有,检查 BOM 并相信它所说的如果存在,否则嗅探纯 7 位 ascii ,然后是 utf8 或 iso8859 的顺序。您可以构建一个丑陋的例程,一次性遍历文件。

(我认为随着时间的推移,问题会变得更糟。Unicode 每年都会有一个新版本,有效代码点之间存在真正细微的差异。要做到这一点,您需要检查每个代码点的有效性。如果我们'很幸运,它们都是向后兼容的。)

[编辑:OP 似乎在用 Java 编码时遇到了问题。我们的解决方案和另一页上的草图不是用 Java 编码的,所以我不能直接复制和粘贴答案。我将根据他的代码在这里起草一个Java版本;它没有被编译或测试。 YMMV]

int UTF8size(byte[] buffer, int buf_index)
// Java-version of character-sniffing test on other page
// This only checks for UTF8 compatible bit-pattern layout
// A tighter test (what we actually did) would check for valid UTF-8 code points
{   int first_character=buffer[buf_index];

    // This first character test might be faster as a switch statement
    if ((first_character & 0x80) == 0) return 1; // ASCII subset character, fast path
    else ((first_character & 0xF8) == 0xF0) { // start of 4-byte sequence
        if (buf_index+3>=buffer.length) return 0;
        if (((buffer[buf_index + 1] & 0xC0) == 0x80)
         && ((buffer[buf_index + 2] & 0xC0) == 0x80)
         && ((buffer[buf_index + 3] & 0xC0) == 0x80))
            return 4;
    }
    else if ((first_character & 0xF0) == 0xE0) { // start of 3-byte sequence
        if (buf_index+2>=buffer.length) return 0;
        if (((buffer[buf_index + 1] & 0xC0) == 0x80)
         && ((buffer[buf_index + 2] & 0xC0) == 0x80))
            return 3;
    }
    else if ((first_character & 0xE0) == 0xC0) { // start of 2-byte sequence
        if (buf_index+1>=buffer.length) return 0;
        if ((buffer[buf_index + 1] & 0xC0) == 0x80)
            return 2;
    }
    return 0;
}

public static boolean isUTF8 ( File file ) {
    int file_size;
    if (null == file) {
        throw new IllegalArgumentException ("input file can't be null");
    }
    if (file.isDirectory ()) {
        throw new IllegalArgumentException ("input file refers to a directory");
    }

    file_size=file.size();
    // read input file
    byte [] buffer = new byte[file_size];
    try {
        FileInputStream fis = new FileInputStream ( input ) ;
        fis.read ( buffer ) ;
        fis.close ();
    }
    catch ( IOException e ) {
        throw new IllegalArgumentException ("Can't read input file, error = " + e.getLocalizedMessage () );
    }

    { int buf_index=0;
      int step;

      while (buf_index<file_size) {
         step=UTF8size(buffer,buf_index);
         if (step==0) return false; // definitely not UTF-8 file
         buf_index+=step;

      }

    }

   return true ; // appears to be UTF-8 file
}

【讨论】:

  • 如何检查不同的编码类型?证明 currentFile 有一些编码类型。在这种情况下使用哪种方式更好?
  • 我引用的 SO question 告诉您如何使用相对简单的检查来检测不同的编码类型。您使用更复杂的检查(“UniversalCharsetDetection”),准确度更高,但除非您想花费一生来复制这项工作,否则我会坚持使用简单的方案。
  • 检查哪个比较简单?我不明白。你能更好地展示这个方案吗?
  • 关键是你必须检查整个文件看它是否兼容UTFxxx。我已经(也许笨拙地)编写了一个 UTF8 版本。检查 UTF-16 的方法是检查文件中的字节对,以查看所有解释为 16 位代码的字节对都是有效的 UTF16 代码点。您需要对用于处理 0x010000 到 0x10FFFF 代码点范围内的字符的 UTF-16 转义码进行特殊处理。
  • 如果你不阅读整个文件,你怎么知道你没有阅读的字节序列包含不是UTF8的东西?您可以读取几千字节而不是整个文件,并做出大多数情况下可能是正确的猜测,但在极少数情况下所做的只是让人头疼。关于UTF16:UTF8代码我基本都给你写好了;是时候展开你的翅膀,想办法自己做 UTF16 了。
猜你喜欢
  • 2018-12-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-01-07
  • 2012-02-29
  • 2011-02-09
  • 2021-12-30
相关资源
最近更新 更多