Чому String hashCode () не кешує 0?


75

Я помітив у вихідному коді Java 6 для String, що hashCode кешує лише значення, відмінні від 0. Різницю в продуктивності демонструє такий фрагмент:

public class Main{
   static void test(String s) {
      long start = System.currentTimeMillis();
      for (int i = 0; i < 10000000; i++) {
         s.hashCode();
      }
      System.out.format("Took %d ms.%n", System.currentTimeMillis() - start);
   }
   public static void main(String[] args) {
      String z = "Allocator redistricts; strict allocator redistricts strictly.";
      test(z);
      test(z.toUpperCase());
   }
}

Запуск цього на ideone.com дає такий результат:

Took 1470 ms.
Took 58 ms.

Тож мої запитання:

  • Чому String hashCode () не кешує 0?
  • Яка ймовірність того, що рядок Java хешує до 0?
  • Який найкращий спосіб уникнути показників продуктивності при повторному обчисленні хеш-значення кожного разу для рядків, хеш яких дорівнює 0?
  • Це найкращий спосіб кешування значень? (тобто кешувати всі, крім одного?)

Для вашої розваги кожен рядок тут є рядком, хеш якого дорівнює 0:

pollinating sandboxes
amusement & hemophilias
schoolworks = perversive
electrolysissweeteners.net
constitutionalunstableness.net
grinnerslaphappier.org
BLEACHINGFEMININELY.NET
WWW.BUMRACEGOERS.ORG
WWW.RACCOONPRUDENTIALS.NET
Microcomputers: the unredeemed lollipop...
Incentively, my dear, I don't tessellate a derangement.
A person who never yodelled an apology, never preened vocalizing transsexuals.

6
ЛОЛ! +1 за чудовий приклад тролінгу, виконаного за допомогою übergeek!
Mike Nakis

Відповіді:


57

Ви ні про що не турбуєтесь. Ось спосіб подумати над цим питанням.

Припустимо, у вас є програма, яка не робить нічого, крім як сидіти навколо хешування рядків протягом усього року. Скажімо, для цього потрібна тисяча рядків, усі в пам’яті, щоразу повторно викликає hashCode () на них, мільйон разів, потім отримує ще тисячу нових рядків і робить це знову.

І припустимо, що ймовірність того, що хеш-код рядка дорівнює нулю, насправді була набагато більшою, ніж 1/2 ^ 32. Я впевнений, що це дещо більше, ніж 1/2 ^ 32, але припустимо, це набагато гірше за це, наприклад, 1/2 ^ 16 (квадратний корінь! Тепер це набагато гірше!).

У цій ситуації ви можете отримати більше користі від інженерів Oracle, які покращують кешування кеш-кодів цих рядків, ніж будь-хто інший. Тож ви пишете їм і просите їх це виправити. І вони працюють зі своєю магією так, що коли s.hashCode () дорівнює нулю, він миттєво повертається (навіть у перший раз! 100% покращення!). І припустимо, що вони роблять це, не погіршуючи продуктивність у будь-якому іншому випадку.

Ура! Тепер ваш додаток ... давайте подивимось ... на 0,0015% швидше!

Те, що раніше займало цілий день, зараз займає лише 23 години, 57 хвилин і 48 секунд!

І пам’ятайте, ми створили сценарій, щоб дати всі можливі переваги сумнівам, часто до смішного ступеня.

Вам це здається вартим?

РЕДАГУВАТИ: з моменту розміщення цього повідомлення пару годин тому, я дозволив одному з моїх процесорів дико шукати фрази з двох слів з нульовими хеш-кодами. Поки це придумано: bequirtle zorillo, chronogrammic schtoff, contusive cloisterlike, creashaks organzine, барабанний валун, електроаналітичний тренувальний пристрій і, насамперед, неконструктивний. Це з приблизно 2 ^ 35 можливостей, тому за ідеального розподілу ми очікуємо побачити лише 8. Очевидно, що до того моменту, коли це буде зроблено, ми матимемо в кілька разів більше, але не дивно більше. Більш важливим є те, що я зараз придумав кілька цікавих назв груп / назв альбомів! Не чесне крадіжку!


2
Це дуже практичний аргумент. Однак з цікавості такий механізм кешування також поширений деінде? Тобто, якщо спроба кешувати всі значення потребує додаткового прапора, то найкраще практикувати лише одне значення, щоб не можна було кешувати?
полігенні мастила

2
Я впевнений, що використовував цей трюк раз-два. Звичайно, вимоги до класу String є надзвичайно надзвичайними порівняно з більшістю класів. Чудово доречне ім'я користувача btw :)
Кевін Буррілліон

