如何将字节流转换为UTF-8字符?

6
我需要将一串字节流转换成一行UTF-8字符。在这一行中,我只关心最后一个字符。而且这个转换需要在一个循环中进行,因此性能非常重要。一个简单而低效的方法是:
public class Foo {
  private ByteArrayOutputStream buffer = new ByteArrayOutputStream();
  void next(byte input) {
    this.buffer.write(input);
    String text = this.buffer.toString("UTF-8"); // this is time consuming
    if (text.charAt(text.length() - 1) == THE_CHAR_WE_ARE_WAITING_FOR) {
      System.out.println("hurray!");
      this.buffer.reset();
    }   
  }
}

将字节数组转换为字符串会在每个输入字节上进行,这在我看来非常低效。有没有其他方法可以保留上一个周期中的字节到文本转换结果以避免重复转换?


这就是为什么我需要一个执行此转换任务的ByteArrayOutputStream - yegor256
那么数据从哪里来? - Clyde
InputStreamReader isr = new InputStreamReader(new InputStream() { @Override public int read() throws IOException { return // 从哪里获取数据。 } }); while(true) { try { if(isr.read() == THE_CHAR_WE_ARE_WAITING_FOR) System.out.println("耶!"); } catch(IOException e) { e.printStackTrace(); } } - Clyde
@yegor256:您对哪个代码点感兴趣?它是像128以下的简单代码点吗?还是(另一个极端)像代理字符一样的东西? - A.H.
然后这就非常简单了。只需等待换行符即可。由于它在7位ASCII范围内,您不必进行任何UTF-8的花哨操作。 - Aurand
显示剩余7条评论
4个回答

6
您可以使用一个简单的类来跟踪字符,并在获取完整的UTF8序列时才进行转换。这是一个示例(没有错误检查,您可能需要添加):
class UTF8Processor {
    private byte[] buffer = new byte[6];
    private int count = 0;

    public String processByte(byte nextByte) throws UnsupportedEncodingException {
        buffer[count++] = nextByte;
        if(count == expectedBytes())
        {
            String result = new String(buffer, 0, count, "UTF-8");
            count = 0;
            return result;
        }
        return null;
    }

    private int expectedBytes() {
        int num = buffer[0] & 255;
        if(num < 0x80) return 1;
        if(num < 0xe0) return 2;
        if(num < 0xf0) return 3;
        if(num < 0xf8) return 4;
        return 5;
    }
}

class Bop
{
    public static void main (String[] args) throws java.lang.Exception
    {
        // Create test data.
        String str = "Hejsan åäö/漢ya";
        byte[] bytes = str.getBytes("UTF-8");

        String ch;

        // Processes byte by byte, returns a valid UTF8 char when 
        //there is a complete one to get.

        UTF8Processor processor = new UTF8Processor();

        for(int i=0; i<bytes.length; i++)
        {
            if((ch = processor.processByte(bytes[i])) != null)
                System.out.println(ch);
        }
    }
}

你应该在 processByte() 函数内部调用 reset() 函数 - 没有必要要求外部管理缓冲区。 - Clyde
@Clyde 确实,这不是最干净的代码,因为它是在几分钟内拼凑出来的。但我会修复它的。 - Joachim Isaksson

2

根据评论:

这是换行符(0x0A)

您的next方法只需检查:

if ((char)input == THE_CHAR_WE_ARE_WAITING_FOR) {
    //whatever your logic is.
}

对于字符 < 128,您无需进行任何转换。


1

您有两个选择:

  • 如果您感兴趣的代码点是简单的(在 UTF-8 方面),例如小于 128 的代码点,则可以将 byte 强制转换为 char。请查阅 Wikipadia: UTF-8 中的编码规则以了解此方法的原因。

  • 如果不可能使用第一种方法,则可以查看 Charset 类,该类是 Java 编码/解码库的根。在这里,您将找到 CharsetDecoder,您可以向其提供 N 字节并返回 M 个字符。一般情况下,N ≠ M。但是,您需要处理 ByteBufferCharBuffer


0

将您的字节获取代码包装在一个InputStream中,并将其传递给InputStreamReader。

    InputStreamReader isr = new InputStreamReader(new InputStream() {
        @Override
        public int read() throws IOException {
            return xx();// wherever you get your data from.
        }
    }, "UTF-8");
    while(true) {
        try {
            if(isr.read() == THE_CHAR_WE_ARE_WAITING_FOR)
                System.out.println("hurray!");
        } catch(IOException e) {
            e.printStackTrace(); 
        }
    }

看到我问题的更新。我等不及字节了...它们是从其他地方注入到我的类中的。换句话说,我不能停下来等待下一个字节,就像你的例子中那样。 - yegor256
@yegor256,你现在编辑后的问题与原始问题根本不同。对于你编辑后的问题,有两个选项:使用InputStreamReader和专用线程等待数据,或者实现一个有状态的UTF-8解码器。UTF-8序列的长度最大为4,并且可以通过检查第一个字节来确定长度。一旦每个序列都被检查过,就可以将其丢弃,而不是像你的代码一样缓冲整个输入流(正如你意识到的那样是低效的)。 - Clyde

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接