Fix hash code collisions (#605)
* fix test rounding, pass 10K station names * improved integer conversion, delayed string creation. * new algorithm hash, use ConcurrentHashMap * fix rounding test * added the length of the string in the hash initialization. * fix hash code collisions
This commit is contained in:
parent
6b5b68c772
commit
eea9c33858
@ -20,11 +20,12 @@ import java.io.File;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.RandomAccessFile;
|
import java.io.RandomAccessFile;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.TreeMap;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class CalculateAverage_jgrateron {
|
public class CalculateAverage_jgrateron {
|
||||||
@ -32,6 +33,8 @@ public class CalculateAverage_jgrateron {
|
|||||||
private static final int MAX_LENGTH_LINE = 255;
|
private static final int MAX_LENGTH_LINE = 255;
|
||||||
private static final int MAX_BUFFER = 1024 * 8;
|
private static final int MAX_BUFFER = 1024 * 8;
|
||||||
private static boolean DEBUG = false;
|
private static boolean DEBUG = false;
|
||||||
|
public static int DECENAS[] = { 0, 10, 20, 30, 40, 50, 60, 70, 80, 90 };
|
||||||
|
public static int CENTENAS[] = { 0, 100, 200, 300, 400, 500, 600, 700, 800, 900 };
|
||||||
|
|
||||||
public record Particion(long offset, long size) {
|
public record Particion(long offset, long size) {
|
||||||
}
|
}
|
||||||
@ -92,21 +95,25 @@ public class CalculateAverage_jgrateron {
|
|||||||
Locale.setDefault(Locale.US);
|
Locale.setDefault(Locale.US);
|
||||||
var startTime = System.nanoTime();
|
var startTime = System.nanoTime();
|
||||||
var archivo = new File(FILE);
|
var archivo = new File(FILE);
|
||||||
var totalMediciones = new TreeMap<String, Medicion>();
|
|
||||||
var tareas = new ArrayList<Thread>();
|
var tareas = new ArrayList<Thread>();
|
||||||
|
var totalMediciones = new HashMap<Index, Medicion>();
|
||||||
var particiones = dividirArchivo(archivo);
|
var particiones = dividirArchivo(archivo);
|
||||||
|
|
||||||
for (var p : particiones) {
|
for (var p : particiones) {
|
||||||
var hilo = Thread.ofVirtual().start(() -> {
|
var hilo = Thread.ofVirtual().start(() -> {
|
||||||
try (var miTarea = new MiTarea(archivo, p)) {
|
try (var miTarea = new MiTarea(archivo, p)) {
|
||||||
var mediciones = miTarea.calcularMediciones();
|
var mediciones = miTarea.calcularMediciones();
|
||||||
synchronized (totalMediciones) {
|
for (var entry : mediciones.entrySet()) {
|
||||||
for (var entry : mediciones.entrySet()) {
|
Medicion medicion;
|
||||||
var medicion = totalMediciones.get(entry.getKey());
|
synchronized (totalMediciones) {
|
||||||
|
medicion = totalMediciones.get(entry.getKey());
|
||||||
if (medicion == null) {
|
if (medicion == null) {
|
||||||
totalMediciones.put(entry.getKey(), entry.getValue());
|
totalMediciones.put(entry.getKey(), entry.getValue());
|
||||||
|
medicion = entry.getValue();
|
||||||
}
|
}
|
||||||
else {
|
}
|
||||||
|
synchronized (medicion) {
|
||||||
|
if (!medicion.equals(entry.getValue())) {
|
||||||
var otraMed = entry.getValue();
|
var otraMed = entry.getValue();
|
||||||
medicion.update(otraMed.count, otraMed.tempMin, otraMed.tempMax, otraMed.tempSum);
|
medicion.update(otraMed.count, otraMed.tempMin, otraMed.tempMax, otraMed.tempSum);
|
||||||
}
|
}
|
||||||
@ -119,12 +126,18 @@ public class CalculateAverage_jgrateron {
|
|||||||
});
|
});
|
||||||
tareas.add(hilo);
|
tareas.add(hilo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Comparator<Map.Entry<Index, Medicion>> comparar = (a, b) -> {
|
||||||
|
return a.getValue().getNombreEstacion().compareTo(b.getValue().getNombreEstacion());
|
||||||
|
};
|
||||||
|
|
||||||
for (var hilo : tareas) {
|
for (var hilo : tareas) {
|
||||||
hilo.join();
|
hilo.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = totalMediciones.entrySet().stream()//
|
var result = totalMediciones.entrySet().stream()//
|
||||||
.map(e -> e.getKey() + "=" + e.getValue().toString())//
|
.sorted(comparar)
|
||||||
|
.map(e -> e.getValue().toString())//
|
||||||
.collect(Collectors.joining(", "));
|
.collect(Collectors.joining(", "));
|
||||||
|
|
||||||
System.out.println("{" + result + "}");
|
System.out.println("{" + result + "}");
|
||||||
@ -138,17 +151,38 @@ public class CalculateAverage_jgrateron {
|
|||||||
*/
|
*/
|
||||||
static class Index {
|
static class Index {
|
||||||
private int hash;
|
private int hash;
|
||||||
|
private byte[] data;
|
||||||
|
private int fromIndex;
|
||||||
|
private int length;
|
||||||
|
|
||||||
public Index() {
|
public Index() {
|
||||||
this.hash = 0;
|
this.hash = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Index(int hash) {
|
public Index(byte data[], int fromIndex, int length) {
|
||||||
this.hash = hash;
|
this.data = data;
|
||||||
|
this.fromIndex = fromIndex;
|
||||||
|
this.length = length;
|
||||||
|
this.hash = calcHashCode(length, data, fromIndex, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setHash(int hash) {
|
public void setData(byte data[], int fromIndex, int length) {
|
||||||
this.hash = hash;
|
this.data = data;
|
||||||
|
this.fromIndex = fromIndex;
|
||||||
|
this.length = length;
|
||||||
|
this.hash = calcHashCode(length, data, fromIndex, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Calcula el hash de cada estacion,
|
||||||
|
* variation of Daniel J Bernstein's algorithm
|
||||||
|
*/
|
||||||
|
private int calcHashCode(int result, byte[] a, int fromIndex, int length) {
|
||||||
|
int end = fromIndex + length;
|
||||||
|
for (int i = fromIndex; i < end; i++) {
|
||||||
|
result = ((result << 5) + result) ^ a[i];
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -162,7 +196,8 @@ public class CalculateAverage_jgrateron {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
var otro = (Index) obj;
|
var otro = (Index) obj;
|
||||||
return this.hash == otro.hash;
|
return Arrays.equals(this.data, this.fromIndex, this.fromIndex + this.length, otro.data, otro.fromIndex,
|
||||||
|
otro.fromIndex + otro.length);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -171,14 +206,12 @@ public class CalculateAverage_jgrateron {
|
|||||||
* RandomAccessFile permite dezplazar el puntero de lectura del archivo
|
* RandomAccessFile permite dezplazar el puntero de lectura del archivo
|
||||||
* Tenemos un Map para guardar las estadisticas y un map para guardar los
|
* Tenemos un Map para guardar las estadisticas y un map para guardar los
|
||||||
* nombres de las estaciones
|
* nombres de las estaciones
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
static class MiTarea implements AutoCloseable {
|
static class MiTarea implements AutoCloseable {
|
||||||
private final RandomAccessFile rFile;
|
private final RandomAccessFile rFile;
|
||||||
private long maxRead;
|
private long maxRead;
|
||||||
private Index index = new Index();
|
private Index index = new Index();
|
||||||
private Map<Index, Medicion> mediciones = new HashMap<>();
|
private Map<Index, Medicion> mediciones = new HashMap<>();
|
||||||
private Map<Index, String> estaciones = new HashMap<>();
|
|
||||||
|
|
||||||
public MiTarea(File file, Particion particion) throws IOException {
|
public MiTarea(File file, Particion particion) throws IOException {
|
||||||
rFile = new RandomAccessFile(file, "r");
|
rFile = new RandomAccessFile(file, "r");
|
||||||
@ -197,7 +230,7 @@ public class CalculateAverage_jgrateron {
|
|||||||
* obtiene la posicion de separacion ";" de la estacion y su temperatura
|
* obtiene la posicion de separacion ";" de la estacion y su temperatura
|
||||||
* calcula el hash, convierte a double y actualiza las estadisticas
|
* calcula el hash, convierte a double y actualiza las estadisticas
|
||||||
*/
|
*/
|
||||||
public Map<String, Medicion> calcularMediciones() throws IOException {
|
public Map<Index, Medicion> calcularMediciones() throws IOException {
|
||||||
var buffer = new byte[MAX_BUFFER];// buffer para lectura en el archivo
|
var buffer = new byte[MAX_BUFFER];// buffer para lectura en el archivo
|
||||||
var rest = new byte[MAX_LENGTH_LINE];// Resto que sobra en cada lectura del buffer
|
var rest = new byte[MAX_LENGTH_LINE];// Resto que sobra en cada lectura del buffer
|
||||||
var lenRest = 0;// Longitud que sobró en cada lectura del buffer
|
var lenRest = 0;// Longitud que sobró en cada lectura del buffer
|
||||||
@ -211,17 +244,15 @@ public class CalculateAverage_jgrateron {
|
|||||||
if (numBytes == -1) {
|
if (numBytes == -1) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
var totalLeidos = totalRead + numBytes;
|
numBytes = totalRead + numBytes > maxRead ? maxRead - totalRead : numBytes;
|
||||||
if (totalLeidos > maxRead) {
|
|
||||||
numBytes = maxRead - totalRead;
|
|
||||||
}
|
|
||||||
totalRead += numBytes;
|
totalRead += numBytes;
|
||||||
int pos = 0;
|
int pos = 0;
|
||||||
int len = 0;
|
int len = 0;
|
||||||
int idx = 0;
|
int idx = 0;
|
||||||
int semicolon = 0;
|
int semicolon = 0;
|
||||||
while (pos < numBytes) {
|
while (pos < numBytes) {
|
||||||
if (buffer[pos] == '\n' || buffer[pos] == '\r') {
|
var b = buffer[pos];
|
||||||
|
if (b == '\n' || b == '\r') {
|
||||||
if (lenRest > 0) {
|
if (lenRest > 0) {
|
||||||
// concatenamos el sobrante anterior con la nueva linea
|
// concatenamos el sobrante anterior con la nueva linea
|
||||||
System.arraycopy(buffer, idx, rest, lenRest, len);
|
System.arraycopy(buffer, idx, rest, lenRest, len);
|
||||||
@ -238,7 +269,7 @@ public class CalculateAverage_jgrateron {
|
|||||||
semicolon = 0;
|
semicolon = 0;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (buffer[pos] == ';') {
|
if (b == ';') {
|
||||||
semicolon = len;
|
semicolon = len;
|
||||||
}
|
}
|
||||||
len++;
|
len++;
|
||||||
@ -250,7 +281,7 @@ public class CalculateAverage_jgrateron {
|
|||||||
lenRest = len;
|
lenRest = len;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return transformMediciones();
|
return mediciones;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -270,19 +301,14 @@ public class CalculateAverage_jgrateron {
|
|||||||
* Busca una medicion por su hash y crea o actualiza la temperatura
|
* Busca una medicion por su hash y crea o actualiza la temperatura
|
||||||
*/
|
*/
|
||||||
public void updateMediciones(byte data[], int pos, int semicolon) {
|
public void updateMediciones(byte data[], int pos, int semicolon) {
|
||||||
var hashEstacion = calcHashCode(0, data, pos, semicolon);
|
|
||||||
var temp = strToInt(data, pos, semicolon);
|
var temp = strToInt(data, pos, semicolon);
|
||||||
index.setHash(hashEstacion);
|
index.setData(data, pos, semicolon);
|
||||||
var estacion = estaciones.get(index);
|
|
||||||
if (estacion == null) {
|
|
||||||
estacion = new String(data, pos, semicolon);
|
|
||||||
estaciones.put(new Index(hashEstacion), estacion);
|
|
||||||
}
|
|
||||||
index.setHash(hashEstacion);
|
|
||||||
var medicion = mediciones.get(index);
|
var medicion = mediciones.get(index);
|
||||||
if (medicion == null) {
|
if (medicion == null) {
|
||||||
medicion = new Medicion(1, temp, temp, temp);
|
var estacion = new byte[semicolon];
|
||||||
mediciones.put(new Index(hashEstacion), medicion);
|
System.arraycopy(data, pos, estacion, 0, semicolon);
|
||||||
|
medicion = new Medicion(estacion, 1, temp, temp, temp);
|
||||||
|
mediciones.put(new Index(estacion, 0, semicolon), medicion);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
medicion.update(1, temp, temp, temp);
|
medicion.update(1, temp, temp, temp);
|
||||||
@ -290,50 +316,15 @@ public class CalculateAverage_jgrateron {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Convierte las estaciones de hash a string
|
* convierte de un arreglo de bytes a integer
|
||||||
*/
|
*/
|
||||||
private Map<String, Medicion> transformMediciones() {
|
|
||||||
var newMediciones = new HashMap<String, Medicion>();
|
|
||||||
for (var e : mediciones.entrySet()) {
|
|
||||||
var estacion = estaciones.get(e.getKey());
|
|
||||||
var medicion = e.getValue();
|
|
||||||
newMediciones.put(estacion, medicion);
|
|
||||||
}
|
|
||||||
return newMediciones;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Calcula el hash de cada estacion, esto es una copia de java.internal.hashcode
|
|
||||||
*/
|
|
||||||
private int calcHashCode(int result, byte[] a, int fromIndex, int length) {
|
|
||||||
int end = fromIndex + length;
|
|
||||||
for (int i = fromIndex; i < end; i++) {
|
|
||||||
result = 31 * result + a[i];
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* convierte de un arreglo de bytes a double
|
|
||||||
*/
|
|
||||||
public int strToInt(byte linea[], int idx, int posSeparator) {
|
public int strToInt(byte linea[], int idx, int posSeparator) {
|
||||||
int number = 0;
|
|
||||||
int pos = idx + posSeparator + 1;
|
int pos = idx + posSeparator + 1;
|
||||||
boolean esNegativo = linea[pos] == '-';
|
boolean esNegativo = linea[pos] == '-';
|
||||||
if (esNegativo) {
|
pos = esNegativo ? pos + 1 : pos;
|
||||||
pos++;
|
int number = linea[pos + 1] == '.' ? DECENAS[(linea[pos] - 48)] + linea[pos + 2] - 48
|
||||||
}
|
: CENTENAS[(linea[pos] - 48)] + DECENAS[(linea[pos + 1] - 48)] + (linea[pos + 3] - 48);
|
||||||
int digit1 = linea[pos] - 48;
|
|
||||||
pos++;
|
|
||||||
if (linea[pos] == '.') {
|
|
||||||
pos++;
|
|
||||||
number = (digit1 * 10) + (linea[pos] - 48);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
int digit2 = linea[pos] - 48;
|
|
||||||
pos += 2;
|
|
||||||
number = (digit1 * 100) + (digit2 * 10) + (linea[pos] - 48);
|
|
||||||
}
|
|
||||||
return esNegativo ? -number : number;
|
return esNegativo ? -number : number;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -346,9 +337,12 @@ public class CalculateAverage_jgrateron {
|
|||||||
private int tempMin;
|
private int tempMin;
|
||||||
private int tempMax;
|
private int tempMax;
|
||||||
private int tempSum;
|
private int tempSum;
|
||||||
|
private byte estacion[];
|
||||||
|
private String nombreEstacion;
|
||||||
|
|
||||||
public Medicion(int count, int tempMin, int tempMax, int tempSum) {
|
public Medicion(byte estacion[], int count, int tempMin, int tempMax, int tempSum) {
|
||||||
super();
|
super();
|
||||||
|
this.estacion = estacion;
|
||||||
this.count = count;
|
this.count = count;
|
||||||
this.tempMin = tempMin;
|
this.tempMin = tempMin;
|
||||||
this.tempMax = tempMax;
|
this.tempMax = tempMax;
|
||||||
@ -357,12 +351,8 @@ public class CalculateAverage_jgrateron {
|
|||||||
|
|
||||||
public void update(int count, int tempMin, int tempMax, int tempSum) {
|
public void update(int count, int tempMin, int tempMax, int tempSum) {
|
||||||
this.count += count;
|
this.count += count;
|
||||||
if (tempMin < this.tempMin) {
|
this.tempMin = Math.min(tempMin, this.tempMin);
|
||||||
this.tempMin = tempMin;
|
this.tempMax = Math.max(tempMax, this.tempMax);
|
||||||
}
|
|
||||||
if (tempMax > this.tempMax) {
|
|
||||||
this.tempMax = tempMax;
|
|
||||||
}
|
|
||||||
this.tempSum += tempSum;
|
this.tempSum += tempSum;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -370,12 +360,20 @@ public class CalculateAverage_jgrateron {
|
|||||||
return Math.round(number) / 10.0;
|
return Math.round(number) / 10.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getNombreEstacion() {
|
||||||
|
if (nombreEstacion == null) {
|
||||||
|
nombreEstacion = new String(estacion);
|
||||||
|
}
|
||||||
|
return nombreEstacion;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
var min = round(tempMin);
|
var min = round(tempMin);
|
||||||
var mid = round(1.0 * tempSum / count);
|
var mid = round(1.0 * tempSum / count);
|
||||||
var max = round(tempMax);
|
var max = round(tempMax);
|
||||||
return "%.1f/%.1f/%.1f".formatted(min, mid, max);
|
var nombre = getNombreEstacion();
|
||||||
|
return "%s=%.1f/%.1f/%.1f".formatted(nombre, min, mid, max);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user