20
Так, нещодавно я був досить одержимий String's hashCode (), про що свідчить моє ім'я користувача. Джошуа Блох у відео від Google Tech Talk від 23 липня 2007 р. Заявив, що знайшов "полігенмастильні речовини" серед (200 000) ^ 2 пар слів за 10 хвилин. Я скористався властивостями хеш-функції, щоб зробити це в O (N) всього за кілька секунд. Наприклад, такий рядок також має хеші до MIN_VALUE:"And so my fellow mismanagements: ask not what your newsdealer can sugarcoat for you -- ask what you can sugarcoat for your newsdealer."
полігенні

6
Якщо рядки надходять від користувачів, то ймовірність близька до 1. Ви знаєте, хтось спробує це.
Сурма

1
Думаю, це може коштувати полігенілюбрікантів, оскільки сервери займають більше часу, щоб відповідати на запити, що змушує їх перерахувати хеш :) Примітка для себе: вибирайте ім'я користувача розумно
jontejj

24

Він використовує 0 для позначення "Я ще не розробив хеш-код". Альтернативою було б використання окремого булевого прапора, який забирав би більше пам'яті. (Або, звичайно, не кешувати хеш-код.)

Я не очікую, що багато рядків хеш до 0; можливо, було б сенсом, щоб процедура хешування навмисно уникала 0 (наприклад, перекласти хеш 0 до 1 і кешувати це). Це збільшило б зіткнення, але уникнуло б повторного повторення. Зараз це робити вже пізно, оскільки алгоритм String hashCode явно задокументований.

Щодо того, чи є це загальною ідеєю взагалі: це, безумовно, ефективний механізм кешування, і, можливо, (див. Редагування) може бути навіть кращим із змінами, щоб уникнути перепрофілювання значень, які в кінцевому підсумку отримують хеш 0. Особисто мені було б цікаво побачити дані, які спонукали Sun вважати, що в першу чергу це варто було зробити - це займає додаткові 4 байти для кожного коли-небудь створеного рядка, однак часто або рідко він хеш, і єдина перевага для рядків, які хешуються більше одного разу .

РЕДАГУВАТИ: Як зазначає КевінБ в іншому коментарі, пропозиція "уникати 0" вище може мати чисту вартість, оскільки вона допомагає в дуже рідкісних випадках, але вимагає додаткового порівняння для кожного розрахунку хешу.


Я щойно додав тег найкращої практики та 4-е запитання, щоб зробити це більше питанням дизайну. Чи має бути так? Чи повинна спрацьовувати ненульова ймовірність збереження O (n) кожного разу, коли метод викликається (і він буде називатися достатньо, оскільки Strings та hashCode () є такими основними частинами Java), що виправдовує додатковий простір для зберігання O (1)? Або це насправді найкраща практика, як правило, просто кешувати всі значення, крім одного, а не мати прапор?
полігенмастильні речовини

1
@Stephen C: Це передбачає ідеально розподілений хеш. Я не знаю, чи так це для того, що використовується String.
Джон Скіт,

1
Msgstr "Я не очікую, що багато рядків хеш до 0". Ну, хіба що струни були навмисно обрані.
Том Хоутін - таклін

1
"Ну, хіба що рядки були навмисно вибрані." Ну "," мабуть, найпоширеніший рядок у світі Java (хто навіть ЗНАЄ, скільки рядків введено в "" і ніколи не змінювалося, правда?) Та "" .hashCode () дорівнює 0. Я не бачу багатьох випадків використання, скажімо, використання "" в якості ключа карти, але я впевнений, що це відбувається, тому це, мабуть, непропорційно дорого. Тим не менш, "" .hashCode () в основному просто виконує цикл від 0 до 0, тому я не думаю, що це буде точно повільно ... і навіть якщо це було, кому все одно (див. Відповідь Кевіна)
Кован

1
@Sergio: Так, це так. Наприклад, "aaaaaa".hashCode()повертає -1425372064.
Джон Скіт,

19

Я думаю, є щось важливе, чого бракує в інших відповідях на даний момент: нульове значення існує, щоб механізм кешування hashCode працював надійно в багатопотоковому середовищі.

Якщо у вас були дві змінні, такі як сам cachedHashCode та булеве значення isHashCodeCalculated, щоб вказати, чи було розраховано cachedHashCode, вам знадобиться синхронізація потоків, щоб речі працювали в багатопоточному середовищі. І синхронізація буде поганою для продуктивності, тим більше, що рядки дуже часто використовуються в декількох потоках.

