как кеширование хэш-кода работает в Java, как это было предложено Джошуа Блохом в эффективной Java?

У меня есть следующий фрагмент кода из эффективного java Джошуа Блоха (пункт 9, глава 3, страница 49)

Если класс является неизменяемым и стоимость вычисления хеш-кода значительна, вы можете рассмотреть возможность кэширования хэш-кода в объекте, а не пересчитывать его каждый раз, когда он запрашивается. Если вы считаете, что большинство объектов этого типа будут использоваться в качестве хэш-ключей, то вам следует вычислить хеш-код при создании экземпляра. В противном случае вы можете выбрать ленивую инициализацию при первом вызове hashCode (статья 71). Неясно, заслуживает ли наш класс PhoneNumber такого обращения, но просто чтобы показать вам, как это делается:

    // Lazily initialized, cached hashCode
    private volatile int hashCode;  // (See Item 71)
    @Override public int hashCode() {
        int result = hashCode;
        if (result == 0) {
            result = 17;
            result = 31 * result + areaCode;
            result = 31 * result + prefix;
            result = 31 * result + lineNumber;
            hashCode = result;
        }
        return result;
    }

мой вопрос в том, как здесь работает кеширование (запоминание хэш-кода). В самый первый раз вызывается метод hashCode(), нет hashCode, чтобы присвоить его результату. краткое объяснение того, как работает это кэширование, будет отличным. Спасибо


person brain storm    schedule 27.08.2013    source источник
comment
Кэширование означает сохранение рассчитанного вами значения, чтобы вы могли повторно использовать его без повторного вычисления. Вот и все.   -  person Sotirios Delimanolis    schedule 27.08.2013
comment
А? Кэш — это приватный volatile int hashCode. Когда хэш вычисляется, он сохраняется в кэше. Первоначально значение равно 0, как и все нелокальные числовые переменные.   -  person Kayaman    schedule 27.08.2013


Ответы (3)


Простой. Прочтите мои встроенные комментарии ниже...

private volatile int hashCode;
//You keep a member field on the class, which represents the cached hashCode value

   @Override public int hashCode() {
       int result = hashCode;
       //if result == 0, the hashCode has not been computed yet, so compute it
       if (result == 0) {
           result = 17;
           result = 31 * result + areaCode;
           result = 31 * result + prefix;
           result = 31 * result + lineNumber;
           //remember the value you computed in the hashCode member field
           hashCode = result;
       }
       // when you return result, you've either just come from the body of the above
       // if statement, in which case you JUST calculated the value -- or -- you've
       // skipped the if statement in which case you've calculated it in a prior
       // invocation of hashCode, and you're returning the cached value.
       return result;
   }
person Amir Afghani    schedule 27.08.2013
comment
Зачем использовать модификатор volatile ? Что-то связанное с кэшированием? Если да, пожалуйста, дайте краткое описание. Спасибо. - person Charan; 09.02.2016
comment
@Charan в этом нет необходимости (и если вы посмотрите на источник java.lang.String, поле hash не является volatile. Единственный недостаток того, что оно не является изменчивым, заключается в том, что потоки, работающие на разных процессорах, могут многократно пересчитывать хэш-код. Но поскольку строки неизменяемый в java, это не приведет к какой-либо несогласованности, просто возможному снижению производительности, что, я думаю, нормально, поскольку хэш читается намного чаще, чем вычисляется (и изменчивые чтения могут иметь значительные накладные расходы по сравнению с обычным чтением) . - person karlicoss; 16.04.2016

Переменная hashCode в переменной экземпляра, и она явно не инициализирована, поэтому Java инициализирует его как 0 (раздел 4.12.5 JLS). Сравнение result == 0 на самом деле является проверкой того, присвоен ли result предположительно ненулевой хеш-код. Если он еще не был назначен, он выполняет вычисление, в противном случае он просто возвращает ранее вычисленный хэш-код.

person rgettman    schedule 27.08.2013

Если бы вы действительно хотели, чтобы это работало правильно, вы бы поместили другую изменчивую логическую переменную с именем isHashInvalid. Каждый сеттер, включающий значения, доступные в вашей хеш-функции, будет устанавливать эту переменную. Тогда это становится (теперь не нужно проверять «0»):

private volatile int isHashInvalid=TRUE;
private volatile int hashCode; //Automatically zero but it doesn't matter

//You keep a member field on the class, which represents the cached hashCode value
@Override public int hashCode() {
    int result = hashCode;
    if (isHashInvalid) {
       result = 17;
       result = 31 * result + areaCode;
       result = 31 * result + prefix;
       result = 31 * result + lineNumber;
       //remember the value you computed in the hashCode member field
       hashCode = result;
       isHashInvalid=FALSE;
    }
    // when you return result, you've either just come from the body of the above
    // if statement, in which case you JUST calculated the value -- or -- you've
    // skipped the if statement in which case you've calculated it in a prior
    // invocation of hashCode, and you're returning the cached value.
    return result;
}
person Dennis    schedule 29.09.2013
comment
Я не уверен, что добавление дополнительного целого числа стоит затрат. Единственная проблема с использованием нуля заключается в том, что часовой был бы, если бы hashCode мог вернуть ноль, и эту проблему можно было бы решить, просто сказав после вычисления result что-то вроде if (result == 0) result = 8675309 + areacode;. - person supercat; 12.11.2013
comment
Это либо 64-битное число, либо 32-битное число. Количество раз, когда оно фактически будет равно нулю, НИЗКО. Таким образом, для этого бесконечно малого количества элементов хэш-код будет рассчитываться каждый раз. Не ахти какое дело. - person Dennis; 13.11.2013
comment
Если я не ошибаюсь, вы используете isHashInvalid, чтобы позволить нулю быть допустимым значением хеш-функции, не требуя его повторного хеширования. Моя точка зрения заключалась в том, что если кто-то беспокоится о риске повторного хеширования некоторых объектов каждый раз (что для объектов произвольного размера должно быть, хотя, возможно, не объекты фиксированного размера, как в этом примере) , для защиты от этого не нужно использовать дополнительный флаг. - person supercat; 13.11.2013
comment
Даже если кто-то хочет разрешить инкрементное хеширование (например, чтобы добавить данные в список, для которого уже был вычислен хеш, просто нужно будет вычислить хэш новых элементов), использование 31-битного хэша кажется лучше, чем использование дополнительного флага ( если хэш хороший, ложные срабатывания должны быть редкими даже без 32-го бита, а если он плохой, этот 32-й бит вряд ли поможет) - person supercat; 13.11.2013