Подтвердить что ты не робот

Вычисление длины в UTF-8 строки Java без фактического кодирования

Кто-нибудь знает, предоставляет ли стандартная библиотека Java (любая версия) средство вычисления длины двоичного кодирования строки (в частности, UTF-8 в этом случае) без фактического генерирования кодированного вывода? Другими словами, я ищу эффективный эквивалент этого:

"some really long string".getBytes("UTF-8").length

Мне нужно вычислить префикс длины для потенциально долго сериализованных сообщений.

4b9b3361

Ответ 1

Здесь реализована реализация, основанная на спецификации UTF-8:

public class Utf8LenCounter {
  public static int length(CharSequence sequence) {
    int count = 0;
    for (int i = 0, len = sequence.length(); i < len; i++) {
      char ch = sequence.charAt(i);
      if (ch <= 0x7F) {
        count++;
      } else if (ch <= 0x7FF) {
        count += 2;
      } else if (Character.isHighSurrogate(ch)) {
        count += 4;
        ++i;
      } else {
        count += 3;
      }
    }
    return count;
  }
}

Эта реализация не допускает неверных строк.

Здесь проверка JUnit 4 для проверки:

public class LenCounterTest {
  @Test public void testUtf8Len() {
    Charset utf8 = Charset.forName("UTF-8");
    AllCodepointsIterator iterator = new AllCodepointsIterator();
    while (iterator.hasNext()) {
      String test = new String(Character.toChars(iterator.next()));
      Assert.assertEquals(test.getBytes(utf8).length,
                          Utf8LenCounter.length(test));
    }
  }

  private static class AllCodepointsIterator {
    private static final int MAX = 0x10FFFF; //see http://unicode.org/glossary/
    private static final int SURROGATE_FIRST = 0xD800;
    private static final int SURROGATE_LAST = 0xDFFF;
    private int codepoint = 0;
    public boolean hasNext() { return codepoint < MAX; }
    public int next() {
      int ret = codepoint;
      codepoint = next(codepoint);
      return ret;
    }
    private int next(int codepoint) {
      while (codepoint++ < MAX) {
        if (codepoint == SURROGATE_FIRST) { codepoint = SURROGATE_LAST + 1; }
        if (!Character.isDefined(codepoint)) { continue; }
        return codepoint;
      }
      return MAX;
    }
  }
}

Извините за компактное форматирование.

Ответ 2

Использование Guava Utf8:

Utf8.encodedLength("some really long string")

Ответ 3

Лучший способ, с помощью которого я мог придумать, - использовать CharsetEncoder для повторного ввода в один и тот же временный буфер:

public int getEncodedLength(CharBuffer src, CharsetEncoder encoder)
    throws CharacterCodingException
{
    // CharsetEncoder.flush fails if encode is not called with >0 chars
    if (!src.hasRemaining())
        return 0;

    // encode into a byte buffer that is repeatedly overwritten
    final ByteBuffer outputBuffer = ByteBuffer.allocate(1024);

    // encoding loop
    int bytes = 0;
    CoderResult status;
    do
    {
        status = encoder.encode(src, outputBuffer, true);
        if (status.isError())
            status.throwException();
        bytes += outputBuffer.position();

        outputBuffer.clear();
    }
    while (status.isOverflow());

    // flush any remaining buffered state
    status = encoder.flush(outputBuffer);
    if (status.isError() || status.isOverflow())
        status.throwException();
    bytes += outputBuffer.position();

    return bytes;
}

public int getUtf8Length(String str) throws CharacterCodingException
{
    return getEncodedLength(CharBuffer.wrap(str),
        Charset.forName("UTF-8").newEncoder());
}

Ответ 4

Вы можете выполнить цикл через строку:

/**
 * Deprecated: doesn't support surrogate characters.
 */
@Deprecated
public int countUTF8Length(String str)
{
    int count = 0;
    for (int i = 0; i < str.length(); ++i)
    {
        char c = str.charAt(i);
        if (c < 0x80)
        {
            count++;
        } else if (c < 0x800)
        {
            count +=2;
        } else
            throw new UnsupportedOperationException("not implemented yet");
        }
    }
    return count;
}