Я розумію модель пам'яті Java трохи схематично, але ось приблизно те, що відбувається:

  1. Коли кілька потоків отримують доступ до змінної (наприклад, кешований hashCode), немає гарантії, що кожен потік побачить останнє значення. Якщо змінна починається з нуля, тоді A оновлює її (встановлює ненульове значення), тоді потік B читає її незабаром після цього, потік B все ще може бачити нульове значення.

  2. Існує ще одна проблема з доступом до спільних значень із декількох потоків (без синхронізації) - в кінцевому підсумку ви можете спробувати використати об’єкт, який лише частково ініціалізований (побудова об’єкта не є атомним процесом). Багатопотокове читання та запис 64-розрядних примітивів, таких як longs і double, також не обов'язково є атомними, тому, якщо два потоки намагаються прочитати і змінити значення long або double, один потік може в результаті побачити щось дивне і частково встановлене . Або все одно щось подібне. Існують подібні проблеми, якщо ви намагаєтесь використовувати дві змінні разом, наприклад cachedHashCode та isHashCodeCalculated - потік може легко з’явитися і побачити останню версію однієї із цих змінних, але стару версію іншої.

  3. Звичайним способом обійти ці багатопотокові проблеми є використання синхронізації. Наприклад, ви можете розмістити весь доступ до кешованого hashCode всередині синхронізованого блоку або скористатися ключовим словом volatile (хоча будьте обережні з цим, оскільки семантика трохи заплутана).

  4. Однак синхронізація гальмує ситуацію. Погана ідея для чогось на зразок рядка hashCode. Рядки дуже часто використовуються як ключі в HashMaps, тому вам потрібен метод hashCode, щоб добре працювати, в тому числі в багатопоточних середовищах.

  5. Примітиви Java, які мають 32 біти або менше, як int, є особливими. На відміну від, скажімо, довгого (64-розрядне значення), ви можете бути впевнені, що ніколи не прочитаєте частково ініціалізоване значення int (32 біти). Коли ви читаєте int без синхронізації, ви не можете бути впевнені, що отримаєте останнє встановлене значення, але можете бути впевнені, що отримане значення - це значення, яке явно встановлено в якийсь момент вашим потоком або інша нитка.

Механізм кешування hashCode у java.lang.String налаштований на опору на пункт 5 вище. Ви можете краще зрозуміти це, подивившись на джерело java.lang.String.hashCode (). В основному, якщо декілька потоків викликають hashCode одночасно, hashCode може закінчитися обчислюватися кілька разів (або якщо обчислене значення дорівнює нулю, або якщо кілька потоків викликають hashCode одночасно, і обидва бачать нульове кешоване значення), але ви можете бути впевнені, що hashCode () завжди поверне одне і те ж значення. Отже, він надійний, і він також є продуктивним (оскільки немає синхронізації, яка би виступала як вузьке місце в багатопоточних середовищах).

Як я вже сказав, моє розуміння моделі пам'яті Java трохи схематичне, але я впевнений, що суть вищевказаного я зрозуміла. Зрештою, це дуже розумна ідіома для кешування hashCode без накладних витрат на синхронізацію.


Вам не обов'язково потрібна синхронізація - як ви вже згадували, є речі, такі як нестабільні. Хоча вам дійсно потрібно бути обережними з мінливими, я думаю, можна з упевненістю сказати, що автори класу String, швидше за все, знатимуть, як ним правильно користуватися, або матимуть відповідних людей для консультацій. Я дійсно сприймаю вашу думку ... але я все ще не впевнений, що це взагалі варто кешувати, і вартість пам'яті все ще присутня для кожного рядка в системі :(
Джон Скіт,

1
Як я розумію, волатильність - це форма синхронізації, просто вона має менше накладних витрат, ніж синхронізоване ключове слово. Я знайшов це посилання cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html, яке частково пояснює ідіому, яка використовується в хеш- коді String. Мені це, швидше, подобається - я думаю, я почну використовувати його більше насправді :) Хоча я ціную вашу думку щодо пам'яті - це може бути проблемою для деяких речей. BTW String.intern () є причиною того, чому багатопотокова продуктивність важлива для рядків - вони можуть повторно використовуватися внутрішньо JVM.
МБ.

1
Це може бути вагомою причиною для того, щоб вважати нуль особливим, але це не є вагомою причиною для того, щоб мати хеш-функцію, яка повертає некешоване значення. Чи виникли б якісь труднощі із включенням у хеш-функцію щось на зразок if (computedHash != 0) return computedHash; else return «some other function»;:? Навіть якщо інша функція просто взяла значення ASCII першого символу в рядку, плюс 991 раз у порівнянні з останніми символами в рядку, і додала 1234567890, це не завадило б розподілу.
supercat

