Уникальный идентификатор для объекта 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, чтобы сэкономить немного места.