Уникальный идентификатор для объекта Java

Я индексирую Java-объекты в Elasticsearch. Ниже приведена структура класса:

public Class Document{
    private String name;
    private double value;
    private Date date;
    private Map<String, String> attributes;
    //getters and setters
}

Прежде чем индексировать какой-либо объект, я хочу вычислить / получить уникальный идентификатор для объекта, который должен основываться на значениях этих членов. Если я создаю другой объект с такими же значениями для имени, даты, значения и атрибутов (т. Е. Если число и значения пар ключ-значение совпадают), то идентификаторы также должны быть одинаковыми.

В настоящее время я использую Objects.hash(Object... objects) вычислить hashCode и установить этот hashCode как id. Вроде нормально работает. Он возвращает одинаковое целое число для объектов, имеющих одинаковые значения для этих атрибутов. Однако, учитывая количество документов и диапазон значений int в java, хэш-код может / не может быть одинаковым (что приведет к дублированию документов).

Какие-нибудь альтернативные решения для этого? Можем ли мы создать буквенно-цифровую строку (или что-то) в зависимости от этих значений?

Заранее спасибо.

4 ответа

Вы не сможете полностью избежать коллизий, если не используете сам объект в качестве ключа... если вы хотите это сделать, вы можете сериализовать свои значения в последовательность байтов, т.е. 8 байтов для double 8 для date (потому что внутреннее представление longи произвольное количество байтов в зависимости от длины вашего name...

Самое разумное, что нужно сделать, это использовать эти значения для вычисления hashCode, а затем, когда происходит коллизия, сравнивать каждый член один за другим для обеспечения равенства. Это как ява Hashtable работает.

Если вы хотите пойти дальше и создать свой "безусловно уникальный идентификатор", хотя...

byte[] defoUnique = new byte[24 + name.size()];
byte[] dateBytes = Long.toByteArray(date.getTime());
for (int i = 0 ; i < 8 ; i++) defoUnique[i] = dateBytes[i];
byte[] valueBytes = Long.toByteArray(Double.doubleToLongBits(value));
for (int i = 0 ; i < 8 ; i++) defoUnique[i+8] = valueBytes[i];
byte[] nameBytes = name.getBytes();
for (int i = 0 ; i < nameBytes.length ; i++) defoUnique[i+16] = nameBytes[i];

/* Make byte sequence into alphanumeric string */
String identifierString = Base64.getEncoder().encodeToString(defoUnique);

Вы должны переопределить equals() И hashcode(). (Это распространенная ошибка - не перекрывать оба вместе).

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

ПРИМЕР:

    // from http://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/builder/HashCodeBuilder.html
     public class Person {
       String name;
       int age;
       boolean smoker;
       int id;  // this is your bit

       public int hashCode() {
         // you pick a hard-coded, randomly chosen, non-zero, odd number
         // ideally different for each class
         return new HashCodeBuilder(17, 37).
           append(name).
           append(age).
           append(smoker).
           toHashCode();
       }
     }

  public boolean equals(Object obj) {
  // the next 3 ifs are a 'short' circuit'
       if (obj == null) { return false; }
       if (obj == this) { return true; }
       if (obj.getClass() != getClass()) {
         return false;
       }

       // the meat of it
       MyClass rhs = (MyClass) obj;

       boolean sameClass = new EqualsBuilder()
                     .appendSuper(super.equals(obj))
                     .append(field1, rhs.field1)
                     .append(field2, rhs.field2)
                     .append(field3, rhs.field3)
                     .isEquals();

       //  here set/update your id
           if (sameClass){
                 this.id = rhs.id
           }

           return sameClass 
          }

В итоге получилось что-то вроде этого:

/**
     * Sets the id of document by calculating hash for individual elements
     */
    public void calculateHash(){
        ByteBuffer byteBuffer = ByteBuffer.allocate(16);
        byteBuffer.putInt(Objects.hashCode(name));
        byteBuffer.putInt(Objects.hashCode(date));
        byteBuffer.putInt(Objects.hashCode(value));
        byteBuffer.putInt(Objects.hashCode(attributes));
        super.setId(DigestUtils.sha512Hex(byteBuffer.array())); 
        byteBuffer.clear();
    }

Итак, в основном, я вычисляю хеши отдельных элементов, складываю их в байтовый массив и затем вычисляю хеш SHA-1. Итак, шансов на столкновение очень меньше. Даже если один хеш сталкивается, очень маловероятно, что столкнутся и другие хэши (так как это комбинация из 4 хешей). Я думаю, что вероятность столкновения составляет (1/4 миллиарда)^4, что более чем хорошо для меня:)Например, int hash может иметь 4 миллиарда значений, поэтому вероятность одного значения равна 1/(4 миллиарда), а вероятность наличие того же номера для других мест составляет 1/4b x 1/4b x 1/4b x 1/4b, т.е. (1/4b)^4, если я не ошибаюсь.

Не знаю, является ли это наиболее подходящим (или подходящим) способом. Но, похоже, сработало.

Спасибо

hashCode() дает 32 бита, если это может привести к коллизиям, используйте другой алгоритм хеширования.

java.security.MessageDigest предоставляет параметры в Java

Я бы порекомендовал для этого «MD5», который дает вам 128-битное число.

      "MD5" = 128 bits
"SHA1" = 160 bits
"SHA-256" = 256 bits
"SHA-384" = 384 bits
"SHA-512" = 512 bits

Вам не нужно беспокоиться о проблемах с криптографией с md5 или sha-1

Компрометируйте размер хэша с вероятностью столкновения.

Всегда есть риск столкновения, чтобы полностью избежать его стягивания элементов в цепочку. Представляйте числа в базе 16,32 или 64, чтобы сэкономить немного места.

Другие вопросы по тегам