if (computedHash != 0) return computedHash; else return «some other function»;- це ефективно те, що є у hashCodeфункції, з обережністю, що стосується того, що відбувається, якщо її викликають кілька потоків. Ви можете поглянути на джерело. Багатопотоковість в сторону, це просто означає, що якщо обчислений хеш-код дорівнює нулю (що в будь-якому випадку дуже малоймовірно), хеш-код буде перераховуватися кожного разу, коли буде викликана функція.
МБ.

Я згоден з першим пунктом . @supercat це зайняло досить багато часу, але вони це виправили.
Євген

8

0 не кешоване, оскільки реалізація інтерпретує кешоване значення 0 як "кешоване значення, яке ще не ініціалізоване". Альтернативою було б використання a java.lang.Integer, де null означало, що значення ще не кешоване. Однак це означало б додаткові накладні витрати на зберігання.

Щодо ймовірності обчислення хеш-коду рядка як 0, я б сказав, що ймовірність досить низька і може трапитися в наступних випадках:

  • Рядок порожній (хоча повторне обчислення цього хеш-коду кожного разу ефективно O (1)).
  • Відбувається переповнення, при якому остаточний обчислений хеш-код дорівнює 0 ( e.g. Integer.MAX_VALUE + h(c1) + h(c2) + ... h(cn) == 0).
  • Рядок містить лише символ Unicode 0. Дуже малоймовірно, оскільки це контрольний символ без будь-якого значення, крім "світу паперової стрічки" (!):

З Вікіпедії :

Код 0 (кодова назва ASCII NUL) є особливим випадком. У паперовій стрічці це так, коли немає отворів. Зручно ставитися до цього як до символу заливки, не маючи значення інакше .


\ u0000 все ще живий, якщо ви взаємодієте з новим кодом нового файлу ("C: \\ CONFIG.SYS \ u0000ignored"). isFile () == true на моїй машині Windows. Це джерело всіляких проблем безпеки. Для більшості програм фільтруйте цього персонажа!
Thomas Jung

@Thomas Jung Якщо вам потрібно переглянути шлях до файлу, спершу його нормалізуйте (а символи білого списку, звичайно, не вносять у чорний список). Навіть це не допоможе вам проти символічних посилань.
Том Хоутін - таклін

1
Зверніть увагу, що якщо у вас є символи, що не є NUL, рядок повинен мати шість чи сім символів, перш ніж він може мати нульовий хеш-код.
Том Хоутін - таклін

6

Це виявляється хорошим питанням, пов’язаним із вразливістю системи безпеки .

"При хешуванні рядка Java також кешує хеш-значення в атрибуті хешу, але лише в тому випадку, якщо результат відрізняється від нуля. Таким чином, цільове значення нуль особливо цікаве для зловмисника, оскільки воно запобігає кешуванню та змушує повторний хеш."



2

Через десять років і все змінилося. Я, чесно кажучи, не можу в це повірити (але виродк у мене надзвичайно щасливий).

Як ви вже зазначали, є шанси, де є деякі String::hashCodeдля деяких рядків, zeroі це не було кешовано (дійдемо до цього). Багато людей сперечалися (включаючи ці запитання та відповіді), чому не було додано поле java.lang.String, щось на зразок: hashAlreadyComputedі просто використовувати це. Проблема очевидна: зайвий простір для кожного окремого рядка String. Існує, однак, причина, java-9 введена compact Strings, з того простого факту, що багато тестів показали, що це більшість (надмірно) використовуваний клас у більшості програм. Додавання більше місця? Рішення було: ні. Тим більше, що якнайменшим можливим додаванням було б 1 byte, ні 1 bit(для 32 bit JMVs, додатковий простір був би8 bytes : 1 для прапора, 7 для вирівнювання).

Так, Compact Strings прийшов в 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){
            // or any other sane computation
            hash = 42;
            hashCodeComputed = true;
        }

        return hash;
    }

Навіть якщо я прочитав коментар під вихідним кодом:

    // The hash or hashIsZero fields are subject to a benign data race,
    // making it crucial to ensure that any observable result of the
    // calculation in this method stays correct under any possible read of
    // these fields. Necessary restrictions to allow this to be correct
    // without explicit memory fences or similar concurrency primitives is
    // that we can ever only write to one of these two fields for a given
    // String instance, and that the computation is idempotent and derived
    // from immutable state

Це мало сенс лише після того, як я прочитав це . Швидше хитро, але це пишеться за раз, набагато більше деталей у дискусії вище.


0
  • Чому String hashCode () не кешує 0?

