Есть ли эффективная памятью замена java.lang. Строка?

Что такое NullPointerException?

Хорошим местом для начала является JavaDocs . Они охватывают это:

Брошено, когда приложение пытается использовать null в случае, когда требуется объект. К ним относятся:

  • Вызов метода экземпляра нулевого объекта.
  • Доступ или изменение поля нулевого объекта.
  • Выполнение длины null, как если бы это был массив.
  • Доступ или изменение слотов с нулевым значением, как если бы это был массив.
  • Бросать нуль, как если бы это было значение Throwable.

Приложения должны бросать экземпляры этого класса для указания других незаконных видов использования нулевого объекта.

blockquote>

Также, если вы попытаетесь использовать нулевую ссылку с synchronized, который также выдаст это исключение, за JLS :

SynchronizedStatement:
    synchronized ( Expression ) Block
  • В противном случае, если значение выражения равно null, NullPointerException.
blockquote>

Как это исправить?

Итак, у вас есть NullPointerException. Как вы это исправите? Возьмем простой пример, который выдает NullPointerException:

public class Printer {
    private String name;

    public void setName(String name) {
        this.name = name;
    }

    public void print() {
        printString(name);
    }

    private void printString(String s) {
        System.out.println(s + " (" + s.length() + ")");
    }

    public static void main(String[] args) {
        Printer printer = new Printer();
        printer.print();
    }
}

Идентифицирует нулевые значения

. Первый шаг - точно определить , значения которого вызывают исключение . Для этого нам нужно выполнить некоторую отладку. Важно научиться читать stacktrace . Это покажет вам, где было выбрано исключение:

Exception in thread "main" java.lang.NullPointerException
    at Printer.printString(Printer.java:13)
    at Printer.print(Printer.java:9)
    at Printer.main(Printer.java:19)

Здесь мы видим, что исключение выбрано в строке 13 (в методе printString). Посмотрите на строку и проверьте, какие значения равны нулю, добавив протоколирующие операторы или используя отладчик . Мы обнаруживаем, что s имеет значение null, а вызов метода length на него вызывает исключение. Мы видим, что программа перестает бросать исключение, когда s.length() удаляется из метода.

Трассировка, где эти значения взяты из

Затем проверьте, откуда это значение. Следуя вызовам метода, мы видим, что s передается с printString(name) в методе print(), а this.name - null.

Трассировка, где эти значения должны быть установлены

Где установлен this.name? В методе setName(String). С некоторой дополнительной отладкой мы видим, что этот метод вообще не вызывается. Если этот метод был вызван, обязательно проверьте порядок , что эти методы вызывают, а метод set не будет называться после методом печати. ​​

Этого достаточно, чтобы дать нам решение: добавить вызов printer.setName() перед вызовом printer.print().

Другие исправления

Переменная может иметь значение по умолчанию setName может помешать ему установить значение null):

private String name = "";

Либо метод print, либо printString может проверить значение null например:

printString((name == null) ? "" : name);

Или вы можете создать класс, чтобы name всегда имел ненулевое значение :

public class Printer {
    private final String name;

    public Printer(String name) {
        this.name = Objects.requireNonNull(name);
    }

    public void print() {
        printString(name);
    }

    private void printString(String s) {
        System.out.println(s + " (" + s.length() + ")");
    }

    public static void main(String[] args) {
        Printer printer = new Printer("123");
        printer.print();
    }
}

См. также:

Я все еще не могу найти проблему

Если вы попытались отладить проблему и до сих пор не имеете решения, вы можете отправить вопрос для получения дополнительной справки, но не забудьте включить то, что вы пробовали до сих пор. Как минимум, включите stacktrace в вопрос и отметьте важные номера строк в коде. Также попробуйте сначала упростить код (см. SSCCE ).

36
задан Perception 16 December 2012 в 14:52
поделиться

13 ответов

