Все извлечено из исходного кода JDK
1 i++ vs i--
Строка 985 исходного кода String в методе equals
while (n--!= 0) {
if (v1[i] != v2[i])
return false;
i++;
}
Этот код используется для определения равенства строк, но есть странное место, где i--!=0 используется для оценки, разве мы обычно не используем i++? Зачем использовать я--? И количество циклов такое же. Причина в том, что после компиляции будет еще одна инструкция:
i-- Сама операция повлияет на CPSR (регистр текущего состояния программы).Общие признаки CPSR: N (результат отрицательный), Z (результат 0), C (перенос), O (переполнение). i > 0, об этом можно судить непосредственно по флагу Z. Операция i++ также влияет на CPSR (регистр текущего состояния программы), но влияет только на флаг O (с переполнением), что не помогает в оценке i
Проще говоря, на одну инструкцию меньше по сравнению с 0. Поэтому i-- перерабатывается, а high-end атмосфера полноценная.
2 переменные-члены против локальных переменных
Исходный код JDK будет использовать локальную переменную для принятия переменных-членов практически в любом методе, например
public int compareTo(String anotherString) {
int len1 = value.length;
int len2 = anotherString.value.length;
Так как локальные переменные инициализируются в стеке потока метода, а переменные-члены инициализируются в куче памяти, первый явно быстрее, поэтому мы стараемся избегать использования переменных-членов непосредственно в методе, а используем локальные переменные.
3 Намеренно загружать в регистры && выводить трудоемкие операции вне блокировки
В ConcurrentHashMap очень интересна работа сегмента блокировки.Это не прямая блокировка, а похожая на спин-блокировку.Он неоднократно пытается получить блокировку, и в процессе получения блокировки он будет проходить по связанному списку, чтобы данные сначала загружались в регистр и кешировались Во избежание удобства в процессе блокировки, в то же время операция генерации новых объектов также производится вне блокировки, чтобы избежать трудоемких операций в блокировке
final V put(K key, int hash, V value, boolean onlyIfAbsent) {
/** 在往该 segment 写入前,需要先获取该 segment 的独占锁
不是强制lock(),而是进行尝试 */
HashEntry<K,V> node = tryLock() ? null :
scanAndLockForPut(key, hash, value);
исходный код scanAndLockForPut()
private HashEntry<K,V> scanAndLockForPut(K key, int hash, V value) {
HashEntry<K,V> first = entryForHash(this, hash);
HashEntry<K,V> e = first;
HashEntry<K,V> node = null;
int retries = -1; // negative while locating node
// 循环获取锁
while (!tryLock()) {
HashEntry<K,V> f; // to recheck first below
if (retries < 0) {
if (e == null) {
if (node == null) // speculatively create node
//该hash位无值,新建对象,而不用再到put()方法的锁中再新建
node = new HashEntry<K,V>(hash, key, value, null);
retries = 0;
}
//该hash位置key也相同,退化成自旋锁
else if (key.equals(e.key))
retries = 0;
else
// 循环链表,cpu能自动将链表读入缓存
e = e.next;
}
// retries>0时就变成自旋锁。当然,如果重试次数如果超过 MAX_SCAN_RETRIES(单核1多核64),那么不抢了,进入到阻塞队列等待锁
// lock() 是阻塞方法,直到获取锁后返回,否则挂起
else if (++retries > MAX_SCAN_RETRIES) {
lock();
break;
}
else if ((retries & 1) == 0 &&
// 这个时候是有大问题了,那就是有新的元素进到了链表,成为了新的表头
// 所以这边的策略是,相当于重新走一遍这个 scanAndLockForPut 方法
(f = entryForHash(this, hash)) != first) {
e = first = f; // re-traverse if entry changed
retries = -1;
}
}
return node;
}
4 Чтобы судить о равенстве объектов, вы можете использовать == first
При оценке равенства объектов вы можете сначала использовать ==, потому что == напрямую сравнивает адреса, что очень быстро, а equals сравнивает большинство значений объектов, что относительно медленно, поэтому, если возможно, вы можете использовать a== b || a.equals(b) для сравнения объектов на равенство
5 О переходных
Transient используется для предотвращения сериализации, но внутренний массив в исходном коде HashMap определяется как переходный.
/**
* The table, resized as necessary. Length MUST Always be a power of two.
*/
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
Нельзя ли сериализовать в нем пары ключ-значение, нельзя ли передать с помощью hashmap в сети, но это не так.
Эффективная Java 2nd, Item75, Джошуа упомянул:
For example, consider the case of a hash table. The physical representation is a sequence of hash buckets containing key-value entries. The bucket that an entry resides in is a function of the hash code of its key, which is not, in general, guaranteed to be the same from JVM implementation to JVM implementation. In fact, it isn't even guaranteed to be the same from run to run. Therefore, accepting the default serialized form for a hash table would constitute a serious bug. Serializing and deserializing the hash table could yield an object whose invariants were seriously corrupt.
Как вы это понимаете? Взгляните на HashMap.get()/put(), чтобы узнать, что чтение и запись Map основаны на Object.hashcode(), чтобы определить, из какой корзины читать/записывать. нативный метод, возможно и в разных JVM они разные.
Например, запись хранится в HashMap, ключом является строка «STRING», в первой Java-программе хэш-код () «STRING» равен 1, и сохраняется первое ведро; во второй Java-программе Здесь хэш-код () «STRING» может быть равен 2, который хранится в сегменте № 2. Если используется сериализация по умолчанию (в таблице Entry [] не используются переходные процессы), то этот HashMap сериализуется из первой программы Java. при импорте второй java-программы ее распределение памяти такое же, что неверно.
Например, например, сохраните запись пары ключ-значение в HashMap, ключ = "Fang Laosi", в первой программе Java хэш-код () "Fang Laosi" равен 1, и он хранится в таблице [1] , ОК, теперь. Если он передается другой программе JVM, хэш-код () "Fang Laosi" может быть равен 2, поэтому перейдите к таблице [2], чтобы получить его, и значение результата не существует.
Текущие readObject и writeObject HashMap предназначены для вывода/ввода содержимого и повторного создания HashMap.
6 Не используйте char
char закодирован в utf-16 в Java и составляет 2 байта, а 2 байта не могут представлять все символы. 2-байтовое представление называется BMP, а другое объединяется как старший суррогат и младший суррогат, чтобы сформировать символ, представленный 4 байтами. Например, indexOf в исходном коде String:
//这里用int来接受一个char,方便判断范围
public int indexOf(int ch, int fromIndex) {
final int max = value.length;
if (fromIndex < 0) {
fromIndex = 0;
} else if (fromIndex >= max) {
// Note: fromIndex might be near -1>>>1.
return -1;
}
//在Bmp范围
if (ch < Character.MIN_SUPPLEMENTARY_CODE_POINT) {
// handle most cases here (ch is a BMP code point or a
// negative value (invalid code point))
final char[] value = this.value;
for (int i = fromIndex; i < max; i++) {
if (value[i] == ch) {
return i;
}
}
return -1;
} else {
//否则转到四个字节的判断方式
return indexOfSupplementary(ch, fromIndex);
}
}
Таким образом, символ Java может представлять только часть bmp символов в utf16. Для CJK (Единая идеограмма Китая, Японии и Кореи) часть расширенного набора символов не может быть представлена.
Например, на рисунке ниже, кроме части Ext-A, char не может быть представлен.
Кроме того, есть еще поговорка, что используется char, а пароль не String.String является константой (то есть ее нельзя изменить после создания).Она будет сохранена в пуле констант.Если есть другие процессы который может сбрасывать память этого процесса, то пароль будет изменен с помощью Поскольку постоянный пул сбрасывается и происходит утечка, а char[] может записывать другую информацию для изменения, даже если она сбрасывается, это снизит риск утечки паролей.
Но я лично думаю, что можно сбросить память.Это чар, который может помешать ей жить? Разве что String не перерабатывается в пуле констант, а читается напрямую из пула констант другими потоками, но боюсь это очень редко.
Один из моих курсов:«Учебник по основам Java: рукописный JDK»Все, пожалуйста, посмотрите :)