|
前面学习HashSet的时候,已经说过了,HashSet的底层就是HashMap,但是前面的键值对<key,value>,其中key是增加的元素,而value是一个PRESENT。
中PRESENT这个就是一个静态final常量,类型为object类型
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
而现在学习的map集合,保存的数据是映射关系Key-Value,Map中的Key-Value可以是任意的引用类型数据,他们都会封装到HashMap$Node中。
Map中的key是不允许重复的,Map中的key可以为null,但是只能有一个,而value的话,可以为null,也可以设置多个。Map中的key常是String类型。
添加相同的key,会覆盖原来的值。
Key-Value之间是一一对应的关系,所以通过key,可以找到value值。

?
Map集合常用的方法有:
put(Key,Value).
get(key)
remove(key)? ?remove(Key,Value)? ? 删除元素,可以根据键或者键值对来删除
size()? 元素个数
isEmpty() 是否为空
clear()? ?清除全部元素
containskey(key) 查看键是否存在
由于Map不能直接new出来,所以用它子类HashMap来实现,实际上学习HashSet前面已经说了下,但是我估摸我也没说清楚,重新来
public class MapStudy {
public static void main(String[] args) {
Map map = new HashMap();
map.put("1","武当山");
map.put("2","峨眉山");
????????map.put("1","张三丰")
map.put("3","松山");
System.out.println(map);
}
}
源码剖析:
Map map = new HashMap();这个操作调用构造器初始化加载因子LoadFactor(0.75)
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
然后再来操作 map.put("1","武当山");这个是添加值进去,如何添加的
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
进入put方法,传入key和value这两个值,也就是key=1,value=武当山。然后put里会返回一个putVal(hash(key), key, value, false, true);这个方法,这个方法里还有一个方法hash(key)。
我们添加数值进去,是通过计算hash值,然后再添加进去的。
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
而在这里的hash值不完全等于hashcode。因为这个hash值会这样取(h = key.hashCode()) ^ (h >>> 16)无符号右移16位^运算这个h = key.hashCode()。
然后返回这个hash值。然后再进去 putVal(hash(key), key, value, false, true)方法中
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
下面这个是核心代码。
Node<K,V>[] tab; Node<K,V> p; int n, i;这个定义的局部变量
现在的话,我们还没有创建出,元素放的地方。
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
首先代码会进行if判断,我们这里开始的时候存放元素的地方就是空的(tab = table) == null,所以它会进去这里,n = (tab = resize()).length;然后进行一个扩容的处理resize()。这里存放元素的地方是什么类型,等下就会揭晓
DEFAULT_INITIAL_CAPACITY默认的扩容16,下次的扩容为
DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY=12,其中这个就是加载因子DEFAULT_LOAD_FACTOR(0.75)
final Node<K,V>[] resize() {
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;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { // zero initial threshold signifies using defaults
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];
table = newTab;
if (oldTab != null) {
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;
}
扩容不细说,它最后会返回 return newTab;现在它就开辟出了容量为16的数组。再后它继续if判断,然后根据 tab[i] = newNode(hash, key, value, null);newNode的操作, newNode里的new Node进行装箱操作。
Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
return new Node<>(hash, key, value, next);
}
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
同时在对应的数组tab[i] = newNode(hash, key, value, null)中。到目前为止,已经成功的加入了一个元素。然后
++modCount;
if (++size > threshold) resize();
afterNodeInsertion(evict);
return null;
现在的我们已经可以看见了,我们的数组类型是啥子了--->HashMap$Node
newNode(hash, key, value, null);
实际上是HashMap$Node node=newNode(hash, key, value, null).但是为了方便遍历数组,还会创建EntrySet集合,该集合存放的元素的类型就是Entry。
Set<Map,Entry<key,value>>EntrySet.
但是实际上,这个EnteySet中存放的还是这个HashMap$Node。为什么这么说呢?因为这Node<K,V> implements Map.Entry<K,V> ,也就是Node实现了Entry.
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
归根到底,用这个EnteySet的原因就是为了方便我们的遍历
EnteySet提供了2个方法,一个是getKey().一个是getValue().
for(Object obj:set){
Map.Entry entry = (Map.Entry) obj;
System.out.println(entry.getKey+"-"+entry.getValue());
}
?Set set =map.keySet()? 可以取出key.
Collection value= map.value() 可以取值value
为什么Set和Collection能操作这个数组中的元素,原因是Set指向了HashMap$Node的Key,可以操作取出这个值。Collection也指向了HashMap$Node的Value,同样可以取出这个值。
到目前位置,我们已经添加入了一个元素了。
第二个 map.put("2","峨眉山");同样是先进行计算hash值,然年进入 putVal中进行判断。因为已经加入了一个元素,所以最先的这个判断不进入
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
会直接进入中进行,新增元素,由于这个元素前面没有添加,所以直接进入这个table里。
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
操作的话,就跟上面一样。
添加相同的key值,它会覆盖原来的数。这个操作的比较难点
??map.put("1","张三丰")
同样,它会进行hash值得计算,由于前面已经加入了一个同样的key,所以它不会进去这个if判断 了,if ((p = tab[i = (n - 1) & hash]) == null)? ?。tab[i] = newNode(hash, key, value, null);
它会进入下面的判断,然后进行替换值,然后还不进行++modCount;之后的操作,因为它只是替换了,不是新增一个元素。
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
然后下面还有2段操作,分别是一个链表操作,一个红黑数是操作。
红黑树操作
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab,
int h, K k, V v) {
Class<?> kc = null;
boolean searched = false;
TreeNode<K,V> root = (parent != null) ? root() : this;
for (TreeNode<K,V> p = root;;) {
int dir, ph; K pk;
if ((ph = p.hash) > h)
dir = -1;
else if (ph < h)
dir = 1;
else if ((pk = p.key) == k || (k != null && k.equals(pk)))
return p;
else if ((kc == null &&
(kc = comparableClassFor(k)) == null) ||
(dir = compareComparables(kc, k, pk)) == 0) {
if (!searched) {
TreeNode<K,V> q, ch;
searched = true;
if (((ch = p.left) != null &&
(q = ch.find(h, k, kc)) != null) ||
((ch = p.right) != null &&
(q = ch.find(h, k, kc)) != null))
return q;
}
dir = tieBreakOrder(k, pk);
}
TreeNode<K,V> xp = p;
if ((p = (dir <= 0) ? p.left : p.right) == null) {
Node<K,V> xpn = xp.next;
TreeNode<K,V> x = map.newTreeNode(h, k, v, xpn);
if (dir <= 0)
xp.left = x;
else
xp.right = x;
xp.next = x;
x.parent = x.prev = xp;
if (xpn != null)
((TreeNode<K,V>)xpn).prev = x;
moveRootToFront(tab, balanceInsertion(root, x));
return null;
}
}
}
红黑树这里,我们就先不说,等后面再来
链表操作
它是通过for循环遍历这个链表的,而且这个for循环是个死循环,只能通过break跳出
第一循环,是找一圈,然后没发现,就在最后一个后面添加,然后进行判断这个链表长度是不是已经8了 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash);然后进行树化操作。第二个if判断是,找到一个相同的,就退出,然后进行替换。
注意点:链表长度达到八,并不是立即树化,还要进行判断这个table是否已经大于等于64长度,才进行树化
{
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
?
if (e != null) { // existing mapping for key V
oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue; }
?
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)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
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();
afterNodeInsertion(evict);
return null;
}
map集合的遍历方式
foreach循环和迭代器循环
1.先取出key,再通过key取出value值
Set keySet = map.keySet();
for (Object key:keySet
) {
System.out.println(key+"-"+map.get(key));
}
Iterator iterator = keySet.iterator();
while (iterator.hasNext()) {
Object key = iterator.next();
System.out.println(key+"-"+map.get(key));
}
2.获取values的值
Collection values = map.values();
for (Object value:values
) {
System.out.println(value);
}
3.获取所有关系的key-value
Set entrySet = map.entrySet();
for (Object obj:entrySet
) {
Map.Entry entry = (Map.Entry) obj;
System.out.println(entry);
}
System.out.println("==============");
Iterator iterator1 = entrySet.iterator();
while (iterator1.hasNext()) {
Object next = iterator1.next();
Map.Entry m= (Map.Entry) next;
System.out.println(m.getKey()+"="+m.getValue());
System.out.println(next.getClass());
}
这里我犯了一个小错误Iterator,取错对象了,取成ketSet的,搞的我直接怀疑人生了,幸好我通过了getClass()这个方法,得到了返回的对象和我取的对象不一样,我一下子就回过神来了。
?
|