计算字符长度的有效方法,具体取决于编码方式

考虑到字符编码,计算字符字节长度的最有效方法是什么? 编码只能在运行时知道。 例如,在UTF-8中,字符具有可变字节长度,因此需要单独确定每个字符。 到目前为止,我已经想出了这个:

char c = getCharSomehow(); String encoding = getEncodingSomehow(); // ... int length = new String(new char[] { c }).getBytes(encoding).length; 

但是这在循环中是笨拙和低效的,因为每次都需要创建一个new String 。 我在Java API中找不到其他更有效的方法。 有一个String#valueOf(char) ,但根据它的来源它基本上与上面相同。 我想这可以通过像位移这样的按位运算来完成,但这是我的弱点,我不确定如何在这里考虑编码:)

如果您对此有疑问,请查看此主题 。


更新: @Bkkbrad的答案在技术上是最有效的:

 char c = getCharSomehow(); String encoding = getEncodingSomehow(); CharsetEncoder encoder = Charset.forName(encoding).newEncoder(); // ... int length = encoder.encode(CharBuffer.wrap(new char[] { c })).limit(); 

然而正如@Stephen C指出的那样,这有更多的问题。 例如,可能需要考虑组合/代理字符。 但这是另一个需要在此步骤之前的步骤中解决的问题。

使用CharsetEncoder并重用CharBuffer作为输入,并使用ByteBuffer作为输出。

在我的系统上,以下代码需要25秒来编码100,000个单个字符:

 Charset utf8 = Charset.forName("UTF-8"); char[] array = new char[1]; for (int reps = 0; reps < 10000; reps++) { for (array[0] = 0; array[0] < 10000; array[0]++) { int len = new String(array).getBytes(utf8).length; } } 

但是,以下代码在4秒内完成相同的操作:

 Charset utf8 = Charset.forName("UTF-8"); CharsetEncoder encoder = utf8.newEncoder(); char[] array = new char[1]; CharBuffer input = CharBuffer.wrap(array); ByteBuffer output = ByteBuffer.allocate(10); for (int reps = 0; reps < 10000; reps++) { for (array[0] = 0; array[0] < 10000; array[0]++) { output.clear(); input.clear(); encoder.encode(input, output, false); int len = output.position(); } } 

编辑:为什么仇恨者会讨厌?

这是一个从CharBuffer读取并跟踪代理对的解决方案:

 Charset utf8 = Charset.forName("UTF-8"); CharsetEncoder encoder = utf8.newEncoder(); CharBuffer input = //allocate in some way, or pass as parameter ByteBuffer output = ByteBuffer.allocate(10); int limit = input.limit(); while(input.position() < limit) { output.clear(); input.mark(); input.limit(Math.max(input.position() + 2, input.capacity())); if (Character.isHighSurrogate(input.get()) && !Character.isLowSurrogate(input.get())) { //Malformed surrogate pair; do something! } input.limit(input.position()); input.reset(); encoder.encode(input, output, false); int encodedLen = output.position(); } 

如果你能保证输入是格式良好的UTF-8,那么根本没有理由找到代码点。 UTF-8的优势之一是您可以从字符串中的任何位置检测代码点的开始。 只需向后搜索,直到找到一个字节(b&0xc0)!= 0x80,然后你就找到了另一个字符。 由于UTF-8编码的代码点始终为6个字节或更少,因此可以将中间字节复制到固定长度的缓冲区中。

编辑:我忘了提及,即使你没有采用这种策略,使用Java“char”存储任意代码点是不够的,因为代码点值可能超过0xffff。 您需要将代码点存储在“int”中。

编码方案可能将给定字符编码为可变数量的字节,这取决于字符序列中之前和之后的内容。 因此,从编码单个字符串获得的字节长度不是完整的答案。

(例如,理论上你可以接收每3个字节编码为4个字符的baudot /电传打字符,或者你理论上可以将UTF-16 +流压缩器视为一种编码方案。是的,它有点难以置信,但是。 ..)

尝试Charset.forName("UTF-8").encode("string").limit(); 可能会更有效率,也许不是。