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

时间:2010-04-28 00:27:19

标签: java character-encoding character byte

考虑到字符编码,计算字符字节长度的最有效方法是什么?编码只在运行时才知道。例如,在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),但根据其来源,它基本上与上面相同。我想这可以通过像位移这样的按位操作来完成,但这是我的弱点,我不确定如何在这里考虑编码:)

如果您对此有疑问,请检查this topic


更新:来自@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指出的那样,这方面存在更多问题。例如,可能需要考虑组合/代理字符。但这是另一个需要在步骤之前解决的问题。

4 个答案:

答案 0 :(得分:10)

使用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读取并跟踪surrogate pairs的解决方案:

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

答案 1 :(得分:3)

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

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

答案 2 :(得分:3)

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

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

答案 3 :(得分:1)

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