Эффективный способ расчета длины байта символа в зависимости от кодировки - PullRequest
11 голосов
/ 28 апреля 2010

Как наиболее эффективно рассчитать длину символа в байтах, учитывая кодировку символов? Кодировка будет известна только во время выполнения. Например, в 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 , с этим есть и другие проблемы. Например, могут быть комбинированные / суррогатные символы, которые также должны быть приняты во внимание. Но это еще одна проблема, которую необходимо решить на шаге до этого шага.

Ответы [ 4 ]

10 голосов
/ 28 апреля 2010

Используйте 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();
}
3 голосов
/ 28 апреля 2010

Если вы можете гарантировать, что вход UTF-8 правильно сформирован, то нет никаких причин искать кодовые точки вообще. Одной из сильных сторон UTF-8 является то, что вы можете обнаружить начало кодовой точки с любой позиции в строке. Просто ищите в обратном направлении, пока не найдете такой байт, что (b & 0xc0)! = 0x80, и вы найдете другой символ. Поскольку кодовая точка в кодировке UTF-8 всегда составляет 6 байтов или меньше, вы можете скопировать промежуточные байты в буфер фиксированной длины.

Edit: я забыл упомянуть, даже если вы не используете эту стратегию, недостаточно использовать Java "char" для хранения произвольных кодовых точек, поскольку значения кодовых точек могут превышать 0xffff. Вам нужно хранить кодовые точки в «int».

3 голосов
/ 28 апреля 2010

Возможно, что схема кодирования могла бы кодировать данный символ как переменное число байтов, в зависимости от того, что предшествует и после него в последовательности символов. Длина байта, полученная при кодировании одного символа String, поэтому не является полным ответом.

(Например, теоретически вы можете получить символы бодо / телетайпа, закодированные как 4 символа каждые 3 байта, или вы можете теоретически рассматривать UTF-16 + потоковый компрессор как схему кодирования. Да, все это немного неправдоподобно , но ...)

1 голос
/ 28 апреля 2010

Попробуйте Charset.forName("UTF-8").encode("string").limit(); Может быть немного эффективнее, а может и нет.

...