Помните, что существует много типов сжатия. Используя кодирование методом Хаффмана хороший подход общего назначения - но это - относительно интенсивный ЦП. Для реализации B+Tree я работал над несколькими годами назад, мы знали, что ключи будут, вероятно, иметь общие начальные символы, таким образом, мы реализовали алгоритм сжатия начального символа для каждой страницы в B+Tree. Код был легок, очень, очень быстро, и привел к использованию памяти 1/3 того, с чего мы запустили. В нашем случае настоящей причине для того, чтобы сделать это должно было оставить свободное место на диске и уменьшить время, проведенное на диске-> передачи RAM (и что 1/3 сбережения имели огромное значение в эффективной производительности диска).

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

Попытка оптимизировать несколько байтов тут и там в Строковом объекте не может стоить того в сравнении.

0
ответ дан Kevin Day 27 November 2019 в 05:39
поделиться

Вы сказали для не повторения предложения статьи прокрутки собственной схемы интернирования, но что случилось с String.intern самой? Статья содержит следующий холостой комментарий:

Многочисленные причины существуют для предотвращения String.intern () метод. Каждый - те немного современных JVMs, может интернировать большие объемы данных.

, Но даже если бы использование памяти фигурирует с 2002 все еще, содержат шесть лет спустя, я был бы удивлен, не были ли никакие успехи сделаны на том, сколько данных JVMs может интернировать.

Это не просто риторический вопрос - мне интересно знать, существуют ли серьезные основания избежать его. Это реализовано неэффективно для высоко многопоточного использования? Это заполняет некоторую специальную определенную область JVM "кучи"? У Вас действительно есть сотни мегабайтов уникальных строк (настолько интернирующий, было бы бесполезно так или иначе)?

0
ответ дан Sam Stokes 27 November 2019 в 05:39
поделиться

Я полагаю, что Строки являются менее интенсивно использующими память в течение некоторого времени теперь, потому что инженеры Java реализовали шаблон разработки в наилегчайшем весе для совместного использования как можно больше. На самом деле Строки, которые имеют ту же точку значения к тому же самому объекту в памяти, которой я верю.

0
ответ дан nkr1pt 27 November 2019 в 05:39
поделиться

Из любопытства несколько байтов сохранены действительно стоящий того?

Обычно, я предлагаю угробить строки по причинам производительности, в пользу StringBuffer (Помните, Строки неизменны).

действительно ли Вы являетесь серьезно исчерпывающими своя "куча" из строковых ссылок?

0
ответ дан FlySwat 27 November 2019 в 05:39
поделиться

Java выбрал UTF-16 для компромисса скорости и размера ресурса хранения. Данными UTF-8 обработки является намного больше ЛАВАША, чем обработка данных UTF-16 (например, при попытке найти положение символа X в массиве байтов, как Вы собираетесь сделать так быстрым способом, если каждый символ может иметь один, два, три или сгладить к шести байтам? Когда-нибудь думавший об этом? Осмотр через строковый байт байтом не действительно быстр, Вы видите?). Конечно, UTF-32 был бы самым легким обработать, но потратить впустую дважды пространство памяти. Вещи изменились с ранних дней Unicode. Теперь для определенных символов нужны 4 байта, даже когда UTF-16 используется. Обработка их правильно делает UTF-16 почти одинаково плохо как UTF-8.

Так или иначе, пребывайте в уверенности, что при реализации Строкового класса с внутренней памятью, которая использует UTF-8, Вы могли бы выиграть некоторую память, но Вы потеряете скорость обработки для многих строковых методов. Также Вашим аргументом является слишком ограниченная точка зрения. Ваш аргумент не будет сохраняться для кого-то в Японии, так как японские символы не будут меньшими в UTF-8, чем в UTF-16 (на самом деле, они возьмут 3 байта в UTF-8, в то время как они - только два байта в UTF-16). Я не понимаю, почему программисты в таком глобальном мире как сегодня с вездесущим Интернетом все еще говорят о "западных языках", как будто это - все, что рассчитало бы, как будто только западный мир имеет компьютеры, и остальная часть его живет в пещерах. Рано или поздно любое приложение укушено тем, что ему не удается эффективно обработать несимволы западных алфавитов.

2
ответ дан Mecki 27 November 2019 в 05:39
поделиться

Просто сожмите их всех с gzip.:) Просто шутящий..., но я видел более странные вещи, и это дало бы Вам намного меньшие данные за значительный счет ЦП.

