如何在Java中逐步解码大型多字节字符串文件?

时间:2015-11-25 16:17:32

标签: java string unicode decoding

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

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

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

3 个答案:

答案 0 :(得分:3)

就像使用Reader一样简单。

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

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

鲜为人知的事实是,大多数(但不是全部......见下文)JDK中的默认解码器(例如从FileReader创建的解码器,或只有InputStreamReader的解码器charset)的政策为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

答案 1 :(得分:1)

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

答案 2 :(得分:0)

@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字符中被卡在“中间字节”的某处。 / p>

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