Нульове значення зарезервовано як значення "хеш-код не кешований".

  • Яка ймовірність того, що рядок Java хешує до 0?

Відповідно до Javadoc, формула хеш-коду рядка така:

s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]

з використанням intарифметики, де s[i]- i-й символ рядка та nдовжина рядка. (Хеш порожнього рядка визначається рівним нулю як особливий випадок.)

Моя інтуїція полягає в тому, що функція хеш-коду, як зазначено вище, забезпечує рівномірний розподіл значень хешу String по діапазону intзначень. Рівномірний розкид, який означав би, що ймовірність випадково сформованого хешування рядка до нуля становила 1 у 2 ^ 32.

  • Який найкращий спосіб уникнути показників продуктивності при повторному обчисленні хеш-значення кожного разу для рядків, хеш яких дорівнює 0?

Найкраща стратегія - ігнорувати проблему. Якщо ви неодноразово хешуєте одне і те ж значення рядка, у вашому алгоритмі є щось досить дивне.

  • Це найкращий спосіб кешування значень? (тобто кешувати всі, крім одного?)

Це компроміс між часом та часом. AFAIK, альтернативами є:

  • Додайте cachedпрапор до кожного об’єкта String, змушуючи кожну Java String займати зайве слово.

  • Використовуйте верхній біт hashелемента як кешований прапор. Таким чином ви можете кешувати всі хеш-значення, але у вас є лише вдвічі менше можливих значень хеш-рядків.

  • Не кешуйте хеш-коди на рядках взагалі.

Я вважаю, що дизайнери Java зробили правильний заклик до Strings, і я впевнений, що вони зробили широке профілювання, що підтверджує обгрунтованість їх рішення. Однак з цього не випливає, що це завжди буде найкращим способом боротьби з кешуванням.

(Зверніть увагу, що є два "загальних" значення рядка, які мають хеш до нуля; порожній рядок і рядок, що складається лише з символу NUL. Однак, вартість обчислення хеш-кодів для цих значень невелика порівняно з вартістю обчислення хеш-код для типового значення рядка.)


Я не вважаю, що 1 з 2 ^ 32 є правильним: для коротших рядків хеш-код буде в діапазоні: [0, Integer.MAX_VALUE], а для будь-яких рядків, достатньо довгих, щоб викликати переповнення, хеш-код буде в діапазоні: [ Ціле число.MIN_VALUE, Ціле число.MAX_VALUE]. Отже, для випадково сформованих рядків (і припускаючи рівномірно розподілений алгоритм хешування) розподіл не є повністю рівномірним; існує більше шансів на позитивний чи нульовий хеш-код, ніж на негативний.
Адамський

hashCodeАлгоритм викликає Целочисленное переповнення досить швидко, Адамський . Беручи кілька випадкових прикладів, здається, достатньо символів із 6 слів - але я думаю, що ваші міркування слушні, це призводить до перекосу в бік позитивних хеш-значень (які погіршуються із
збільшенням строки

Випадково сформовані рядки мають випадкові довжини, а також випадкові символи.
Stephen C

@Stephen: Випадкові довжини - це мій точний пункт: Для повністю рівномірного розподілу рядків випадкової довжини, що містять випадкові символи, буде трохи більше рядків, які мають хеш до> = 0, оскільки коротші рядки не спричиняють переповнення.
Адамський

Ви нехтували варіантом, який я вказав у своїй відповіді: додавши "if (hash == 0) hash = 1;" в кінці алгоритму. Таким чином ви не втратите половину нормальних хеш-значень, лише на одну менше.
Джон Скіт,

0

Ну, люди, він тримає 0, тому що якщо це нульова довжина, то в будь-якому випадку воно буде нульовим.

І це не займе багато часу, щоб зрозуміти, що len дорівнює нулю, і таким повинен бути хеш-код.

Отже, для вашого коду-reviewz! Ось вона у всій славі Java 8:

 public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

Як бачите, це завжди повертає швидкий нуль, якщо рядок порожній:

  if (h == 0 && value.length > 0) ...

0

Пропозиція "уникати 0" здається доречною рекомендувати як найкращу практику, оскільки вона допомагає справжній проблемі (серйозно несподіваному погіршенню продуктивності у конструктивних випадках, які можуть бути надані зловмисником) за мізерні витрати на операцію гілки перед записом. Залишилося деяке «несподіване погіршення продуктивності», яке можна здійснити, якщо єдине, що переходить у набір хешу, до спеціально скоригованого значення. Але це в гіршому випадку деградація вдвічі, а не необмежена.

Звичайно, реалізацію String неможливо змінити, але немає необхідності продовжувати проблему.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.