[JavaSE 8 고급 프로 그래 밍 집합 프레임 워 크] 집합 프레임 워 크 입문 시리즈 ③ Map 실현 류 HashMap 20203_2
166751 단어 자바 고급
정의: Hashtable 기반 맵 인터페이스 구현
Hashtable 과 구별:
본체
본질: 본 체 는 Node 유형 배열 -
Node[] table
이 고 내부 에 저 장 된 요 소 는 Node 가 트 리 화 조건 에 도달 한 후에 일반 Node 는 TreeNode 로 전환 된다.주석 요점
1.Iterator
Iterator HashMap “ Capacity”(Bucket ) ( )
, ( ),
2.HashMap :
, [16]
[0.75]
Entry , , *2
3.
, (put,get )
Map , rehash
4.
rehash , K-V, Map
Hash , ( ) Hash
5. sync
, , 。
Map m = Collections.synchronizedMap(new HashMap(...)); //
6.fail-faster
“ ” ——
( ) , ConcurrentModificationException
, , , , 。
, : bug。
; 。
필기 실현
1.bucket & tree bucket (bin/Treebin)
Hashtable bucket( / ), 1 bucket TreeNodes
TreeNode bucket bucket ,
, bucket bucket , bucket ,
2.bucket
, hasCode , , Comparable
compareTo 。 ,
hashCode() hashCode , O(log n)
3.TreeNode & NormalNode
, bucket TreeNode
( ) , 。 hashcodes ,
, hashCodes, bucket , 0.75
0.5, resizing granularity 。
bucket k (exp(-0.5) pow(0.5, k) / factorial(k))
0: 0.60653066
1: 0.30326533
2: 0.07581633
3: 0.01263606
4: 0.00157952
5: 0.00015795
6: 0.00001316
7: 0.00000094
8: 0.00000006
8 1 [ Hash ]
Treebin.root 。 Iterator.remove , TreeNode.root()
HashCode ,
“tab” , table,
bin list/ treeified、split untreeified , / ,
, iterator.remove splits traversals
, ( ),
classes identityHashCodes
vs LinkedHashMap 。?
, , LinkedHashMap
( 。)
소스 코드 분석
1. 클래스 서명
public class HashMap<K,V>
extends AbstractMap<K,V>//implements Map
implements Map<K,V>, Cloneable, Serializable
2. 관건 적 인 매개 변수 (클래스 변수)
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 16
static final int MAXIMUM_CAPACITY = 1 << 30; //
static final float DEFAULT_LOAD_FACTOR = 0.75f; //
static final int TREEIFY_THRESHOLD = 8; //
static final int UNTREEIFY_THRESHOLD = 6; //
static final int MIN_TREEIFY_CAPACITY = 64; // · 64 resize
// bin , , resizing treeification
3. 본체 및 속성 (인 스 턴 스 변수)
//The table
// ; , 2 。 0 ·tableSizeFor
transient Node<K,V>[] table;
//Holds cached entrySet()
// ,AbstractMap keySet() values()。
transient Set<Map.Entry<K,V>> entrySet;
//k-v
transient int size;
//
// , Map (rehash)
transient int modCount;
// resize
// , , , DEFAULT_INITIAL_CAPACITY
int threshold;
// ( )
final float loadFactor;
4. 데이터 구 조 · 일반 노드
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}
5. 도구 정적 클래스 방법
//
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
//
// x C Comparable C, null
static Class<?> comparableClassFor(Object x) {
if (x instanceof Comparable) {
Class<?> c; Type[] ts, as; Type t; ParameterizedType p;
if ((c = x.getClass()) == String.class) // bypass checks
return c;
if ((ts = c.getGenericInterfaces()) != null) {
for (int i = 0; i < ts.length; ++i) {
if (((t = ts[i]) instanceof ParameterizedType) &&
((p = (ParameterizedType)t).getRawType() ==
Comparable.class) &&
(as = p.getActualTypeArguments()) != null &&
as.length == 1 && as[0] == c) // type arg is c
return c;
}
}
}
return null;
}
//
static int compareComparables(Class<?> kc, Object k, Object x) {
return (x == null || x.getClass() != kc ? 0 :
((Comparable)k).compareTo(x));
}
// - -> 2^ [ (64,128) 128]
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
※ 6. 공통 실례 방법
*4, *2, *2, *0, *8
*1, *1, *7, *2 [Java8·Map ]
구조 기 * 4
1. +
2.
3. ,
4. Map HashMap
//1
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity); //new Table, threshold
}
//2 1 threshold
public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); }
//3
public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted }
//4
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false); //false HashMap LinkedHashMap
}
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
int s = m.size();
if (s > 0) {
if (table == null) { // pre-size
float ft = ((float)s / loadFactor) + 1.0F;
int t = ((ft < (float)MAXIMUM_CAPACITY) ?
(int)ft : MAXIMUM_CAPACITY); //
if (t > threshold)
threshold = tableSizeFor(t); //
}
else if (s > threshold)
resize(); // Entry resize
for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
K key = e.getKey();
V value = e.getValue();
putVal(hash(key), key, value, false, evict); //for k-v
}
}
}
첨삭
*2
//1.
public V put(K key, V value) { return putVal(hash(key), key, value, false, true); }
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0) // : tab HERE
n = (tab = resize()).length; // threshold
if ((p = tab[i = (n - 1) & hash]) == null) // NULL 1 Node
tab[i] = newNode(hash, key, value, null);
else { // NULL,
Node<K,V> e; K k;
//IF1 .Key == put.Key Node p Node e,
if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//IF2 TreeNode.putTreeVal()
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
//ELSE3
else {
//
for (int binCount = 0; ; ++binCount) {
//IF1 next new
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash); // : treeifyBin HERE
break;
}
//IF2 Key [e = p.next]
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e; // e=p.next p,
}
}
// Key Value V [Put 2]
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount; //
if (++size > threshold) // resize
resize();
afterNodeInsertion(evict); // LinkedHashMap
return null; // [Put 1]
}
final Node<K,V>[] resize() { // | table Hash
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) { //
threshold = Integer.MAX_VALUE;
return oldTab; // oldTab
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY) // 16 tab
newThr = oldThr << 1; // 2
}
else if (oldThr > 0) //
newCap = oldThr;
else { // DEFAULT_INITIAL_CAPACITY 16
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr; //
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; // new tab
table = newTab; // tab
if (oldTab != null) { // , hash ,
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
final void treeifyBin(Node<K,V>[] tab, int hash) { //
int n, index; Node<K,V> e;
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) //
resize(); // tab 64 16-32,32-64 2 resize
else if ((e = tab[index = (n - 1) & hash]) != null) {
TreeNode<K,V> hd = null, tl = null;
do {
TreeNode<K,V> p = replacementTreeNode(e, null){
return new TreeNode<>(e.hash, e.key, e.value, next);
}; // ->
if (tl == null)
hd = p;
else {
p.prev = tl;
tl.next = p;
}
tl = p;
} while ((e = e.next) != null);
if ((tab[index] = hd) != null)
hd.treeify(tab); //
}
}
Class TreeNode
final void treeify(Node<K,V>[] tab) { //
TreeNode<K,V> root = null;
for (TreeNode<K,V> x = this, next; x != null; x = next) { //
next = (TreeNode<K,V>)x.next;
x.left = x.right = null;
if (root == null) { //root
x.parent = null;
x.red = false;
root = x;
}
else { // TreeNode
K k = x.key;
int h = x.hash;
Class<?> kc = null;
for (TreeNode<K,V> p = root;;) { // NULL |
int dir, ph;
K pk = p.key;
if ((ph = p.hash) > h)
dir = -1;
else if (ph < h)
dir = 1;
else if ((kc == null &&
(kc = comparableClassFor(k)) == null) ||
(dir = compareComparables(kc, k, pk)) == 0)
dir = tieBreakOrder(k, pk); //
TreeNode<K,V> xp = p;
if ((p = (dir <= 0) ? p.left : p.right) == null) {
x.parent = xp;
if (dir <= 0)
xp.left = x;
else
xp.right = x;
root = balanceInsertion(root, x);
break;
}
}
}
}
moveRootToFront(tab, root); // root bin
}
static int tieBreakOrder(Object a, Object b) {
int d;
if (a == null || b == null ||
(d = a.getClass().getName().
compareTo(b.getClass().getName())) == 0)
d = (System.identityHashCode(a) <= System.identityHashCode(b) ?
-1 : 1);
return d;
}
static <K,V> void moveRootToFront(Node<K,V>[] tab, TreeNode<K,V> root) {
int n;
if (root != null && tab != null && (n = tab.length) > 0) {
int index = (n - 1) & root.hash;
TreeNode<K,V> first = (TreeNode<K,V>)tab[index];
if (root != first) {
Node<K,V> rn;
tab[index] = root;
TreeNode<K,V> rp = root.prev;
if ((rn = root.next) != null)
((TreeNode<K,V>)rn).prev = rp;
if (rp != null)
rp.next = rn;
if (first != null)
first.prev = root;
root.next = first;
root.prev = null;
}
assert checkInvariants(root);
}
}
//2.
// : Map HashMap · for putVal
public void putAll(Map<? extends K, ? extends V> m) { putMapEntries(m, true); }
*2
//1. Key Value/NULL
public V remove(Object key) {
Node<K,V> e;
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}
final Node<K,V> removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable) {
//matchValue ,
//movable false,
Node<K,V>[] tab; Node<K,V> p; int n, index;
if ((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n - 1) & hash]) != null) {
Node<K,V> node = null, e; K k; V v;
if (p.hash == hash && //IF1 hash && key || key NULL && K. equals(Node.key)
((k = p.key) == key || (key != null && key.equals(k))))
node = p; // Node node
else if ((e = p.next) != null) { //IF2 Node
if (p instanceof TreeNode) // , TreeNode
node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
else { // ,
do {
if (e.hash == hash &&
((k = e.key) == key ||
(key != null && key.equals(k)))) {
node = e; // e node
break;
}
p = e;
} while ((e = e.next) != null);
}
}
// node
if (node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v)))) {
if (node instanceof TreeNode) // , TreeNode.removeTreeNode
((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
else if (node == p) // , ?
tab[index] = node.next;
else // ,
p.next = node.next; // .next .next
++modCount;
--size;
afterNodeRemoval(node); // LinkedHashMap
return node;
}
}
return null;
}
//2.
public void clear() {
Node<K,V>[] tab;
modCount++;
if ((tab = table) != null && size > 0) {
size = 0;
for (int i = 0; i < tab.length; ++i)
tab[i] = null; // tab[i] NULL
}
}
*0
*8
//1. k-v
public int size() { return size; }
//2.
public boolean isEmpty() { return size == 0; }
//3. Key Value NULL
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
//IF1: table NULL && table.length>0 && hash Node NULL
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
//IF2: first Key hash && first.Key Key
Key NULL && Key.equals(first.Key) =>
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
//IF3: first NULL
if ((e = first.next) != null) {
//IF4: , TreeNode getTreeNode
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
// ,
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
//4. Key
public boolean containsKey(Object key) { return getNode(hash(key), key) != null; }
//5. Value
public boolean containsValue(Object value) {
Node<K,V>[] tab; V v;
if ((tab = table) != null && size > 0) { // for
for (int i = 0; i < tab.length; ++i) {
for (Node<K,V> e = tab[i]; e != null; e = e.next) {
if ((v = e.value) == value ||
(value != null && value.equals(v)))
return true;
}
}
}
return false;
}
//6. KeySet [HashMap =KeySet+ValueSet || =EntrySet]
public Set<K> keySet() {
Set<K> ks = keySet;
if (ks == null) {
ks = new KeySet(); //KeySet Key, Iterator() tab[]
keySet = ks;
}
return ks;
}
final class KeySet extends AbstractSet<K> {
public final int size() { return size; }
public final void clear() { HashMap.this.clear(); }
public final Iterator<K> iterator() { return new KeyIterator(); }
public final boolean contains(Object o) { return containsKey(o); }
public final boolean remove(Object key) {
return removeNode(hash(key), key, null, false, true) != null;
}
public final Spliterator<K> spliterator() {
return new KeySpliterator<>(HashMap.this, 0, -1, 0, 0);
}
public final void forEach(Consumer<? super K> action) {
Node<K,V>[] tab;
if (action == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
int mc = modCount;
for (int i = 0; i < tab.length; ++i) {
for (Node<K,V> e = tab[i]; e != null; e = e.next)
action.accept(e.key);
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
}
//7. ValueCollection [ KeySet]
public Collection<V> values() {
Collection<V> vs = values;
if (vs == null) {
vs = new Values();
values = vs;
}
return vs;
}
final class Values extends AbstractCollection<V> {
public final int size() { return size; }
public final void clear() { HashMap.this.clear(); }
public final Iterator<V> iterator() { return new ValueIterator(); }
public final boolean contains(Object o) { return containsValue(o); }
public final Spliterator<V> spliterator() {
return new ValueSpliterator<>(HashMap.this, 0, -1, 0, 0);
}
public final void forEach(Consumer<? super V> action) {
Node<K,V>[] tab;
if (action == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
int mc = modCount;
for (int i = 0; i < tab.length; ++i) {
for (Node<K,V> e = tab[i]; e != null; e = e.next)
action.accept(e.value);
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
}
//8. EntrySet
public Set<Map.Entry<K,V>> entrySet() {
Set<Map.Entry<K,V>> es;
return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
}
final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
public final int size() { return size; }
public final void clear() { HashMap.this.clear(); }
public final Iterator<Map.Entry<K,V>> iterator() {
return new EntryIterator();
}
public final boolean contains(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry<?,?> e = (Map.Entry<?,?>) o;
Object key = e.getKey();
Node<K,V> candidate = getNode(hash(key), key);
return candidate != null && candidate.equals(e);
}
public final boolean remove(Object o) {
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>) o;
Object key = e.getKey();
Object value = e.getValue();
return removeNode(hash(key), key, value, true, true) != null;
}
return false;
}
public final Spliterator<Map.Entry<K,V>> spliterator() {
return new EntrySpliterator<>(HashMap.this, 0, -1, 0, 0);
}
public final void forEach(Consumer<? super Map.Entry<K,V>> action) {
Node<K,V>[] tab;
if (action == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
int mc = modCount;
for (int i = 0; i < tab.length; ++i) {
for (Node<K,V> e = tab[i]; e != null; e = e.next)
action.accept(e);
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
}
첨삭
//Java 8 Map ·
1 1 7 2
*1
//1.Key , K-V
V putIfAbsent(K key, V value)
*1
//1. KV ,
boolean remove(Object key, Object value)
*7
//1. K V, boolean
boolean replace(K key, V oldValue, V newValue)
//2. K V, Value|NULL
V replace(K key, V value)
//3. · Key V NULL
V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction)
//4. · Key V
V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)
//5. ·K·BiFunction
V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)
//6. · Key
void replaceAll(BiFunction<? super K, ? super V, ? extends V> function)
//7. , value NULL , Key
V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction)
*2
//1. , Key Value
V getOrDefault(Object key, V defaultValue)
//2.
void forEach(BiConsumer<? super K, ? super V> action)
Cloning and serialization
public Object clone() loadFactor() capacity() private void writeObject() private void readObject()
Iterator
HashIterator HashMapSpliterator KeySpliterator ValueSpliterator EntrySpliterator
링크 드 HashMap 하위 클래스 지원 방법
다음 private - package 방법 은 링크 드 HashMap 으로 덮어 쓸 수 있 으 나 다른 하위 클래스 로 덮어 쓰 지 않 습 니 다.LinkedHashMap, view classes, HashSet 에서 newNode () replacementNode () newTreeNode () replacementTreeNode () reitialize () afterNodeAccess () afterNodeInsertion () afterNodeRemoval () internalWriteEntries () 사용 가능
TreeNode
22832 chars, 588 line 한 편 씩 주세요.