如何在Java NIO解码期间摆脱不正确的符号?

问题描述

我需要从文件中读取文本,例如在控制台中打印。该文件为UTF-8。看来我做错了,因为某些俄语符号的打印不正确。我的代码有什么问题?

ssh username@dbserver.example.com -L 5555:/var/run/postgresql/.s.PGsql.5432 -fN

结果:

StringBuilder content = new StringBuilder();

        try (FileChannel fChan = (FileChannel) Files.newByteChannel(Paths.get("D:/test.txt")) ) {

            ByteBuffer byteBuf = ByteBuffer.allocate(16);
            Charset charset = Charset.forName("UTF-8");

            while(fChan.read(byteBuf) != -1) {
                byteBuf.flip();
                content.append(new String(byteBuf.array(),charset));
                byteBuf.clear();
            }

            System.out.println(content);
        } 

实际文本:

Здравствуйте,как поживае��е?
Это п��имер текста на русском яз��ке.ом яз�

解决方法

UTF-8每个字符使用可变数目的字节。这给您带来了一个边界错误:您将基于缓冲区的代码与基于字节数组的代码混合在一起,不能在此处执行;您有可能读取足够多的字节以将其插入字符的一半,然后将输入转换为字节数组并进行转换,这将失败,因为您不能转换半个字符。

您真正想要的是要么首先读取所有数据,然后转换整个输入,要么在向后翻转时将任何半字符保留在字节缓冲区中,或者更好的是,放弃所有这些东西并使用代码用来读取实际字符。通常,使用通道API使事情变得复杂很多。它很灵活,但很复杂-就是这样。

除非您可以解释为什么需要它,否则不要使用它。而是这样做:

Path target = Paths.get("D:/test.txt");
try (var reader = Files.newBufferedReader(target)) {
    // read a line at a time here. Yes,it will be UTF-8 decoded.
}

或者更好,因为您显然想一口气阅读整个内容:

Path target = Paths.get("D:/test.txt");
var content = Files.readString(target);

NB:与大多数将字节转换为char或将char转换为char的java方法不同,Files API默认使用UTF-8(而不是大多数情况下无用且危险,无法测试的,导致错误的“平台默认编码” Java API)。因此,这最后一个非常简单的代码仍然是正确的。