предисловие
Оптимизация кода — очень важная тема. Некоторые люди могут подумать, что это бесполезно. Какие мелкие места нужно изменить? Как влияет модификация или отсутствие модификации на эффективность работы кода? Я задумался над этим вопросом так, как кит в море, полезно ли ему есть маленькую креветку? Это бесполезно, но съев больше креветок, киты насытятся. То же самое и с оптимизацией кода: если проект нацелен на то, чтобы как можно скорее выйти в онлайн без ошибок, то можно сосредоточиться на большом и отпустить на это время мелкое, а детали кода можно не шлифовать; но если есть достаточно времени для разработки и поддержки кода, вы должны учитывать каждый Детали, которые можно оптимизировать, накапливаются Накопление небольших точек оптимизации определенно повысит эффективность работы кода.
Цели оптимизации кода:
1, уменьшить объем кода
2, чтобы повысить эффективность кода для запуска
Детали оптимизации кода
1. Попробуйте указать финальный модификатор классов и методов
Класс с модификатором final не является производным. В основном API Java есть много примеров применения final, таких как java.lang.String, весь класс является final. Указание модификатора final для класса делает класс ненаследуемым, а указание модификатора final для метода делает метод непереопределяемым. Если класс обозначен как окончательный, все методы этого класса являются окончательными. Компилятор Java будет искать возможности для встраивания всех конечных методов. Встраивание играет важную роль в повышении эффективности Java. Подробнее см. в разделе Оптимизация времени выполнения Java. Это может повысить производительность в среднем на 50%.
2. Максимально возможное повторное использование объектов
В частности, для использования объектов String вместо этого следует использовать StringBuilder/StringBuffer, когда происходит конкатенация строк. Поскольку виртуальной машине Java требуется не только время для создания объектов, но также может потребоваться время для сбора мусора и обработки этих объектов в будущем, создание слишком большого количества объектов сильно повлияет на производительность программы.
3. Используйте локальные переменные, когда это возможно
Параметры, передаваемые при вызове метода, и временные переменные, созданные при вызове, быстрее сохраняются в стеке, а другие переменные, такие как статические переменные, переменные экземпляра и т. д., создаются в куче, что медленнее. Кроме того, переменные, созданные в стеке, исчезают по завершении метода, и дополнительная сборка мусора не требуется.
4. Вовремя закрыть поток
В процессе программирования на Java необходимо соблюдать осторожность при подключении к базе данных и операциях с потоками ввода-вывода, после использования вовремя закрывать ее для освобождения ресурсов. Поскольку работа с этими большими объектами вызовет много накладных расходов в системе, небольшая небрежность приведет к серьезным последствиям.
5. Сведите к минимуму двойной учет переменных
Уточните концепцию, вызов метода, даже если в методе есть только один оператор, потребляет, включая создание кадра стека, защиту сцены при вызове метода и восстановление сцены при вызове метода. Так, например, следующая операция:
for (int i = 0; i < list.size(); i++){...}
Рекомендуется заменить:
for (int i = 0, int length = list.size(); i < length; i++){...}
Таким образом, список .size () много времени, снижение потребления много
6. Попробуйте использовать стратегию ленивой загрузки, то есть создавайте ее тогда, когда это необходимо
Например:
String str = "aaa";if (i == 1)
{
list.add(str);
}
Рекомендуется заменить на:
if (i == 1)
{
String str = "aaa";
list.add(str);
}
7. Используйте исключения с осторожностью
Исключения плохо влияют на производительность. Выброс исключения сначала создает новый объект.Конструктор интерфейса Throwable вызывает локальный метод синхронизации с именем fillInStackTrace().Метод fillInStackTrace() проверяет стек и собирает информацию о трассировке вызовов. Всякий раз, когда создается исключение, виртуальная машина Java должна корректировать стек вызовов, поскольку во время обработки создается новый объект. Исключения должны использоваться только для обработки ошибок и не должны использоваться для управления ходом выполнения программы.
8. Не используйте попробуйте ... поймать ... в петле, он должен быть помещен в самый внешний слой
Если только в этом нет крайней необходимости. Если ты так пишешь без причины, пока твой руководитель более старший и обсессивно-компульсивный, 80% из них будут ругать тебя за то, что ты пишешь такой мусорный код.
9. Если длину добавляемого контента можно оценить, укажите начальную длину базовой коллекции и класса инструмента, реализованного в виде массива.
Например, ArrayList, LinkedLlist, StringBuilder, StringBuffer, HashMap, HashSet и т. д. В качестве примера возьмем StringBuilder:
(1) StringBuilder() // по умолчанию выделяется 16 символов пробела
(2) StringBuilder(int size) // Пространство символов размера выделяется по умолчанию
(3) StringBuilder(String str) // 16 символов + символы str.length() выделяются по умолчанию
пространство символов
Вы можете установить его мощность инициализации через класс (здесь имеется в виду не только StringBuilder выше), что может значительно повысить производительность. Например, для StringBuilder длина указывает количество символов, которые может содержать текущий StringBuilder. Поскольку, когда StringBuilder достигает своей максимальной емкости, он увеличивает свою собственную емкость в 2 раза по сравнению с текущей емкостью плюс 2, всякий раз, когда StringBuilder достигает своей максимальной емкости, он должен создавать новый массив символов, а затем заменять старый символ. Содержимое массива копируется в новый массив символов — это очень затратная операция. Только представьте, если можно подсчитать, что в массиве символов будет храниться около 5000 символов без указания длины, ближайшая степень двойки к 5000 равна 4096, а двойка, добавляемая за каждое расширение, не имеет значения, то:
(1) На основе 4096 подать заявку на массив символов размером 8194, что эквивалентно подаче заявки на массив символов размером 12290. Если вы можете указать массив символов размером 5000 в начале это сэкономит более чем вдвое больше места
(2) Скопируйте исходные 4096 символов в новый массив символов. Таким образом, пространство памяти тратится впустую, а эффективность выполнения кода снижается. Поэтому нет ничего плохого в том, чтобы установить разумную емкость инициализации для базовых коллекций и классов инструментов, реализованных с помощью массивов, что даст немедленные результаты. Однако обратите внимание, что для коллекций, реализованных в виде массивов + связанных списков, таких как HashMap, не устанавливайте начальный размер равным вашему предполагаемому размеру, потому что возможность подключения только одного объекта к таблице почти равна нулю. Начальный размер рекомендуется устанавливать равным степени 2. Если можно оценить, что имеется 2000 элементов, его можно установить как новый HashMap(128) или новый HashMap(256).
10. При копировании больших объемов данных используйте команду System.arraycopy()
11. Умножение и деление используют операции сдвига
Например:
for (val = 0; val < 100000; val += 5)
{
a = val * 8;
b = val / 2;
}
Работа сдвига может значительно улучшить производительность, поскольку в нижней части компьютера рабочее положение является наиболее удобным, самым быстрым, его предложено поправками следующим образом:
for (val = 0; val < 100000; val += 5)
{
a = val << 3;
b = val >> 1;
}
Хотя операция сдвига выполняется быстро, она может сделать код менее понятным, поэтому лучше добавить соответствующие комментарии.
12, внутренняя цикла не продолжает создавать ссылку на объект
Например:
for (int i = 1; i <= count; i++)
{
Object obj = new Object();
}
Такой подход приведет к тому, что в памяти будет существовать количество ссылок на объекты объектов count.
Object obj = null;for (int i = 0; i <= count; i++)
{
obj = new Object();
}
В этом случае в памяти имеется только одна ссылка на объект Object, и каждая новая Когда используется Object(), ссылка на объект Object указывает на разные объекты, но в памяти находится только одна копия, что значительно экономит место в памяти.
13. Исходя из соображений эффективности и проверки типов, массив следует использовать как можно чаще, а ArrayList следует использовать только тогда, когда невозможно определить размер массива.
14. Постарайтесь использовать Hashmap, ArrayList, StringBuilder, если не рекомендуется использовать Hashtable, Vector, StringBuffer, не рекомендует использовать Hashtable, Vector, StringBuffer, последние три из-за механизма синхронизации, что приводит к накладным расходам.
15. Не объявляйте массивы как public static final
Поскольку это бессмысленно, он просто определяет ссылку как static final, содержимое массива по-прежнему может быть изменено по желанию, а объявление массива как общедоступного является дырой в безопасности, а это означает, что массив может быть изменен внешними классами.
16. Старайтесь использовать синглтоны там, где это уместно
Использование синглтона может уменьшить нагрузку на загрузку, сократить время загрузки и повысить эффективность загрузки, но не все места подходят для синглтона.Короче говоря, синглтоны в основном подходят для следующих трех аспектов:
(1) Контролируйте использование ресурсов и контролируйте одновременный доступ к ресурсам посредством синхронизации потоков.
(2) Контролируйте создание экземпляров для достижения цели экономии ресурсов.
(3) управлять общими данными без прямой связи, чтобы обеспечить несвязанную связь между несколькими процессами или потоками.
17. Старайтесь избегать произвольного использования статических переменных
Вы должны знать, что когда на объект ссылается переменная, определенная как статическая, то gc обычно не восстанавливает память кучи, занимаемую объектом, например:
/**
* Java学习交流QQ群:956011797 我们一起学Java!
*/
public class A
{
private static B b = new B();
}
В настоящее время жизненный цикл статической переменной b такой же, как у класса A. Если класс A не выгружен, объект B, на который указывает ссылка B, будет находиться в памяти до тех пор, пока программа не завершится.
18. Своевременно очищайте сеансы, которые больше не нужны
Для очистки сеансов, которые больше не активны, многие серверы приложений имеют время ожидания сеанса по умолчанию, обычно 30 минут. Когда серверу приложений необходимо сохранить больше сессий, если памяти недостаточно, операционная система перенесет часть данных на диск, а также сервер приложений может сбросить некоторые неактивные сессии на диск по алгоритму MRU (самые последние использованные) , Это может даже вызвать исключение нехватки памяти. Если сеанс необходимо сбросить на диск, его необходимо сначала сериализовать, а в крупномасштабных кластерах сериализация объектов обходится дорого. Поэтому, когда сеанс больше не нужен, следует вовремя вызвать метод invalidate() класса HttpSession, чтобы очистить сеанс.
19. Коллекции, реализующие интерфейс RandomAccess, такие как ArrayList, должны проходиться с использованием наиболее распространенного цикла for вместо цикла foreach.
Это JDK рекомендуется пользователю. JDK API для интерпретации интерфейса RandomAccess: для достижения интерфейса RandomAccess используется для указания того, что они поддерживают быстрый случайный доступ, основная цель этого интерфейса состоит в том, чтобы разрешить общие алгоритмы изменять их поведение, чтобы применить его к случайным или последовательным спискам доступа может обеспечить хорошую производительность. Практический опыт показывает экземпляр класса интерфейса RandomAccess, если случайного доступа с использованием нормальной эффективности цикла будет выше, чем для использования Foreach Coop; наоборот, если последовательный доступ, использование более высокой эффективности будет итератор. Код, аналогичный следующему, можно определить:
if (list instanceof RandomAccess)
{ for (int i = 0; i < list.size(); i++){}
}else{
Iterator<?> iterator = list.iterable(); while (iterator.hasNext()){iterator.next()}
}
Базовым принципом реализации цикла foreach является итератор Iterator, см. Синтаксис Java Sugar 1: Параметры переменной длины и принцип цикла Foreach. Таким образом, вторая половина предложения «И наоборот, если к нему осуществляется последовательный доступ, будет более эффективно использовать итератор» означает, что те экземпляры класса, к которым осуществляется последовательный доступ, используют цикл foreach для обхода.
20. Используйте синхронизированные блоки кода вместо синхронизированных методов
Это было ясно изложено в статье "синхронизированный блок метода блокировки" в многопоточном модуле. Если вы не можете определить, что весь метод должен быть синхронизирован, попробуйте использовать синхронизированные блоки кода, чтобы избежать кода, который не нужно синхронизировать. Он также синхронизирован, что влияет на эффективность выполнения кода.
21. Объявите константы как static final и назовите их в верхнем регистре.
Это помещает это содержимое в пул констант во время компиляции и позволяет избежать вычисления значения сгенерированной константы во время выполнения. Кроме того, имена констант в верхнем регистре также могут облегчить различие между константами и переменными.
Говоря об этом, я хотел бы порекомендоватьУчебная группа по обмену архитектурой Java: 956011797,Нажмите, чтобы присоединиться сейчасОн бесплатно поделится несколькими видеороликами, записанными старшими архитекторами: анализ исходного кода Spring, MyBatis, Netty, принципы высокой параллелизма, высокой производительности, распределенной и микросервисной архитектуры, а также оптимизация производительности JVM, которые стали обязательной системой знаний. для архитекторов. Вы также можете получить бесплатные учебные ресурсы, а также опыт собеседований и вопросы интервью старших.Я считаю, что для друзей кода, которые уже работали и столкнулись с техническими узкими местами, в этой группе найдется нужный вам контент.
22. Не создавайте неиспользуемые объекты, не импортируйте неиспользуемые классы
Это бессмысленно, если в коде есть «Значение локальной переменной i не используется», «Импорт java.util никогда не используется», то удалите этот бесполезный контент.
23. Избегайте использования отражения во время выполнения программы
О см. Отражение. Рефлексия — очень мощная функция, предоставляемая пользователям Java.Мощные функции часто означают неэффективность. Не рекомендуется использовать механизм отражения, особенно метод вызова Метода, во время работы программы.Если это действительно необходимо, предлагаемый подход заключается в использовании экземпляра отражения для тех классов, которые необходимо загрузить через отражение. при запуске проекта Создайте объект и поместите его в память — пользователя заботит только получение максимальной скорости отклика при взаимодействии с пиром, и ему все равно, сколько времени потребуется для запуска пира проекта.
24. Используйте пул соединений с базой данных и пул потоков
Оба эти пула используются для повторного использования объектов: первый избегает частого открытия и закрытия соединений, а второй — частого создания и уничтожения потоков.
25. Используйте буферизованные входные и выходные потоки для операций ввода-вывода.
Буферизованные входные и выходные потоки, а именно BufferedReader, BufferedWriter, BufferedInputStream, BufferedOutputStream, могут значительно повысить эффективность ввода-вывода.
26. ArrayList используется в сценариях с более последовательной вставкой и случайным доступом, а LinkedList используется в сценариях с большим количеством удалений элементов и промежуточной вставкой.
Это понимаю принцип арарилиста и ссылками.
27, не позволяйте публичному процессу иметь слишком много параметров
Публичные методы — это методы, предоставляемые внешнему миру.Если вы зададите этим методам слишком много параметров, есть два основных недостатка:
1. Нарушает идею объектно-ориентированного программирования.Java подчеркивает, что все является объектом.Слишком много формальных параметров не вписывается в идею объектно-ориентированного программирования.
2. Слишком большое количество параметров неизбежно приведет к увеличению вероятности ошибки вызовов методов
Что касается количества «слишком много», скажем, 3 или 4. Например, мы используем JDBC для написания метода insertStudentInfo.В таблицу Student нужно вставить 10 полей информации о студентах.Эти 10 параметров могут быть инкапсулированы в классе сущностей в качестве формальных параметров метода вставки.
28. Когда строковые переменные и строковые константы равны, напишите строковые константы впереди
Это относительно распространенный трюк, если у вас есть следующий код:
String str = "123";
if (str.equals("123")) {
...
}
Предлагается изменить на:
String str = "123";
if ("123".equals(str))
{
...
}
Это в основном для того, чтобы избежать исключений нулевого указателя.
29. Пожалуйста, знайте, что нет никакой разницы между if (i == 1) и if (1 == i) в java, но из привычек чтения рекомендуется использовать первый
Обычно кто-то спрашивает, есть ли разница между "if (i == 1)" и "if (1== i)", которые начинаются с C/C++.
В C/C++ устанавливается условие оценки "if (i == 1)", основанное на 0 и не-0, 0 означает ложь, не-0 означает истину, если есть такой фрагмент кода:
int i = 2;
if (i == 1)
{
...
}else{
...
}
C/C++ считает, что "i==1" не выполняется, поэтому оно представлено 0, то есть ложно. но если:
int i = 2;if (i = 1) { ... }else{ ... }
Если программист случайно напишет «if (i == 1)» как «if (i = 1)», возникнет проблема. Присвойте i значение 1 внутри оператора if, и если определит, что содержимое внутри не равно 0, возвращаемое значение равно true, но ясно, что i равно 2, а значение сравнения равно 1, что должно вернуть false. Такая ситуация с большой долей вероятности может возникнуть при разработке C/C++ и приведет к некоторым непонятным ошибкам, поэтому во избежание некорректных операций присваивания в операторе if рекомендуется записывать оператор if в виде:
int i = 2;if (1 == i) { ... }else{ ... }
Таким образом, даже если разработчик случайно напишет «1 = i», компилятор C/C++ может немедленно это проверить, потому что мы можем присвоить i переменной как 1, но мы не можем присвоить 1 константе как i.
Однако в Java синтаксис C/C++ «if (i = 1)» невозможен, потому что после написания этого синтаксиса Java будет компилироваться с ошибкой «Несоответствие типов: невозможно преобразовать из int в boolean». Однако, несмотря на отсутствие семантической разницы между выражениями «if (i == 1)» и «if (1 == i)» в Java, по привычке чтения лучше использовать первое.
30. Не используйте метод toString() для массивов
Взгляните на то, что напечатано с помощью toString() в массиве:
public static void main(String[] args)
{ int[] is = new int[]{1, 2, 3};
System.out.println(is.toString());
}
оказаться:
[I@18a992f
Первоначальное намерение состоит в том, чтобы распечатать содержимое массива, но это может вызвать исключение нулевого указателя, поскольку ссылка на массив имеет значение null. Однако, хотя toString() не имеет смысла для массивов, toString() для коллекций может распечатать содержимое коллекции, поскольку родительский класс коллекций AbstractCollections переопределяет метод toString() класса Object.
31. Не преобразовывайте базовые типы данных, которые находятся вне допустимого диапазона.
Это не даст желаемых результатов:
public static void main(String[] args)
{
long l = 12345678901234L;
int i = (int)l;
System.out.println(i);
}
Мы могли бы ожидать некоторых из этих битов, но результат таков:
1942892530
объяснять. В Java long составляет 8 байт и 64 бита, поэтому представление 12345678901234 на компьютере должно быть таким:
0000 0000 0000 0000 0000 1011 0011 1010 0111 0011 1100 1110 0010 1111 1111 0010
Данные типа int составляют 4 байта по 32 бита, а первые 32 бита приведенной выше строки двоичных данных берутся из младшего разряда:
0111 0011 1100 1110 0010 1111 1111 0010
Эта строка представлена в виде десятичного числа 1942892530 в двоичном формате, так что это то, что мы выводим на консоль выше. Из этого примера можно сделать два вывода:
1. Целочисленный тип данных по умолчанию — int, long l = 12345678901234L, это число выходит за пределы диапазона int, поэтому в конце стоит буква L, указывающая, что это длинное число. Кстати, тип с плавающей запятой по умолчанию — double, поэтому при определении float пишите ""float f = 3.5f"
2. Далее напишите "int ii = l+i;" сообщит об ошибке, т.к. long + int является длинной и не может быть присвоена int
32. Данные, не используемые в классе общедоступной коллекции, должны быть своевременно удалены.
Если класс коллекции является общедоступным (то есть не является свойством метода), то элементы в коллекции не освобождаются автоматически, поскольку на них всегда есть ссылки. Поэтому, если некоторые данные в общедоступной коллекции не используются и не удаляются, общедоступная коллекция будет продолжать расти, что сделает систему уязвимой для утечек памяти.
33. Преобразование базового типа данных в строку, базовый тип данных.toString() — самый быстрый способ, String.valueOf(data) — второй, data+"" — самый медленный
Есть три способа преобразовать базовый тип данных в общий.У меня есть данные целочисленного типа i, и я могу использовать три способа: i.toString(), String.valueOf(i), i+"", насколько эффективны три способа см. Тест:
public static void main(String[] args)
{
int loopTime = 50000;
Integer i = 0; long startTime = System.currentTimeMillis(); for (int j = 0; j < loopTime; j++)
{
String str = String.valueOf(i);
}
System.out.println("String.valueOf():" + (System.currentTimeMillis() - startTime) + "ms");
startTime = System.currentTimeMillis(); for (int j = 0; j < loopTime; j++)
{
String str = i.toString();
}
System.out.println("Integer.toString():" + (System.currentTimeMillis() - startTime) + "ms");
startTime = System.currentTimeMillis(); for (int j = 0; j < loopTime; j++)
{
String str = i + "";
}
System.out.println("i + \"\":" + (System.currentTimeMillis() - startTime) + "ms");
}
Текущий результат:
String.valueOf():11ms Integer.toString():5ms i + "":25ms
Поэтому, когда в будущем вы столкнетесь с преобразованием базового типа данных в String, отдавайте приоритет использованию метода toString(). А почему, все просто:
1. Метод String.valueOf() вызывает метод Integer.toString() в нижней части, но перед вызовом он выносит краткое суждение.
2. О методе Integer.toString() нельзя говорить, он вызывается напрямую
3. Нижний слой i + "" реализуется StringBuilder. Сначала используйте метод append для объединения, а затем используйте метод toString() для получения строки.
Сравнивая три, очевидно, что 2 — самый быстрый, 1 — второй, а 3 — самый медленный.
34. Используйте наиболее эффективный способ перемещения по карте.
Есть много способов обойти карту.Обычно в сценарии нам нужно пройти по ключу и значению на карте.Рекомендуемый и наиболее эффективный способ:
public static void main(String[] args)
{
HashMap<String, String> hm = new HashMap<String, String>();
hm.put("111", "222");
Set<Map.Entry<String, String>> entrySet = hm.entrySet();
Iterator<Map.Entry<String, String>> iter = entrySet.iterator(); while (iter.hasNext())
{
Map.Entry<String, String> entry = iter.next();
System.out.println(entry.getKey() + "\t" + entry.getValue());
}
}
Если вы просто хотите просмотреть значение ключа этой карты, было бы более уместно использовать «Set keySet = hm.keySet();»
35. Рекомендуется использовать close() ресурсов отдельно
Значение, например, у меня есть этот фрагмент кода:
try{
XXX.close();
YYY.close();
}catch (Exception e)
{
...
}
Предлагается изменить на:
try{ XXX.close(); }catch (Exception e) { ... }try{ YYY.close(); }catch (Exception e) { ... }
Хотя это немного хлопотно, но позволяет избежать утечки ресурсов. Мы думаем, что если нет измененного кода, если XXX.close() выкинет исключение, то оно войдет в блок кат, YYY.close() не будет выполняться, а ресурс YYY не будет переработан. такого кода может привести к утечке дескриптора ресурса. После изменения на следующее гарантируется, что XXX и YYY будут закрыты несмотря ни на что.