[toc]
为什么
总觉得java学习了这么久也要多来一些基础的,也是对原来零散的记忆的一些总结吧
个人认为基础这种东西永远不会过时,学了就是好的。基础思想都是相通的。
参考:http://wiki.jikexueyuan.com/project/java-collection/ 这个是基于1.7的 hashmap还是看jdk8好,改了不少呢
hashMap
- 构造的时候 含有数量initialCapacity和负载因子默认16 负载0.75,由于
while(capacity<initialCapacity)capacity<<=1;
每次扩张一倍;// 这个是采用位运算,最高位为1 然后右移把他的位全部变成1后 加1 即可实现只有一个1; 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; } static class Entry<K,V> implements Map.Entry<K,V> { final K key; V value; Entry<K,V> next; final int hash; …… } 有一个next 就是表示当hash相同时使用链表
put
- 添加:先判断key是否为null,因为hashmap允许key或value为null,但不能同时;为null使用return putForNullKey(value);不为空,通过indexFor(hash,table)判断当前hash值下是否有值,有只就只能遍历链表的next对比key是否相同,
// 对hash相同的 进行链表遍历 for (Entry<K,V> e = table[i]; e != null; e = e.next) { // key 相同就修改 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; // 找不到key就插入到链表中,如果大于8就整理树 if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } } //根据上一步骤中求出的hash得到在数组中是索引i int i = indexFor(hash, table.length); // 没有key的化 就添加到索引i, ++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); return null; - add方法就是这样判断key,为null,存在修改,不存在就添加然后判断是否resize 扩容.“
get
-
读取就很简单了: 就是通过hash 获取table[]里面的firstEntity,然后遍历链表即可
final Node<K,V> getNode(int hash, Object key) { Node<K,V>[] tab; Node<K,V> first, e; int n; K k; if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) { if (first.hash == hash && // always check first node ((k = first.key) == key || (key != null && key.equals(k)))) return first; if ((e = first.next) != null) { 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; }
resize
当 HashMap 中的元素个数超过数组大小 loadFactor时,就会进行数组扩容,loadFactor的默认值为 0.75,这是一个折中的取值。也就是说,默认情况下,数组大小为 16,那么当 HashMap 中元素个数超过 160.75=12 的时候,就把数组的大小扩展为 2*16=32,即扩大一倍,
– jdk1.8与1.7最大的不同就是不是每个都计算一下hash值然后算索引;而是
我们在扩充HashMap的时候,不需要像JDK1.7的实现那样重新计算hash,只需要看看原来的hash值新增的那个bit是1还是0就好了,是0的话索引没变,是1的话索引变成“原索引+oldCap
– ** 通过进行判断if ((e.hash & oldCap) == 0)
**
– 这样访问一条链表时,就可以将一条链表分成两条链表,然后newTab[j] = loHead;
,newTab[j + oldCap] = hiHead;
JDK1.7中rehash的时候,旧链表迁移新链表的时候,如果在新表的数组索引位置相同,则链表元素会倒置,但是JDK1.8不会倒置
resize参考: https://tech.meituan.com/java-hashmap.html
- 遍历方式:
- 使用entrySet:效率高,Iterator iter= map.entrySet().iterator()
- map.keySet 效率很低
- 代码如下
// 第一个是lambda的访问使用 map.forEach((key, value) -> { }); for (Map.Entry<String,String>jia:map.entrySet()) { } for (String value:map.keySet()) { }
hashTable
和hashMap也是键值对的构造,1.7与1.8没有变化
- table是一个 Entry[] 数组类型,而 Entry(在 HashMap 中有讲解过)实际上就是一个单向链表。哈希表的”key-value键值对”都是存储在Entry数组中的。
- count 是 Hashtable 的大小,它是 Hashtable 保存的键值对的数量。
- threshold 是 Hashtable 的阈值,用于判断是否需要调整 Hashtable 的容量。threshold 的值=”容量*加载因子”。
- loadFactor 就是加载因子。
- modCount 是用来实现 fail-fast 机制的。
-
构造函数: 就是new Entry,?>[initialCapacity];一个Entry的数组;默认init是11,负载因子为0.75
if (initialCapacity==0) initialCapacity = 1; this.loadFactor = loadFactor; table = new Entry<?,?>[initialCapacity]; threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
- put:先判断value是否为空,key一定 不能为空.
//也是求hash 然后对entry进行遍历,key相同就修改原值,不同就进行下面的赋值
Entry tab[] = table;
int hash = hash(key); //计算key的hash值
int index = (hash & 0x7FFFFFFF) % tab.length; //确认该key的索引位置
// 在索引位置处插入一个新的节点
Entry<K,V> e = tab[index];
// 这个就是插入链表的操作,把next放入下一个
tab[index] = new Entry<>(hash, key, value, e);
- rehash()
//还是那种1.7的计算hash重新分配,不是hashMap的那种好用的
for (int i = oldCapacity ; i-- > 0 ;) {
for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {
Entry<K,V> e = old;
old = old.next;
int index = (e.hash & 0x7FFFFFFF) % newCapacity;
e.next = (Entry<K,V>)newMap[index];
newMap[index] = e;
}
}
hashMap与hashTable区别
- HashTable 基于 Dictionary 类,而 HashMap 是基于 AbstractMap。Dictionary 是任何可将键映射到相应值的类的抽象父类,而 AbstractMap 是基于 Map 接口的实现,它以最大限度地减少实现此接口所需的工作。
- HashMap 的 key 和 value 都允许为 null,而 Hashtable 的 key 和 value 都不允许为 null。HashMap 遇到 key 为 null 的时候,调用 putForNullKey 方法进行处理,而对 value 没有处理;Hashtable遇到 null,直接返回 NullPointerException。
- Hashtable 方法是同步,而HashMap则不是。我们可以看一下源码,Hashtable 中的几乎所有的 public 的方法都是 synchronized 的,而有些方法也是在内部通过 synchronized 代码块来实现。所以有人一般都建议如果是涉及到多线程同步时采用 HashTable,没有涉及就采用 HashMap,但是在 Collections 类中存在一个静态方法:synchronizedMap(),该方法创建了一个线程安全的 Map 对象,并把它作为一个封装的对象来返回。
HashSet
HashSet 而言,它是基于 HashMap 实现的,底层采用 HashMap 来保存元素public HashSet() {map = new HashMap<>();}
对象要实现equals()和hashCode的方法,然后comparetor方法调用
- 添加key的时候
public boolean add(E e) {return map.put(e, PRESENT)==null;}
map.put(e,object)==null;是object是一定相同的,当e相同时map.put会进行判断由于value会相同然后map.put会更新value;
所以set也是进行插入不管key怎么样,但是插入的值不变,每次传递给map时,map会对hash 和 equla判断,进行的是旧值刷新的功能,因为value是不变的,key重复不怕,我每次都插入变更新,当时候还是一个key;
感觉效率不高呀
发表回复