Единственные другие Строковые реализации, о которых я знаю, являются теми в классах Javolution. Я не думаю, что они - больше эффективной памяти, хотя:

http://www.javolution.com/api/javolution/text/Text.html
http://www.javolution.com/api/javolution/text/TextBuilder.html

2
ответ дан jsight 27 November 2019 в 05:39
поделиться

Внутренняя кодировка UTF-8 имеет свои преимущества (такие как меньший объем потребляемой памяти, на который Вы указали), но это имеет недостатки также.

, Например, определяя длину знака (а не длина байта) UTF-8 закодированная строка является O (n) операция. В строке Java стоимость определения длины знака является O (1), в то время как генерация представления UTF-8 является O (n).

Это - все о приоритетах.

дизайн Структуры данных может часто рассматриваться как компромисс между скоростью и пространством. В этом случае я думаю, что разработчики строкового API Java сделали выбор на основе этих критериев:

  • Строковый класс должен поддерживать все возможные unicode символы.

  • , Хотя unicode определяет 1 байт, 2 байта, и 4-байтовые варианты, 4-байтовые символы (на практике) довольно редки, таким образом, это должно хорошо представить их как суррогатные пары. Вот почему Java использует 2-байтовый символьный примитив.

  • , Когда люди длительность вызова (), indexOf (), и charAt () методы, они интересуются позицией символа, не положением байта. Для создания внедрений FAST этих методов необходимо избежать внутренней кодировки UTF-8.

  • Языки как C++ делают жизнь программиста более сложной, определяя три различных типа символов и вынуждая программиста выбрать между ними. Большинство программистов начинается с помощью простых строк ASCII, но когда они в конечном счете должны поддерживать международные символы, процесс изменения кода для использования многобайтовых символов является чрезвычайно болезненным. Я думаю, что разработчики Java сделали превосходный выбор компромисса путем высказывания, что все строки состоят из 2-байтовых символов.

7
ответ дан benjismith 27 November 2019 в 05:39
поделиться

Я думаю, что необходимо быть очень осторожны относительно базирования любых идей и/или предположений прочь о статье javaworld.com с 2002. Были многие, много изменений в компиляторе и JVM за эти шесть лет с тех пор. По крайней мере протестируйте свою гипотезу и решение против современной JVM сначала, чтобы удостовериться, что решение даже стоит усилия.

7
ответ дан matt b 27 November 2019 в 05:39
поделиться

Статья указывает на две вещи:

  1. Символьные массивы увеличиваются в блоках 8 байтов.
  2. существуют значительные различия в размере между символом [] и Строковыми объектами.

издержки происходят из-за включения символа [] ссылка на объект и три ints: смещение, длина и пространство для хранения хэш-кода Строки, плюс стандарт наверху того, чтобы просто быть объектом.

Немного отличающийся от String.intern (), или символьный массив, используемый String.substring (), использует единственный символ [] для всех Строк, это означает, что Вы не должны хранить ссылку на объект в своей обертке подобный Строке объект. Вам все еще было бы нужно смещение, и Вы представляете (большой) предел на то, сколько символов Вы можете иметь всего.

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

пространственно-временной компромисс не хранения хеша может помочь Вам, если Вам часто не нужен он.

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

, Кроме того, я мог оставить входные данные в массиве байтов, это было первоначально считано в - файл с отображенной памятью.

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

java.lang. Строка была знакомым молотком для того, что я хотел сделать, но не лучший инструмент для задания.

10
ответ дан Stephen Denne 27 November 2019 в 05:39
поделиться

В Terracotta у нас есть некоторые случаи, где мы сжимаем большие Строки, когда они отправляются вокруг сети и на самом деле оставляют их сжатыми, пока распаковка не необходима. Мы делаем это путем преобразования символа [] к байту [], сжатия байта [], затем кодирования того байта [] назад в исходный символ []. Для определенных операций как хеш и длина, мы можем ответить на те вопросы, не декодируя сжатую строку. Для данных как большие строки XML можно получить существенное сжатие этот путь.

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

