【问题标题】:How can I decode a large, multi-byte string file progressively in Java?如何在 Java 中逐步解码大型多字节字符串文件?
【发布时间】:2016-02-28 12:44:01
【问题描述】:

我有一个程序可能需要处理可能包含多字节编码的大文件。我当前执行此操作的代码存在创建用于保存整个文件的内存结构的问题,如果文件很大,这可能会导致内存不足错误:

Charset charset = Charset.forName( "UTF-8" );
CharsetDecoder decoder = charset.newDecoder();
FileInputStream fis = new FileInputStream( file );
FileChannel fc = fis.getChannel();
int lenFile = (int)fc.size();
MappedByteBuffer bufferFile = fc.map( FileChannel.MapMode.READ_ONLY, 0, lenFile );
CharBuffer cb = decoder.decode( bufferFile );
// process character buffer
fc.close();

问题在于,如果我使用较小的缓冲区分割文件字节内容并将其零碎地提供给解码器,那么缓冲区可能会在多字节序列的中间结束。我该如何应对这个问题?

【问题讨论】:

  • 你到底想做什么?你想要一个 CharBuffer 还是一个字符串?
  • CharBuffer(文件包含的数据可能包含字符串内容,但也可能包含非字符串数据)
  • 等等,同一个文件中有非文本数据的文本数据?那就是推动事情
  • @fge 在 CSV 文件中很常见。例如,该文件可能有数据库记录,如:“apple”、5,4345、“Palo Alto, CA”、43[CR][LF] 等。文件中的字符串可能是 UTF-8 或其他多字节编码。
  • 错误,不,等等;你似乎在这里混合了几个概念。文本文件应该使用一种编码方案并且只有一种。例如,您永远不会看到部分使用 UTF-8 和 UTF-16 的文本文件。在 UTF-8 中单个 code point 转换为 1 到 4 个字节的事实是另一回事!

标签: java string unicode decoding


【解决方案1】:

@fge,我不知道报告选项 - 很酷。 @Tyler,我认为诀窍是使用 BufferedReader 的 read() 方法: 摘自这里:https://docs.oracle.com/javase/7/docs/api/java/io/BufferedReader.html#read%28char[],%20int,%20int%29

public int read(char[] cbuf,
       int off,
       int len)
         throws IOException

这是一些示例输出(代码如下):

read #1, found 32 chars
read #2, found 32 chars
read #3, found 32 chars
read #4, found 32 chars
read #80, found 32 chars
...
read #81, found 32 chars
read #82, found 7 chars
Done, read total=2599 chars, readcnt=82

请注意上面的输出,它恰好以最后一个 '7' 字符结尾;您可以调整缓冲区数组大小以处理您想要的任何“块”大小...这只是一个示例,建议您不必担心卡在多字节 UTF8 字符中的“中字节”某处。

import java.io.*;

class Foo {
   public static void main( String args[] ) throws Exception {
      String encoding = "UTF8";
      String inFilename = "unicode-example-utf8.txt";
      // Test file from http://www.i18nguy.com/unicode/unicode-example-intro.htm
      // Specifically the Example Data, CSV format:
      //     http://www.i18nguy.com/unicode/unicode-example-utf8.zip
      char buff[] = new char[ 32 ]; // or whatever size...
      // I know the readers  can be combined to just nest the temp instances,
      // for an  example i think it is easier to parse the structure
      // with each reader explicitly declared.
      FileInputStream finstream = new FileInputStream( inFilename );
      InputStreamReader instream = new InputStreamReader( finstream, encoding );
      BufferedReader in = new BufferedReader( instream );
      int n;
      long total = 0;
      long readcnt = 0;
      while( -1 != (n = in.read( buff, 0, buff.length ) ) ) {
         total += n;
         ++readcnt;
         System.out.println("read #"+readcnt+", found "+n+" chars ");
      }
      System.out.println( "Done, read total="+total+" chars, readcnt="+readcnt );
      in.close();
   }
}

【讨论】:

    【解决方案2】:

    就像使用Reader 一样简单。

    CharsetDecoder 确实是允许将字节解码为字符的底层机制。简而言之,您可以这样说:

    // Extrapolation...
    byte stream --> decoding       --> char stream
    InputStream --> CharsetDecoder --> Reader
    

    鲜为人知的事实是,JDK 中的大多数(但不是全部...见下文)默认解码器(例如从 FileReader 创建的解码器,或仅带有字符集的 InputStreamReader)将具有CodingErrorAction.REPLACE 的策略。效果是用Unicode replacement character 替换输入中的任何无效字节序列(是的,那个臭名昭著的�)。

    现在,如果您担心“坏角色”会溜进来,您还可以选择拥有REPORT 的策略。您也可以在读取文件时这样做,如下所示;这将产生在任何格式错误的字节序列上抛出MalformedInputException 的效果:

    // This is 2015. File is obsolete.
    final Path path = Paths.get(...);
    final CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder()
        .onMalformedInput(CodingErrorAction.REPORT);
    
    try (
        final InputStream in = Files.newInputStream(path);
        final Reader reader = new InputStreamReader(in, decoder);
    ) {
        // use the reader
    }
    

    Java 8 中出现该默认替换操作的一个例外:Files.newBufferedReader(somePath) 将始终尝试以 UTF-8 读取,并且默认操作为 REPORT

    【讨论】:

    • 这个这个这个这个。读者已经知道如何做正确的事了。
    • 是什么让你这么说?就我而言,我可以说它确实有效。我已经使用普通阅读器阅读了超过 500 MB 的文件。
    【解决方案3】:

    将文件作为文本文件打开并读取,因此文件阅读器会为您分离成字符。如果文件有行,只需逐行读取。如果它没有分成多行,则以 1,000 个(或其他)字符为单位读入。让文件库处理将 UTF 多字节序列转换为字符的低级工作。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-06-18
      • 1970-01-01
      • 2021-12-17
      • 2016-03-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多