Эффективный способ вычислить длину байта символа, в зависимости от кодирования

Что самый эффективный путь состоит в том, чтобы вычислить длину байта символа, приняв кодировку символов во внимание? Кодирование было бы только известно во время времени выполнения. В UTF-8, например, символы имеют переменную длину байта, таким образом, каждый символ должен быть определен индивидуально. Поскольку далеко теперь я придумал это:

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

Но это неуклюже и неэффективно в цикле с тех пор a new String потребности, которые будут созданы каждый раз. Я не могу найти другие и более эффективные пути в Java API. Существует a 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 указанный, существует больше проблем с этим. Могут, например, быть объединенные/суррогатные символы, который должен быть принят во внимание также. Но это - другая проблема, которая должна быть решена на шаге перед этим шагом.

10
задан Community 23 May 2017 в 11:53
поделиться

4 ответа

Используйте 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();
}
10
ответ дан 3 December 2019 в 21:20
поделиться

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

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

3
ответ дан 3 December 2019 в 21:20
поделиться

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

1
ответ дан 3 December 2019 в 21:20
поделиться

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

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

3
ответ дан 3 December 2019 в 21:20
поделиться
Другие вопросы по тегам:

Похожие вопросы: