Программа прерывается после метода put в map
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.Objects;
interface IMap<K, V> {
V get(Object k);
void put(K k, V v);
V remove(Object k);
boolean containsKey(Object k);
boolean containsValue(Object v);
int size();
}
public class Main {
public static void main(String[] args) throws IOException {
MyHashMap<String, String> map1 = new MyHashMap<>();
MyHashMap<String, String> map2 = new MyHashMap<>();
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
String S1 = reader.readLine();
int n = Integer.parseInt(S1);
for (int i = 0; i < n; i++) {
String Str = reader.readLine();
String[] a = Str.split(" ");
map1.put(a[0], a[1]);
map2.put(a[1], a[0]);
}
String S2 = reader.readLine();
int m = Integer.parseInt(S2);
for (int i = 0; i < m; i++) {
String str = reader.readLine();
if (map1.containsKey(str)) {
System.out.println(map1.get(str));
} else {
System.out.println(map2.get(str));
}
}
}
}
class MyHashMap<K, V> implements IMap<K, V> {
private static final int DEFAULT_CAPACITY = 16;
private static final int MAX_CAPACITY = 1 << 30;
private static final float DEFAULT_LOAD_FACTOR = 0.75f;
private final float loadFactor;
private int size;
private int threshHold;
private Entry<K, V>[] table;
@SuppressWarnings("unchecked")
public MyHashMap(int capacity, float loadFactor) {
if (capacity < 0) {
throw new IllegalArgumentException("Illegal capacity: " + capacity);
}
if (loadFactor < 0 || Float.isNaN(loadFactor)) {
throw new IllegalArgumentException("Illegal loadFactor: " + loadFactor);
}
if (capacity > MAX_CAPACITY) {
capacity = MAX_CAPACITY;
}
int cap = 1;
while (cap < capacity) {
cap <<= 1;
}
this.threshHold = (int) (cap * loadFactor);
this.loadFactor = loadFactor;
this.table = (Entry<K, V>[]) new Entry[cap];
}
public MyHashMap(int capacity) {
this(capacity, DEFAULT_LOAD_FACTOR);
}
public MyHashMap() {
this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR);
}
static int hash(int h, int length) {
h ^= (h >>> 20) ^ (h >>> 12); //при незначительном изменении входа
h ^= (h >>> 7) ^ (h >>> 4); //происходит сильное изменение выхода
return h & (length - 1);
}
@Override
public V get(Object key) {
if (key == null) {
if (table[0] == null) {
return null;
}
Entry<K, V> tE = table[0];
while (tE.key != null) {
if (tE.next == null) {
return null;
}
tE = tE.next;
}
return tE.value;
} else {
int hashKey = hash(key.hashCode(), table.length);
if (table[hashKey] == null) {
return null;
} else {
Entry<K, V> tE = table[hashKey];
while (tE.key == null || !(tE.key.equals(key))) {
if (tE.next == null) {
return null;
} else {
tE = tE.next;
}
}
return tE.value;
}
}
}
@Override
public void put(K key, V value) {
if (size > threshHold) {
resize(2 * table.length);
}
if (key == null) {
if (table[0] == null) {
Entry<K, V> newE = new Entry<>(null, value, null);
table[0] = newE;
size++;
} else {
Entry<K, V> e = table[0];
while (e.key != null) {
if (e.key == null) {
Entry<K, V> newE = new Entry<>(null, value, table[0]);
table[0] = newE;
size++;
return;
} else {
e = e.next;
}
}
e.value = value;
}
} else {
int hashKey = hash(key.hashCode(), table.length);
if (table[hashKey] == null) {
Entry<K, V> newE = new Entry<>(key, value, null);
table[hashKey] = newE;
size++;
} else {
Entry<K, V> e = table[hashKey];
while (e.key != null || e.key.equals(key)) {
if (e.next == null) {
Entry<K, V> newE = new Entry<>(key, value, table[hashKey]);
table[hashKey] = newE;
size++;
} else {
e = e.next;
}
}
e.value = value;
}
}
}
@Override
public V remove(Object key) {
if (key == null) {
if (table[0] == null) {
return null;
} else {
Entry<K, V> tE = table[0];
if (table[0].key == null) {
Entry<K, V> keyToRemove = table[0];
V vR = keyToRemove.value;
table[0] = keyToRemove.next;
size--;
return vR;
} else {
Entry<K, V> prev = null;
while (tE.key != null) {
if (tE.next == null) {
return null;
} else {
prev = tE;
tE = tE.next;
}
}
V vR = tE.value;
prev.next = tE.next;
size--;
return vR;
}
}
} else {
int hashKey = hash(key.hashCode(), table.length);
if (table[hashKey] == null) {
return null;
} else {
Entry<K, V> tE = table[hashKey];
if (table[hashKey].key.equals(key)) {
Entry<K, V> keyToRemove = table[hashKey];
V vR = keyToRemove.value;
table[hashKey] = keyToRemove.next;
size--;
return vR;
} else {
Entry<K, V> prev = null;
while (tE.key == null || !(tE.key.equals(key))) {
if (tE.next == null) {
return null;
} else {
prev = tE;
tE = tE.next;
}
}
V vR = tE.value;
Objects.requireNonNull(prev).next = tE.next;
size--;
return vR;
}
}
}
}
@Override
public boolean containsKey(Object key) {
if (key == null) {
if (table[0] == null) {
return false;
} else {
Entry<K, V> tE = table[0];
while (tE.key != null) {
if (tE.next == null) {
return false;
} else {
tE = tE.next;
}
}
return true;
}
} else {
Entry<K, V> tE = table[hash(key.hashCode(), table.length)];
if (tE == null) {
return false;
}
while (tE.key == null || !(tE.key.equals(key))) {
if (tE.next == null) {
return false;
} else {
tE = tE.next;
}
}
return true;
}
}
@Override
public boolean containsValue(Object value) {
if (value == null) {
for (var e : table) {
while (e != null) {
if (e.value == null) {
return true;
} else {
e = e.next;
}
}
}
} else {
for (var e : table) {
while (e != null) {
if (e.value == null) {
e = e.next;
} else if (e.value.equals(value)) {
return true;
} else {
e = e.next;
}
}
}
}
return false;
}
@Override
public int size() {
return 0;
}
void resize(int newCapacity) {
if (table.length == MAX_CAPACITY) {
threshHold = Integer.MAX_VALUE;
} else {
Entry<K, V>[] copyOfTable = Arrays.copyOf(table, table.length);
@SuppressWarnings("unchecked")
Entry<K, V>[] newHashTable = (Entry<K, V>[]) new Entry[newCapacity];
table = newHashTable;
size = 0;
for (int i = 0; i < copyOfTable.length; i++) {
if (copyOfTable[i] != null) {
Entry<K, V> e = copyOfTable[i];
while (e != null) {
put(e.key, e.value);
e = e.next;
}
}
}
threshHold = (int) (newCapacity * loadFactor);
}
}
@Override
public String toString() {
StringBuilder result = new StringBuilder("{ ");
for (var el : table) {
if (el != null) {
result.append(el).append(", ");
}
}
int lastComma = result.lastIndexOf(",");
if (lastComma > 1)
result.deleteCharAt(lastComma);
result.append("}");
return result.toString();
}
static class Entry<K, V> { //пара
private final K key;
Entry<K, V> next;
private V value;
public Entry(K key, V value, Entry<K, V> next) {
this.key = key;
this.value = value;
this.next = next;
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
public void setValue(V value) {
this.value = value;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Entry<?, ?> entry = (Entry<?, ?>) o;
return Objects.equals(key, entry.key) && Objects.equals(value, entry.value);
}
@Override
public int hashCode() {
return Objects.hash(key, value);
}
@Override
public String toString() {
return key + "=" + value;
}
}
}
Входные данные:
3
car plane //пары
mouse cat
base stream
3
plane //элементы пар
stream
base
Необходимо вывести для каждого элемента попарный ему: Вывод:
car
base
stream
В моем коде после
map1.put(a[0], a[1]);
map2.put(a[1], a[0]);
программа прерывается, при долгом ожидании возникает проблема
java.lang.OutOfMemoryError: Java heap space в строке
Entry<K, V> newE = new Entry<>(key, value, table[hashKey]);
Ответы (1 шт):
В представленном коде происходит коллизия хэш-кодов для ключей "stream" и "cat" при добавлении во вторую мапу, но при разрешении коллизии получается бесконечный цикл в методе put.
Вероятно, следует поставить break при разрешении коллизии:
// public void put(K key, V value) {
// ...
} else {
// System.out.println("\tput " + key + " -> " + value);
int hashKey = hash(key.hashCode(), table.length);
if (table[hashKey] == null) {
Entry<K, V> newE = new Entry<>(key, value, null);
table[hashKey] = newE;
size++;
} else {
// System.out.println("\tcollision " + key + " -> " + hashKey);
Entry<K, V> e = table[hashKey];
while (e.key != null || e.key.equals(key)) {
if (e.next == null) {
Entry<K, V> newE = new Entry<>(key, value, table[hashKey]);
table[hashKey] = newE;
size++;
break; // <=== !!! коллизия разрешена
} else {
e = e.next;
}
}
e.value = value;
}
}
// }
Тогда получится, что во второй мапе останется два элемента.
Результат для изменённого кода после добавления сообщений в лог:
1. 'car plane': [car, plane] ->
put car -> plane
put plane -> car
map1={ car=plane }; map2={ plane=car }
2. 'mouse cat': [mouse, cat] ->
put mouse -> cat
put cat -> mouse
map1={ car=plane, mouse=cat }; map2={ cat=mouse, plane=car }
3. 'base stream': [base, stream] ->
put base -> stream
put stream -> base
collision stream -> 2
map1={ car=plane, mouse=cat, base=stream }; map2={ stream=base, plane=car }
car
base
stream
Обновление
Более корректный способ обработки коллизии предложил Марк Л в своём ответе, когда будет добавляться новый узел в таблицу.
Также следует отметить, что необходимо улучшить метод toString в реализации мапы, чтобы были видны "вложенные" узлы:
@Override
public String toString() {
StringBuilder result = new StringBuilder("{ ");
for (var el : table) {
if (el != null) {
result.append(el);
Entry<K,V> en = el.next;
while (en != null) {
result.append(" | ").append(en);
en = en.next;
}
result.append(", ");
}
}
int lastComma = result.lastIndexOf(",");
if (lastComma > 1)
result.deleteCharAt(lastComma);
result.append("}");
return result.toString();
}
Пример логов для ввода:
4
ng bi
tb zh
tj ym
vu ou
3
tj
ng
vu
1. 'ng bi': [ng, bi] -> map1={ ng=bi }; map2={ bi=ng }
2. 'tb zh': [tb, zh] -> map1={ tb=zh, ng=bi }; map2={ zh=tb, bi=ng }
3. 'tj ym': [tj, ym] -> map1={ tb=zh, ng=bi, tj=ym }; map2={ zh=tb, bi=ng | ym=tj }
4. 'vu ou': [vu, ou] -> map1={ tb=zh, ng=bi | vu=ou, tj=ym }; map2={ zh=tb, ou=vu, bi=ng | ym=tj }
ym
bi
ou