Вопрос

Мое приложение является многопотативным с интенсивной обработкой строки. Мы испытываем чрезмерное потребление памяти и профилирование, продемонстрировало, что это связано с строковыми данными. Я думаю, что потребление памяти принесет выгоду из-за некоторой реализации какого-подвещания излучения или даже кеш (я точно знаю, что строки часто дублируются, хотя у меня нет никаких жестких данных в этом отношении).

Я посмотрел на постоянный бассейн Java и String.innern, но кажется, что он может спровоцировать некоторые пробные проблемы.

Какая была бы лучшая альтернатива для реализации нанесения накладной, многопоточенного пула строк в Java?

Редактировать: также см. Мой предыдущий, связанный вопрос: Как Java внедрит шаблон излучения для строки под капотом?

Это было полезно?

Решение

Примечание. Этот ответ использует примеры, которые могут быть не актуальны в современной среде выполнения JVM-библиотек. В частности, substring Пример больше не является проблемой в OpenJDK / Oracle 7+.

Я знаю, что это идет против того, что люди часто говорят вам, но иногда явно создают новые String экземпляры могу быть значительным способом уменьшить вашу память.

Поскольку строки неизменяются, несколько методов используют этот факт и делят массив поддерживающего символа для сохранения памяти. Однако иногда это может фактически увеличить память, предотвращая сбор мусора неиспользуемых частей этих массивов.

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

//Format:
//ID: [WARNING|ERROR|DEBUG] Message...
String testLine = "5AB729: WARNING Some really really really long message";

Matcher matcher = Pattern.compile("([A-Z0-9]*): WARNING.*").matcher(testLine);
if ( matcher.matches() ) {
    String id = matcher.group(1);
        //...do something with id...
}

Но посмотрите на данные, которые на самом деле хранятся:

    //...
    String id = matcher.group(1);
    Field valueField = String.class.getDeclaredField("value");
    valueField.setAccessible(true);

    char[] data = ((char[])valueField.get(id));
    System.out.println("Actual data stored for string \"" + id + "\": " + Arrays.toString(data) );

Это вся тестовая линия, потому что сопоставитель просто заворачивает новый экземпляр строка вокруг тех же данных символов. Сравните результаты при замене String id = matcher.group(1); с участием String id = new String(matcher.group(1));.

Другие советы

Это уже сделано на уровне JVM. Вам нужно только убедиться, что вы не создаете new Stringс каждый раз, явно или неявно.

Т.е. не делай:

String s1 = new String("foo");
String s2 = new String("foo");

Это создаст два экземпляра в куче. Скорее делай это:

String s1 = "foo";
String s2 = "foo";

Это создаст один экземпляр в куче, и оба будут относиться то же самое (в качестве доказательства, s1 == s2 вернусь true здесь).

Также не используйте += к объединенным строкам (в цикле):

String s = "";
for (/* some loop condition */) {
    s += "new";
}

То += неявно создает а new String в куче каждый раз. Скорее сделать так

StringBuilder sb = new StringBuilder();
for (/* some loop condition */) {
    sb.append("new");
}
String s = sb.toString();

Если вы можете, скорее использовать StringBuilder или его синхронизированный брат StringBuffer вместо String для "интенсивной обработки строки". Он предлагает полезные методы для конкретных тем целей, таких как append(), insert(), delete(), и т. Д. Также см. его javadoc..

Эффективно упаковывать строки в память! Однажды я написал эффективный класс набора гипер памяти, где строки хранились как дерево. Если бы лист был достигнут путем прохождения букв, запись содержится в комплекте. Быстро работать тоже и идеально подходит для хранения большого словаря.

И не забывайте, что строки часто являются самой большой ролью в памяти почти в каждом приложении, которое я профилировал, так что не заботится о них, если вам нужны.

Иллюстрация:

У вас есть 3 строки: пиво, бобы и кровь. Вы можете создать структуру дерева, как это:

B
+-e
  +-er
  +-ans
+-lood

Очень эффективен, например, список имен улиц, это, очевидно, наиболее разумно с фиксированным словарем, поскольку вставка не может быть выполнена эффективно. На самом деле структура должна быть создана один раз, затем сериализована и впоследствии только нагружена.

Java 7/8

Если вы делаете то, что говорит об общепринятом ответе и используя Java 7 или новее, вы не делаете то, что он говорит, что вы.

Реализация subString() изменился.

Никогда не пишите код, который опирается на реализацию, которая может резко измениться и может сделать все хуже, если вы полагаетесь на старое поведение.

1950    public String substring(int beginIndex, int endIndex) {
1951        if (beginIndex < 0) {
1952            throw new StringIndexOutOfBoundsException(beginIndex);
1953        }
1954        if (endIndex > count) {
1955            throw new StringIndexOutOfBoundsException(endIndex);
1956        }
1957        if (beginIndex > endIndex) {
1958            throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
1959        }
1960        return ((beginIndex == 0) && (endIndex == count)) ? this :
1961            new String(offset + beginIndex, endIndex - beginIndex, value);
1962    }

Поэтому, если вы используете принятый ответ с Java 7 или новее, вы создаете в два раза больше использования памяти и мусора, который необходимо собирать.

Во-первых, решите, насколько ваша заявка и разработчики страдают, если вы устранили некоторые из этих анализа. Более быстрые приложения вы не хотите, если вы удваиваете ставку оборота сотрудника в процессе! Я думаю, что основываясь на вашем вопросе, мы можем предположить, что вы уже проходили этот тест.

Во-вторых, если вы не можете устранить создание объекта, то ваша следующая цель должна заключаться в том, чтобы гарантировать, что она не выживает в коллекции Eden. И поиск анализа может решить эту проблему. Тем не менее, кэш «реализован правильно» (я не согласен с этой основной предпосылкой, но я не буду охватить вас с развязкой сопутствующего: Вы бы заменяли одно вида давления памяти для другого.

Существует вариация идиома поиска анализа, который меньше страдает от своего рода побочных повреждений, который вы обычно получаете от полной кэширования, и это простое предварительное наступление (см. Также «Memoation»). Шаблон, который вы обычно видите, это Тип безопасного перечисления (TSE). С TSE вы разбираете строку, пропустите ее в TSE, чтобы получить ассоциированный перечисленный тип, а затем вы бросаете струну.

Текст вы обрабатываете свободную форму или вход должен следовать жесткой спецификации? Если множество ваших текстов отображается в фиксированном наборе возможных значений, то TSE может помочь вам здесь, и служит большему количеству: добавление контекста / семантики на вашу информацию в точке создания, а не в точке использования Отказ

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top