Это все сделано с отладкой кода байта на java.lang. Строка, которую мы нашли, является очень тонкой из-за того, как ранняя Строка используется в запуске, но устойчива, если Вы следуете некоторым инструкциям.

21
ответ дан Alex Miller 27 November 2019 в 05:39
поделиться

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

, Если Вы собираетесь использовать программирование OO, это - стоимость наличия ясного, применимого, удобного в сопровождении кода.

Для ответа помимо очевидного (который является, что, если использование памяти настолько важно, необходимо, вероятно, использовать C), Вы могли бы реализовать свои собственные Строки с внутренним представлением в массивах байтов BCD.

, Который на самом деле звучит как забава, я мог бы сделать это только для ударов:)

массив Java А берет 2 байта за объект. Закодированная цифра BCD берет 6 битов за букву IIRC, делая Ваши строки значительно меньшими. Было бы немного стоимости преобразования вовремя, но не слишком плохо действительно. Действительно большая проблема состоит в том, что необходимо было бы преобразовать в строку, чтобы сделать что-либо с ним.

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

Наконец примечание. Я полностью против развертывания чего-либо как это, если у Вас нет 3 вещей:

  • реализация, сделанная самый читаемый путь
  • Результаты испытаний и требования, показывающие, как та реализация не отвечает требованиям
  • Результаты испытаний о том, как "улучшенная" реализация ДЕЙСТВИТЕЛЬНО отвечает требованиям.

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

1
ответ дан Bill K 27 November 2019 в 05:39
поделиться

Сегодня (2010 год) каждый гигабайт, добавляемый на сервер, стоит около 80 фунтов стерлингов или 120 долларов. Прежде чем приступить к реинжинирингу String, вы должны спросить себя, действительно ли это того стоит.

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

Способ уплотнения строк зависит от модели использования. Есть ли много повторяющихся строк? (используйте пул объектов) Много ли длинных строк? (использовать сжатие/кодирование)

Еще одна причина, по которой вам могут понадобиться строки меньшего размера, - это уменьшение использования кэша. Даже самые большие процессоры имеют около 8 МБ - 12 МБ кэша. Это может быть очень ценным ресурсом, который нелегко увеличить. В этом случае я предлагаю вам рассмотреть альтернативы строкам, но вы должны иметь в виду, насколько велика разница в £ или $ по сравнению с затратами времени.

1
ответ дан 27 November 2019 в 05:39
поделиться

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

  • Разделите строку на 4-символьные "слова" (если вам нужен весь Unicode) и храните эти байты в long с помощью маскирования/сдвига битов. Если вам не нужен полный набор Unicode, а только 255 символов ASCII, вы можете поместить 8 символов в каждый long. Добавляйте (char) 0 в конец строки до тех пор, пока длина не разделится ровно на 4 (или 8).
  • Переопределите реализацию хэш-набора (например, TLongHashSet от Trove) и добавьте каждое "слово" в этот набор, составляя массив внутренних индексов того, где long заканчивается в наборе (убедитесь, что вы также обновляете свой индекс, когда набор пересоздается)
  • Используйте двумерный массив int для хранения этих индексов (таким образом, первое измерение - каждая сжатая строка, а второе измерение - индекс каждого "слова" в хэш-наборе), и возвращайте единственный int индекс в этом массиве обратно вызывающей стороне (вы должны владеть массивами слов, чтобы вы могли глобально обновлять индекс при перехешировании, как упоминалось выше)

Преимущества:

  • Постоянное время сжатия/декомпрессии
  • Строка длиной n представляется как массив int длиной n/4, с дополнительными накладными расходами на набор слов long, который растет асимптотически по мере того, как встречается все меньше уникальных "слов"
  • Пользователю возвращается единственный int "идентификатор" строки, который удобно и мало хранить в своих объектах

Недостатки:

  • Немного хакерский, поскольку он включает сдвиг битов, возится с внутренностями хэш-набора и т.д. (Bill K не одобрил бы)
  • Хорошо работает, когда вы не ожидаете большого количества дубликатов строк. Очень дорого проверять, существует ли уже строка в библиотеке.
1
ответ дан 27 November 2019 в 05:39
поделиться
Другие вопросы по тегам:

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