Skip to content

Commit 5fab006

Browse files
committed
HHH-19745 Better LinkedIdentityHashMap with simple hash-table array
1 parent 8347b90 commit 5fab006

File tree

1 file changed

+233
-70
lines changed

1 file changed

+233
-70
lines changed

hibernate-core/src/main/java/org/hibernate/internal/util/collections/LinkedIdentityHashMap.java

Lines changed: 233 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -4,132 +4,295 @@
44
*/
55
package org.hibernate.internal.util.collections;
66

7-
import java.util.Collection;
8-
import java.util.LinkedHashMap;
9-
import java.util.LinkedHashSet;
7+
import java.util.AbstractMap;
8+
import java.util.AbstractSet;
9+
import java.util.Arrays;
10+
import java.util.Iterator;
1011
import java.util.Map;
12+
import java.util.NoSuchElementException;
13+
import java.util.Objects;
1114
import java.util.Set;
12-
import java.util.function.BiFunction;
13-
import java.util.function.Function;
14-
import java.util.stream.Collectors;
15+
1516

1617
/**
17-
* Utility {@link Map} implementation that uses identity (==) for key comparison and
18-
* preserves insertion order like {@link LinkedHashMap}.
18+
* Utility {@link Map} implementation that uses identity (==) for key comparison and preserves insertion order
1919
*/
20-
public class LinkedIdentityHashMap<K, V> implements Map<K, V> {
21-
private final LinkedHashMap<Identity<K>, V> delegate;
20+
public class LinkedIdentityHashMap<K, V> extends AbstractMap<K, V> implements Map<K, V> {
21+
private static final int DEFAULT_INITIAL_CAPACITY = 16; // must be power of two
22+
23+
static final class Node<K, V> implements Map.Entry<K, V> {
24+
final K key;
25+
V value;
26+
Node<K, V> next;
27+
Node<K, V> before;
28+
Node<K, V> after;
29+
30+
Node(K key, V value, Node<K, V> next) {
31+
this.key = key;
32+
this.value = value;
33+
this.next = next;
34+
}
2235

23-
record Identity<K>(K key) {
2436
@Override
25-
public int hashCode() {
26-
return System.identityHashCode( key );
37+
public K getKey() {
38+
return key;
39+
}
40+
41+
@Override
42+
public V getValue() {
43+
return value;
44+
}
45+
46+
@Override
47+
public V setValue(V newValue) {
48+
final V old = value;
49+
value = newValue;
50+
return old;
2751
}
2852

2953
@Override
3054
public boolean equals(Object o) {
31-
return o instanceof Identity<?> that && this.key == that.key;
55+
return o instanceof Node<?, ?> node && key.equals( node.key ) && Objects.equals( value, node.value );
3256
}
33-
}
3457

35-
public LinkedIdentityHashMap() {
36-
delegate = new LinkedHashMap<>();
37-
}
58+
@Override
59+
public int hashCode() {
60+
int result = key.hashCode();
61+
result = 31 * result + Objects.hashCode( value );
62+
return result;
63+
}
3864

39-
public LinkedIdentityHashMap(int expectedSize) {
40-
delegate = new LinkedHashMap<>( expectedSize );
65+
@Override
66+
public String toString() {
67+
return key + "=" + value;
68+
}
4169
}
4270

43-
@Override
44-
public int size() {
45-
return delegate.size();
46-
}
71+
private Node<K, V>[] table;
72+
private int size;
4773

48-
@Override
49-
public boolean isEmpty() {
50-
return delegate.isEmpty();
74+
private Node<K, V> head;
75+
private Node<K, V> tail;
76+
77+
private transient Set<Map.Entry<K, V>> entrySetView;
78+
79+
public LinkedIdentityHashMap() {
80+
this( DEFAULT_INITIAL_CAPACITY );
5181
}
5282

53-
@Override
54-
public boolean containsKey(Object key) {
55-
return delegate.containsKey( new Identity<>( key ) );
83+
public LinkedIdentityHashMap(int initialCapacity) {
84+
if ( initialCapacity < 0 ) {
85+
throw new IllegalArgumentException( "Illegal initial capacity: " + initialCapacity );
86+
}
87+
int cap = 1;
88+
while ( cap < initialCapacity ) {
89+
cap <<= 1;
90+
}
91+
//noinspection unchecked
92+
table = (Node<K, V>[]) new Node[cap];
5693
}
5794

58-
@Override
59-
public boolean containsValue(Object value) {
60-
return delegate.containsValue( value );
95+
private static int indexFor(int hash, int length) {
96+
return hash & (length - 1);
6197
}
6298

6399
@Override
64100
public V get(Object key) {
65-
return delegate.get( new Identity<>( key ) );
101+
final Node<K, V> e = getNode( key );
102+
return e != null ? e.value : null;
66103
}
67104

68-
@Override
69-
public V put(K key, V value) {
70-
return delegate.put( new Identity<>( key ), value );
105+
private Node<K, V> getNode(Object key) {
106+
final int hash = System.identityHashCode( key );
107+
final int idx = indexFor( hash, table.length );
108+
for ( Node<K, V> e = table[idx]; e != null; e = e.next ) {
109+
if ( e.key == key ) {
110+
return e;
111+
}
112+
}
113+
return null;
71114
}
72115

73116
@Override
74-
public V putIfAbsent(K key, V value) {
75-
return delegate.putIfAbsent( new Identity<>( key ), value );
117+
public boolean containsKey(Object key) {
118+
return getNode( key ) != null;
76119
}
77120

78121
@Override
79-
public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
80-
return delegate.computeIfAbsent( new Identity<>( key ), k -> mappingFunction.apply( k.key ) );
122+
public boolean containsValue(Object value) {
123+
for ( Node<K, V> e = head; e != null; e = e.after ) {
124+
if ( Objects.equals( e.value, value ) ) {
125+
return true;
126+
}
127+
}
128+
return false;
81129
}
82130

83131
@Override
84-
public V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
85-
return delegate.computeIfPresent( new Identity<>( key ), (k, v) -> remappingFunction.apply( k.key, v ) );
132+
public V put(K key, V value) {
133+
final int hash = System.identityHashCode( key );
134+
final int idx = indexFor( hash, table.length );
135+
for ( Node<K, V> e = table[idx]; e != null; e = e.next ) {
136+
if ( e.key == key ) {
137+
final V old = e.value;
138+
e.value = value;
139+
return old;
140+
}
141+
}
142+
// not found -> insert
143+
final Node<K, V> newNode = new Node<>( key, value, table[idx] );
144+
table[idx] = newNode;
145+
linkLast( newNode );
146+
size++;
147+
if ( size == table.length ) {
148+
resize();
149+
}
150+
return null;
86151
}
87152

88-
@Override
89-
public V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
90-
return delegate.compute( new Identity<>( key ), (k, v) -> remappingFunction.apply( k.key, v ) );
153+
private void linkLast(Node<K, V> node) {
154+
if ( tail == null ) {
155+
head = tail = node;
156+
}
157+
else {
158+
tail.after = node;
159+
node.before = tail;
160+
tail = node;
161+
}
91162
}
92163

93164
@Override
94165
public V remove(Object key) {
95-
return delegate.remove( new Identity<>( key ) );
166+
final int hash = System.identityHashCode( key );
167+
final int idx = indexFor( hash, table.length );
168+
Node<K, V> prev = null;
169+
for ( Node<K, V> e = table[idx]; e != null; prev = e, e = e.next ) {
170+
if ( e.key == key ) {
171+
// remove from bucket chain
172+
if ( prev == null ) {
173+
table[idx] = e.next;
174+
}
175+
else {
176+
prev.next = e.next;
177+
}
178+
// unlink from insertion-order list
179+
final Node<K, V> b = e.before;
180+
final Node<K, V> a = e.after;
181+
if ( b == null ) {
182+
head = a;
183+
}
184+
else {
185+
b.after = a;
186+
}
187+
if ( a == null ) {
188+
tail = b;
189+
}
190+
else {
191+
a.before = b;
192+
}
193+
size--;
194+
return e.value;
195+
}
196+
}
197+
return null;
96198
}
97199

98200
@Override
99-
public void putAll(Map<? extends K, ? extends V> m) {
100-
delegate.putAll( m.entrySet().stream()
101-
.collect( Collectors.toMap( e -> new Identity<>( e.getKey() ), Entry::getValue ) ) );
201+
public void clear() {
202+
Arrays.fill( table, null );
203+
head = tail = null;
204+
size = 0;
102205
}
103206

104207
@Override
105-
public boolean remove(Object key, Object value) {
106-
return delegate.remove( new Identity<>( key ), value );
208+
public int size() {
209+
return size;
107210
}
108211

109-
@Override
110-
public void clear() {
111-
delegate.clear();
212+
private void resize() {
213+
final int oldCap = table.length;
214+
final int newCap = oldCap << 1;
215+
//noinspection unchecked
216+
final Node<K, V>[] newTable = (Node<K, V>[]) new Node[newCap];
217+
for ( int i = 0; i < oldCap; i++ ) {
218+
Node<K, V> e = table[i];
219+
while ( e != null ) {
220+
final Node<K, V> next = e.next;
221+
final int idx = indexFor( System.identityHashCode( e.key ), newCap );
222+
e.next = newTable[idx];
223+
newTable[idx] = e;
224+
e = next;
225+
}
226+
}
227+
table = newTable;
112228
}
113229

114230
@Override
115-
public Set<K> keySet() {
116-
return delegate.keySet().stream().map( w -> w.key )
117-
.collect( Collectors.toCollection( LinkedHashSet::new ) );
231+
public Set<Map.Entry<K, V>> entrySet() {
232+
if ( entrySetView == null ) {
233+
entrySetView = new EntrySet();
234+
}
235+
return entrySetView;
118236
}
119237

120-
@Override
121-
public Collection<V> values() {
122-
return delegate.values();
123-
}
238+
private final class EntrySet extends AbstractSet<Entry<K, V>> {
239+
@Override
240+
public Iterator<Entry<K, V>> iterator() {
241+
return new Iterator<>() {
242+
private Node<K, V> next = head;
243+
private Node<K, V> lastReturned = null;
124244

125-
@Override
126-
public Set<Entry<K, V>> entrySet() {
127-
return delegate.entrySet().stream().map( e -> Map.entry( e.getKey().key, e.getValue() ) )
128-
.collect( Collectors.toCollection( LinkedHashSet::new ) );
129-
}
245+
@Override
246+
public boolean hasNext() {
247+
return next != null;
248+
}
130249

131-
@Override
132-
public Object clone() throws CloneNotSupportedException {
133-
throw new CloneNotSupportedException();
250+
@Override
251+
public Map.Entry<K, V> next() {
252+
if ( next == null ) {
253+
throw new NoSuchElementException();
254+
}
255+
lastReturned = next;
256+
next = next.after;
257+
return lastReturned;
258+
}
259+
260+
@Override
261+
public void remove() {
262+
if ( lastReturned == null ) {
263+
throw new IllegalStateException();
264+
}
265+
LinkedIdentityHashMap.this.remove( lastReturned.key );
266+
lastReturned = null;
267+
}
268+
};
269+
}
270+
271+
@Override
272+
public int size() {
273+
return LinkedIdentityHashMap.this.size;
274+
}
275+
276+
@Override
277+
public void clear() {
278+
LinkedIdentityHashMap.this.clear();
279+
}
280+
281+
@Override
282+
public boolean contains(Object o) {
283+
if ( !(o instanceof Entry<?, ?> e) ) {
284+
return false;
285+
}
286+
final Node<K, V> n = getNode( e.getKey() );
287+
return n != null && Objects.equals( n.value, e.getValue() );
288+
}
289+
290+
@Override
291+
public boolean remove(Object o) {
292+
if ( !(o instanceof Entry<?, ?> e) ) {
293+
return false;
294+
}
295+
return LinkedIdentityHashMap.this.remove( e.getKey() ) != null;
296+
}
134297
}
135298
}

0 commit comments

Comments
 (0)