Хорошим местом для начала является JavaDocs . Они охватывают это:
Брошено, когда приложение пытается использовать null в случае, когда требуется объект. К ним относятся:
- Вызов метода экземпляра нулевого объекта.
- Доступ или изменение поля нулевого объекта.
- Выполнение длины null, как если бы это был массив.
- Доступ или изменение слотов с нулевым значением, как если бы это был массив.
- Бросать нуль, как если бы это было значение Throwable.
Приложения должны бросать экземпляры этого класса для указания других незаконных видов использования нулевого объекта.
blockquote>Также, если вы попытаетесь использовать нулевую ссылку с
synchronized
, который также выдаст это исключение, за JLS :SynchronizedStatement: synchronized ( Expression ) Block
blockquote>
- В противном случае, если значение выражения равно null,
NullPointerException
.Как это исправить?
Итак, у вас есть
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 = "";
Либо метод
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 ).
Помните, что существует много типов сжатия. Используя кодирование методом Хаффмана хороший подход общего назначения - но это - относительно интенсивный ЦП. Для реализации B+Tree я работал над несколькими годами назад, мы знали, что ключи будут, вероятно, иметь общие начальные символы, таким образом, мы реализовали алгоритм сжатия начального символа для каждой страницы в B+Tree. Код был легок, очень, очень быстро, и привел к использованию памяти 1/3 того, с чего мы запустили. В нашем случае настоящей причине для того, чтобы сделать это должно было оставить свободное место на диске и уменьшить время, проведенное на диске-> передачи RAM (и что 1/3 сбережения имели огромное значение в эффективной производительности диска).
причина, что я поднимаю это, состоит в том, что реализация пользовательской строки не помогла бы очень здесь. Мы только смогли достигнуть усилений, которые мы сделали, потому что мы работали слой контейнер , в котором живут строки.
Попытка оптимизировать несколько байтов тут и там в Строковом объекте не может стоить того в сравнении.
Вы сказали для не повторения предложения статьи прокрутки собственной схемы интернирования, но что случилось с String.intern
самой? Статья содержит следующий холостой комментарий:
Многочисленные причины существуют для предотвращения String.intern () метод. Каждый - те немного современных JVMs, может интернировать большие объемы данных.
, Но даже если бы использование памяти фигурирует с 2002 все еще, содержат шесть лет спустя, я был бы удивлен, не были ли никакие успехи сделаны на том, сколько данных JVMs может интернировать.
Это не просто риторический вопрос - мне интересно знать, существуют ли серьезные основания избежать его. Это реализовано неэффективно для высоко многопоточного использования? Это заполняет некоторую специальную определенную область JVM "кучи"? У Вас действительно есть сотни мегабайтов уникальных строк (настолько интернирующий, было бы бесполезно так или иначе)?
Я полагаю, что Строки являются менее интенсивно использующими память в течение некоторого времени теперь, потому что инженеры Java реализовали шаблон разработки в наилегчайшем весе для совместного использования как можно больше. На самом деле Строки, которые имеют ту же точку значения к тому же самому объекту в памяти, которой я верю.
Из любопытства несколько байтов сохранены действительно стоящий того?
Обычно, я предлагаю угробить строки по причинам производительности, в пользу StringBuffer (Помните, Строки неизменны).
действительно ли Вы являетесь серьезно исчерпывающими своя "куча" из строковых ссылок?
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). Я не понимаю, почему программисты в таком глобальном мире как сегодня с вездесущим Интернетом все еще говорят о "западных языках", как будто это - все, что рассчитало бы, как будто только западный мир имеет компьютеры, и остальная часть его живет в пещерах. Рано или поздно любое приложение укушено тем, что ему не удается эффективно обработать несимволы западных алфавитов.
Просто сожмите их всех с gzip.:) Просто шутящий..., но я видел более странные вещи, и это дало бы Вам намного меньшие данные за значительный счет ЦП.
Единственные другие Строковые реализации, о которых я знаю, являются теми в классах Javolution. Я не думаю, что они - больше эффективной памяти, хотя:
http://www.javolution.com/api/javolution/text/Text.html
http://www.javolution.com/api/javolution/text/TextBuilder.html
Внутренняя кодировка 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-байтовых символов.
Я думаю, что необходимо быть очень осторожны относительно базирования любых идей и/или предположений прочь о статье javaworld.com с 2002. Были многие, много изменений в компиляторе и JVM за эти шесть лет с тех пор. По крайней мере протестируйте свою гипотезу и решение против современной JVM сначала, чтобы удостовериться, что решение даже стоит усилия.
Статья указывает на две вещи:
издержки происходят из-за включения символа [] ссылка на объект и три ints: смещение, длина и пространство для хранения хэш-кода Строки, плюс стандарт наверху того, чтобы просто быть объектом.
Немного отличающийся от String.intern (), или символьный массив, используемый String.substring (), использует единственный символ [] для всех Строк, это означает, что Вы не должны хранить ссылку на объект в своей обертке подобный Строке объект. Вам все еще было бы нужно смещение, и Вы представляете (большой) предел на то, сколько символов Вы можете иметь всего.
Вам больше не была бы нужна длина при использовании специального конца строкового маркера. Это сохраняет четыре байта для длины, но стоит Вам двух байтов за маркер, плюс дополнительное время, сложность и риски переполнения буфера.
пространственно-временной компромисс не хранения хеша может помочь Вам, если Вам часто не нужен он.
Для приложения, с которым я работал, где мне было нужно супер быстро и память эффективная обработка большого количества строк, я смог оставить данные в его закодированной форме и работу с массивами байтов. Мое выходное кодирование совпало с моим входным кодированием, и я не должен был декодировать байты к символам, ни закодировать назад к байтам снова для вывода.
, Кроме того, я мог оставить входные данные в массиве байтов, это было первоначально считано в - файл с отображенной памятью.
Мои объекты состояли из международного смещения (предел удовлетворил моей ситуации), международная длина и международный хэш-код.
java.lang. Строка была знакомым молотком для того, что я хотел сделать, но не лучший инструмент для задания.
В Terracotta у нас есть некоторые случаи, где мы сжимаем большие Строки, когда они отправляются вокруг сети и на самом деле оставляют их сжатыми, пока распаковка не необходима. Мы делаем это путем преобразования символа [] к байту [], сжатия байта [], затем кодирования того байта [] назад в исходный символ []. Для определенных операций как хеш и длина, мы можем ответить на те вопросы, не декодируя сжатую строку. Для данных как большие строки XML можно получить существенное сжатие этот путь.
Перемещение сжатых данных вокруг сети является определенной победой. Хранение его сжалось, зависит от варианта использования. Конечно, у нас есть некоторые кнопки, чтобы выключить это и изменить длину, в которой сжатие включает, и т.д.
Это все сделано с отладкой кода байта на java.lang. Строка, которую мы нашли, является очень тонкой из-за того, как ранняя Строка используется в запуске, но устойчива, если Вы следуете некоторым инструкциям.
Существуют издержки создания объекта (по крайней мере, таблица отправки), издержки того, что это использует 2 байта за букву, и издержки нескольких дополнительных переменных там, которые создаются для фактического улучшения скорости и использования памяти во многих случаях.
, Если Вы собираетесь использовать программирование OO, это - стоимость наличия ясного, применимого, удобного в сопровождении кода.
Для ответа помимо очевидного (который является, что, если использование памяти настолько важно, необходимо, вероятно, использовать C), Вы могли бы реализовать свои собственные Строки с внутренним представлением в массивах байтов BCD.
, Который на самом деле звучит как забава, я мог бы сделать это только для ударов:)
массив Java А берет 2 байта за объект. Закодированная цифра BCD берет 6 битов за букву IIRC, делая Ваши строки значительно меньшими. Было бы немного стоимости преобразования вовремя, но не слишком плохо действительно. Действительно большая проблема состоит в том, что необходимо было бы преобразовать в строку, чтобы сделать что-либо с ним.
у Вас все еще есть издержки экземпляра объекта для волнения о..., но это было бы лучше обращено путем обновления дизайна, чем попытки устранить экземпляры.
Наконец примечание. Я полностью против развертывания чего-либо как это, если у Вас нет 3 вещей:
Без всех трех из тех, я ударил бы любое оптимизированное решение разработчик, представленный мне.
Сегодня (2010 год) каждый гигабайт, добавляемый на сервер, стоит около 80 фунтов стерлингов или 120 долларов. Прежде чем приступить к реинжинирингу String, вы должны спросить себя, действительно ли это того стоит.
Если вы собираетесь сэкономить гигабайт памяти, то возможно. Десять гигабайт - определенно. Если вы хотите сэкономить десятки мегабайт, вы, скорее всего, потратите больше времени, чем это того стоит.
Способ уплотнения строк зависит от модели использования. Есть ли много повторяющихся строк? (используйте пул объектов) Много ли длинных строк? (использовать сжатие/кодирование)
Еще одна причина, по которой вам могут понадобиться строки меньшего размера, - это уменьшение использования кэша. Даже самые большие процессоры имеют около 8 МБ - 12 МБ кэша. Это может быть очень ценным ресурсом, который нелегко увеличить. В этом случае я предлагаю вам рассмотреть альтернативы строкам, но вы должны иметь в виду, насколько велика разница в £ или $ по сравнению с затратами времени.
В настоящее время я реализую метод сжатия следующим образом (я работаю над приложением, которому нужно хранить в памяти очень большое количество документов, чтобы мы могли производить вычисления между документами):
long
с помощью маскирования/сдвига битов. Если вам не нужен полный набор Unicode, а только 255 символов ASCII, вы можете поместить 8 символов в каждый long
. Добавляйте (char) 0
в конец строки до тех пор, пока длина не разделится ровно на 4 (или 8). TLongHashSet
от Trove) и добавьте каждое "слово" в этот набор, составляя массив внутренних индексов того, где long
заканчивается в наборе (убедитесь, что вы также обновляете свой индекс, когда набор пересоздается)int
для хранения этих индексов (таким образом, первое измерение - каждая сжатая строка, а второе измерение - индекс каждого "слова" в хэш-наборе), и возвращайте единственный int
индекс в этом массиве обратно вызывающей стороне (вы должны владеть массивами слов, чтобы вы могли глобально обновлять индекс при перехешировании, как упоминалось выше)Преимущества:
int
длиной n/4, с дополнительными накладными расходами на набор слов long
, который растет асимптотически по мере того, как встречается все меньше уникальных "слов"int
"идентификатор" строки, который удобно и мало хранить в своих объектахНедостатки: