| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> Java知识库 -> Map集合学习第一天 -> 正文阅读 |
|
[Java知识库]Map集合学习第一天 |
前面学习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().
?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中进行判断。因为已经加入了一个元素,所以最先的这个判断不进入
会直接进入中进行,新增元素,由于这个元素前面没有添加,所以直接进入这个table里。
操作的话,就跟上面一样。 添加相同的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段操作,分别是一个链表操作,一个红黑数是操作。 红黑树操作
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; }
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()这个方法,得到了返回的对象和我取的对象不一样,我一下子就回过神来了。 ? |
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/23 19:08:29- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |