как избежать потери памяти при хранении символов UTF-8 (8 бит) в символе Java (16 бит). два в одном? - PullRequest
6 голосов
/ 12 апреля 2011

Боюсь, у меня есть вопрос по деталям довольно перенасыщенной темы, я много искал, но не мог найти четкого ответа на эту конкретную, очевидную, очень важную проблему:

При преобразовании байта [] в строку с использованием UTF-8 каждый байт (8 бит) становится 8-битным символом, кодируемым UTF-8, но каждый символ UTF-8 сохраняется как 16-битный символ в Java.Это верно?Если да, это означает, что каждый тупой символ Java использует только первые 8 бит и потребляет вдвое больше памяти?Это тоже правильно?Интересно, как это расточительное поведение приемлемо?

Неужели нет какой-то хитрости, чтобы иметь 8-битную псевдо-строку?Не приведет ли это к меньшему потреблению памяти?Или, может быть, есть способ сохранить> два <8-битных символа в одном 16-битном символе java, чтобы избежать этой потери памяти? </p>

спасибо за любые ответы, вызывающие неясность ...

РЕДАКТИРОВАТЬ: привет, спасибовсе за ответы.Я знал о свойстве переменной длины UTF-8.Однако, поскольку мой источник - 8-битный байт, я понял (по-видимому, неправильно), что ему нужны только 8-битные слова UTF-8.Сохраняет ли преобразование UTF-8 странные символы, которые вы видите, когда в CLI вы делаете "cat somebinary"?Я думал, что UTF-8 просто каким-то образом использовался для отображения каждого из возможных 8-битных слов байта в одно конкретное 8-битное слово UTF-8.Неправильно?Я думал об использовании Base64, но это плохо, потому что он использует только 7 бит ..

переформулированные вопросы: есть ли более умный способ преобразовать байт во что-то String?Возможно, любимым было просто преобразовать byte [] в char [], но тогда у меня все еще есть 16-битные слова.

дополнительная информация о случае использования:

Я адаптирую Jedis (Java-клиент для NoSQL Redis) в качестве «примитивного уровня хранения» для hypergraphDB.Итак, jedis - это база данных для другой «базы данных».Моя проблема в том, что мне приходится постоянно кормить джедаев данными byte [], но внутренне> Redis <(реальный сервер) имеет дело только с «бинарными безопасными» строками.Поскольку Redis написан на C, длина символа 8 бит, AFAIK не ASCIII, который 7 бит.В джедае, однако, в мире java каждый персонаж имеет внутреннюю длину 16 бит.Я не понимаю этот код (пока), но я предполагаю, что jedis затем преобразует эту 16-битную строку Java в 8-битную строку, соответствующую Redis (([здесь] [3]). Он говорит, что расширяет FilterOutputStream. Я надеюсь обойтивообще преобразование строки byte [] <-> и использование этого Filteroutputstream ...?)

Теперь мне интересно: если бы мне приходилось все время преобразовывать byte [] и String, с размерами данных от очень маленьких допотенциально очень большой, разве нет огромной траты памяти на то, чтобы каждый 8-битный символ передавался как 16-битный в Java?

Ответы [ 7 ]

9 голосов
/ 12 апреля 2011

Неужели нет какой-то хитрости, чтобы иметь 8-битную псевдо-строку?

да, убедитесь, что у вас установлена ​​последняя версия Java. ;)

http://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html

-XX: + UseCompressedStrings Использовать байт [] для строк, который может быть представлен как чистый ASCII. (Представлено в Java 6, обновление 21, выпуск Performance)

РЕДАКТИРОВАТЬ: этот параметр не работает в обновлении 22 для Java 6 и не включен по умолчанию в обновлении 24 для Java 6. Примечание.

Следующая программа

public static void main(String... args) throws IOException {
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < 10000; i++)
        sb.append(i);

    for (int j = 0; j < 10; j++)
        test(sb, j >= 2);
}

private static void test(StringBuilder sb, boolean print) {
    List<String> strings = new ArrayList<String>();
    forceGC();
    long free = Runtime.getRuntime().freeMemory();

    long size = 0;
    for (int i = 0; i < 100; i++) {
        final String s = "" + sb + i;
        strings.add(s);
        size += s.length();
    }
    forceGC();
    long used = free - Runtime.getRuntime().freeMemory();
    if (print)
        System.out.println("Bytes per character is " + (double) used / size);
}

private static void forceGC() {
    try {
        System.gc();
        Thread.sleep(250);
        System.gc();
        Thread.sleep(250);
    } catch (InterruptedException e) {
        throw new AssertionError(e);
    }
}

Печатает это по умолчанию

Bytes per character is 2.0013668655941212
Bytes per character is 2.0013668655941212
Bytes per character is 2.0013606946433575
Bytes per character is 2.0013668655941212

с опцией -XX:+UseCompressedStrings

Bytes per character is 1.0014671435440285
Bytes per character is 1.0014671435440285
Bytes per character is 1.0014609725932648
Bytes per character is 1.0014671435440285
5 голосов
/ 12 апреля 2011

На самом деле у вас неправильная часть UTF-8: UTF-8 - это многобайтовая кодировка переменной длины, поэтому допустимые символы имеют длину 1-4 байта (другими словами, некоторые символы UTF-8 являются 8-разрядными некоторые являются 16-битными, некоторые 24-битными, а некоторые 32-битными). Хотя однобайтовые символы занимают 8 бит, существует еще много многобайтовых символов. Если бы у вас были только 1-байтовые символы, это позволило бы иметь всего 256 различных символов (a.k.a. «Extended ASCII»); этого может быть достаточно для 90% использования в английском языке (мой наивный догадывающийся), но укусит вас в задницу, как только вы даже примете что-либо за пределами этого подмножества слово наивное - английский, но не может быть написано только ASCII).

Итак, хотя UTF-16 (который использует Java) выглядит расточительно, на самом деле это не так. В любом случае, если вы не используете очень ограниченную встроенную систему (в таком случае, что вы там делаете с Java?), Попытка обрезать строки - это бессмысленная микрооптимизация.

Для более длинного введения в кодировку символов см., Например. это: http://www.joelonsoftware.com/articles/Unicode.html

2 голосов
/ 12 апреля 2011

Java хранит все свои "символы" внутри как двухбайтовые представления значения.Тем не менее, они не хранятся так же, как UTF-8.Например, максимальное поддерживаемое значение равно «\ uFFFF» (hex FFFF, dec 65536) или 11111111 двоичному 11111111 (два байта) - но это будет 3-байтовый символ Unicode на диске.

Единственно возможный«Потеря» предназначена для действительно «однобайтовых» байтовых символов в памяти (большинство «языковых» символов ASCII в действительности умещаются в 7 бит).Когда символы записываются на диск, они все равно будут в указанной кодировке (поэтому однобайтовые символы UTF-8 будут занимать только один байт).

Единственное место, где это имеет значение, - это куча JVM,Однако вам понадобятся тысячи и тысячи 8-битных символов, чтобы заметить реальную разницу в использовании кучи Java - которая будет намного перевешена всей дополнительной (хакерской) обработкой, которую вы сделали.

Миллион с лишним 8-битных символов в ОЗУ в любом случае «теряет» около 1 МБ ...

2 голосов
/ 12 апреля 2011

При преобразовании байта [] в строку с использованием UTF-8 каждый байт (8 бит) становится 8-битным символом, кодируемым UTF-8

Нет. При преобразовании byte[] в String с использованием UTF-8 каждая UTF-8 последовательность из 1-6 байтов преобразуется в UTF-16 последовательность из 1-2 16-битных символов.

Почти во всех случаях, по всему миру , эта последовательность UTF-16 содержит один символ.

В Западной Европе и Северной Америке для большинства текста используются только 8 битов этого 16-битного символа. Однако, если у вас есть знак евро, вам потребуется более 8 бит.

Для получения дополнительной информации см. Unicode . Или статья Джоэла Спольски .

1 голос
/ 12 апреля 2011

Redis (фактический сервер) имеет дело только с «бинарными безопасными» строками.

Я понимаю, что вы можете использовать произвольные последовательности октетов для ключей / значений. Если вы можете использовать любую последовательность C char без учета кодировки символов, то эквивалентом в Java будет тип byte.

Строки в Java неявно UTF-16. Я имею в виду, что вы можете вставить туда произвольные числа, но цель этого класса - представить данные символов Unicode. Методы, которые выполняют преобразования byte -to- char, выполняют операции транскодирования из известного кодирования в UTF-16.

Если Jedis обрабатывает ключи / значения как UTF-8, то он не будет поддерживать все значения, поддерживаемые Redis. Не каждая последовательность байтов является допустимой UTF-8, поэтому кодировка не может использоваться для двоичные безопасные строки.


Будет ли UTF-8 или UTF-16 потреблять больше памяти, зависит от данных - например, символ евро (€) потребляет три байта в UTF-8 и только два байта в UTF-16.

0 голосов
/ 26 апреля 2011

Просто для записи, я написал свою собственную маленькую реализацию байтового [] <-> String interconverter, который работает путем приведения каждых 2 байтов в 1 символ. Это примерно на 30-40% быстрее и потребляет (возможно, меньше) половину памяти стандартным способом Java: new String (somebyte) и someString.getBytes ().

Тем не менее, он несовместим с существующими строковыми закодированными байтами или байтовыми закодированными строками. Кроме того, небезопасно вызывать метод из разных JVM для общих данных.

https://github.com/ib84/castriba

0 голосов
/ 12 апреля 2011

Может быть, это то, что вы хотите:

// Store them into the 16 bit datatype.
char c1_8bit = 'a';
char c2_8bit = 'h';
char two_chars = (c1_8bit << 8) + c2_8bit;

// extract them
char c1_8bit = two_chars >> 8;
char c2_8bit = two_chars & 0xFF;

Конечно, этот трюк работает только с ASCII-символами (символы в диапазоне [0-255]). Зачем? Потому что вы хотите хранить свои символы таким образом:
xxxx xxxx yyyy yyyy с x - это символ 1, а y - это символ 2. Таким образом, это означает, что у вас есть только 8 бит на символ. И какое самое большое целое число вы можете сделать с 8 битами? Ответ: 255

255 = 0000 0000 1111 1111 (8 бит). И когда вы используете char> 255, тогда у вас будет это:
256 = 0000 0001 0000 0000 (более 8 бит), что не вписывается в 8 бит, который вы предоставляете для 1 символа.

Плюс: имейте в виду, что Java - это язык, разработанный умными людьми. Они знали, что они делают. Воткните Java API

...