Через десять років і все змінилося. Я, чесно кажучи, не можу в це повірити (але виродк у мене надзвичайно щасливий).
Як ви вже зазначали, є шанси, де є деякі String::hashCode
для деяких рядків, zero
і це не було кешовано (дійдемо до цього). Багато людей сперечалися (включаючи ці запитання та відповіді), чому не було додано поле java.lang.String
, щось на зразок: hashAlreadyComputed
і просто використовувати це. Проблема очевидна: зайвий простір для кожного окремого рядка String. Існує, однак, причина, java-9
введена compact String
s, з того простого факту, що багато тестів показали, що це більшість (надмірно) використовуваний клас у більшості програм. Додавання більше місця? Рішення було: ні. Тим більше, що якнайменшим можливим додаванням було б 1 byte
, ні 1 bit
(для 32 bit JMV
s, додатковий простір був би8 bytes
: 1 для прапора, 7 для вирівнювання).
Так, Compact String
s прийшов в java-9
, і якщо ви подивіться уважно (або догляд) , вони зробили додати поле в java.lang.String
: coder
. Хіба я просто не сперечався проти цього? Це не так просто. Здається, важливість компактних рядків переважала аргумент "зайвого простору". Важливо також сказати, що додатковий простір має значення 32 bits VM
лише (оскільки не було прогалини у вирівнюванні). На відміну від цього, в jdk-8
макеті java.lang.String
є:
java.lang.String object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 12 (object header) N/A
12 4 char[] String.value N/A
16 4 int String.hash N/A
20 4 (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
Тут же зауважте важливу річ:
Space losses : ... 4 bytes total.
Оскільки кожен java-об'єкт вирівняний (на скільки залежить від JVM та деяких запускових прапорів, як, UseCompressedOops
наприклад), у String
нас є прогалина 4 bytes
, що не використовується. Отже, додаючи coder
, це просто займало, 1 byte
не додаючи додаткового місця. Таким чином, після Compact String
додавання s макет змінився:
java.lang.String object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 12 (object header) N/A
12 4 byte[] String.value N/A
16 4 int String.hash N/A
20 1 byte String.coder N/A
21 3 (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
coder
їсть 1 byte
і розрив скоротився 3 bytes
. Отже, "шкода" була вже зроблена в Росії jdk-9
. Бо 32 bits JVM
було збільшення з 8 bytes : 1 coder + 7 gap
і за 64 bit JVM
- не було збільшення,coder
займало деякий простір із розриву.
І зараз jdk-13
вони вирішили це використати gap
, оскільки воно все одно існує. Дозвольте мені лише нагадати вам, що ймовірність мати рядок з нульовим хеш-кодом становить 1 з 4 мільярдів; все ще є люди, які кажуть: так що? давайте це виправимо! Voilá: jdk-13
макет java.lang.String
:
java.lang.String object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 12 (object header) N/A
12 4 byte[] String.value N/A
16 4 int String.hash N/A
20 1 byte String.coder N/A
21 1 boolean String.hashIsZero N/A
22 2 (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 2 bytes external = 2 bytes total
І ось це: boolean String.hashIsZero
. І ось це в кодовій основі:
public int hashCode() {
int h = hash;
if (h == 0 && !hashIsZero) {
h = isLatin1() ? StringLatin1.hashCode(value)
: StringUTF16.hashCode(value);
if (h == 0) {
hashIsZero = true;
} else {
hash = h;
}
}
return h;
}
Чекай! h == 0
і hashIsZero
поле? Чи не слід це називати приблизно так hashAlreadyComputed
:? Чому реалізація не є щось на зразок:
@Override
public int hashCode(){
if(!hashCodeComputed){
hash = 42;
hashCodeComputed = true;
}
return hash;
}
Навіть якщо я прочитав коментар під вихідним кодом:
Це мало сенс лише після того, як я прочитав це . Швидше хитро, але це пишеться за раз, набагато більше деталей у дискусії вище.