HashSet和HashMap分析
来源:互联网 发布:python中迭代器 编辑:程序博客网 时间:2024/06/13 00:42
HashSet:
HashSet背后主要是一个HashMap在支持。HashSet的元素都作为HashMap每一对key-value的Key来存储,每个Key的Value都等于PRESENT。
以下是HashSet的部分源代码:
public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable{ static final long serialVersionUID = -5024744406713321676L; private transient HashMap<E,Object> map; // Dummy value to associate with an Object in the backing Map private static final Object PRESENT = new Object(); public HashSet() { map = new HashMap<>(); } public HashSet(Collection<? extends E> c) { map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16)); addAll(c); } public HashSet(int initialCapacity, float loadFactor) { map = new HashMap<>(initialCapacity, loadFactor); } public HashSet(int initialCapacity) { map = new HashMap<>(initialCapacity); } HashSet(int initialCapacity, float loadFactor, boolean dummy) { map = new LinkedHashMap<>(initialCapacity, loadFactor); } public Iterator<E> iterator() { return map.keySet().iterator(); } public int size() { return map.size(); } public boolean isEmpty() { return map.isEmpty(); } public boolean contains(Object o) { return map.containsKey(o); } public boolean add(E e) { return map.put(e, PRESENT)==null; } public boolean remove(Object o) { return map.remove(o)==PRESENT; } public void clear() { map.clear(); }}
有上面的源码可以看出HashSet的相关操作都是HashMap的操作,元素不重复主要是通过HashMap来实现。因为HashMap的Key是不允许重复的,所以就保证了HashSet的元素不重复。那么这里对重复的判断是怎样实现的?那就得看看HashMap的实现。
HashMap:
内部主要有一个Entry<K,V>类型的数组table,即
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE; static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 static final int MAXIMUM_CAPACITY = 1 << 30; static final float DEFAULT_LOAD_FACTOR = 0.75f; static final Entry<?,?>[] EMPTY_TABLE = {}; transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE; transient int size; int threshold; final float loadFactor; transient int modCount; static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;
实际上java容器都是自动增长类型的数据结构,他们实现自动增长的方式都类似,都是通过capacity和loadFactor来动态调整容器大小。当达到loadfactor的数据比例时候,容器申请2倍更大的空间,然后将原来的数据拷贝到新空间中。但是当数据量变小,并不会自动缩小。因此对于提前预知数据量很大的时候,可以直接先设置capacity的初始值大一点,以防止自动增长时候内存拷贝的开销。如果提前预知数据量很小,那么就不需要设置很大的capacity以免浪费内存。
table数组的类型是Entry<K,V>我们来看看这是什么数据结构。
以下是Entry的部分源代码:
static class Entry<K,V> implements Map.Entry<K,V> { final K key; V value; Entry<K,V> next; int hash; Entry(int h, K k, V v, Entry<K,V> n) { value = v; next = n; key = k; hash = h; } public final boolean equals(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry e = (Map.Entry)o; Object k1 = getKey(); Object k2 = e.getKey(); if (k1 == k2 || (k1 != null && k1.equals(k2))) { Object v1 = getValue(); Object v2 = e.getValue(); if (v1 == v2 || (v1 != null && v1.equals(v2))) return true; } return false; } public final int hashCode() { return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue()); } public final String toString() { return getKey() + "=" + getValue(); } void recordAccess(HashMap<K,V> m) { } void recordRemoval(HashMap<K,V> m) { } }
Entry是由final Kkey;V value;Entry<K,V>next;int hash;组成的。其实就是一个链表的结点,数据域有hash,key,value,指针域是next(当然java中是引用)。也就是HashMap是一个数组链表法解决hash冲突实现的hash结构。这里的key是final类型,是不可以改变的;也就是说一旦你put一个新key,那么他就不能再改变指向了。
Entry的equals方法已经被重写了,当且仅当两个对象都是Entry对象且key和value同时相等时才相等。
hashCode方法也被重写成key和value的hashCode异或值。这是为什么呢?似乎HashMap并没有对Entry的比较,HashMap比较的都是Entry.key和Entry.hash。可能有的代码通过HashMap.entrySet()方法得到Entry集合,需要比较里面的Entry。HashMap.Entry是接口Map.Entry的实现类,需要重写这两个方法以便确保两个Entry的正确比较。
知道了table的结构,就来看看主要操作put和remove的实现,这里只介绍put部分源代码:
public V put(K key, V value) { if (table == EMPTY_TABLE) { inflateTable(threshold); } if (key == null) return putForNullKey(value); int hash = hash(key); int i = indexFor(hash, table.length); for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash, key, value, i); return null; }final int hash(Object k) { int h = hashSeed; if (0 != h && k instanceof String) { return sun.misc.Hashing.stringHash32((String) k); } h ^= k.hashCode(); // This function ensures that hashCodes that differ only by // constant multiples at each bit position have a bounded // number of collisions (approximately 8 at default load factor). h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); } private V putForNullKey(V value) {/*这里之所以是循环,是因为可能还有其他非空key也会映射到0地址处*/ for (Entry<K,V> e = table[0]; e != null; e = e.next) { if (e.key == null) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(0, null, value, 0); return null; } private void putForCreate(K key, V value) { int hash = null == key ? 0 : hash(key); int i = indexFor(hash, table.length); for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { e.value = value; return; } } createEntry(hash, key, value, i); }void addEntry(int hash, K key, V value, int bucketIndex) { if ((size >= threshold) && (null != table[bucketIndex])) { resize(2 * table.length); hash = (null != key) ? hash(key) : 0; bucketIndex = indexFor(hash, table.length); } createEntry(hash, key, value, bucketIndex); }/*头插法,先保存头table[bucketIndex],在将新Entry的next域指向为table[bucketIndex],最后将头指向新Entry*/void createEntry(int hash, K key, V value, int bucketIndex) { Entry<K,V> e = table[bucketIndex]; table[bucketIndex] = new Entry<>(hash, key, value, e); size++; }
如果table还是空的,如果第一次调用put,这时候table首先会生成一个16个元素大小的hash表。也就是调用时才申请空间copy on write。
然后分以下情况:
a.key不是空且key为null,则放入null。这里说明HashMap支持nullkey。并且所有的nullkey都放在table中0地址中。这也说明HashSet可以存放一个null元素。HashMap也只能存放一个nullkey的map,再次存入肯定value会被覆盖。
b.以上情况都不是,那么计算key的hash值。每个对象的hashCode方法默认都是本地方法。其实本地方法hashCode返回的就是对象的地址值。hashMap里面的hash函数,实际上是分两种情况处理, 1.String对象,那么直接sun.misc.Hashing.stringHash32((String)k);2.其他对象,先算出hashCode,然后再映射到table数组下标。然后搜索是否存在e使得e.hash==hash和e.key==key同时成立。这里可以看出,即使是不同的key,也有可能最终得到相同的index。如果存在,那么修改value即可,不存在则生成一个新的entry加入。因此如果想保证hashSet或者HashMap只放入内容不重复的元素,必须同时重写hashCode方法和equals方法。
通过返回关注内容的hashCode和比较关注内容(这里关注内容可以是对象的某些属性)重新定义hashCode和equals方法。
关于头插法的示意图:
下面是一个小测试代码:
package test;import java.util.HashMap;import java.util.HashSet;import java.util.Iterator;import java.util.Map;import java.util.Set;class Person {String Id;String name;Person(String id, String name){this.Id = id;this.name = name;}@Overridepublic String toString() {// TODO Auto-generated method stubreturn Id + ":" + name;}@Overridepublic int hashCode() {// TODO Auto-generated method stubreturn this.name.hashCode()^this.Id.hashCode();}@Overridepublic boolean equals(Object obj) {// TODO Auto-generated method stubif (obj instanceof Person ) {Person p = (Person)obj;if (this.Id == p.Id || (this.Id != null && this.Id.equals(p.Id)))return true;elsereturn false;}return false;}}public class JavaFscan {public static void main(String args[]){Map<Person,String> m = new HashMap<Person,String>();Set<Person> s = new HashSet<Person>();Person p1 = new Person("1", "ki");Person p2 = new Person("2", "ki");Person p3 = p1;Person p4 = new Person("4", "qi");s.add(p1);s.add(p2);s.add(p3);s.add(p4);//s.add(null);//m.put(null,null);//m.put(null, "si");//m.put(null, "ti");m.put(p1, "1");m.put(p2, "2");m.put(p3, "3");Iterator<Person> its = s.iterator();Set<Map.Entry<Person, String>> sets = m.entrySet();Iterator<Map.Entry<Person, String>> itm = sets.iterator();while(its.hasNext()){Person p = its.next();p.Id= "1";}while(itm.hasNext()){Map.Entry<Person, String> e = itm.next();Person pp = e.getKey();pp.Id = "1";}System.out.println(m.get(p1));System.out.println(s);System.out.println(m);}}
总结:
1.HashSet可以支持null元素,但最多放一个,HashSet不支持重复元素,因为元素是内部HashMap的Key;
2.HashSet由HashMap支持,HashMap支持null,但最多只能有一个NULL Key map;
3.判断HashSet或者HashMap元素重复,可以重写元素的hashCode和equals方法,比较需要关心的内容;
4.不要试图修改HashSet里面对象元素的内容,这样可能会导致修改后和其中一个已经存在的元素相等的情况,从而造成下次查询HashMap不知道返回哪一个;- HashSet和HashMap分析
- HashMap和HashSet的区别和分析
- HashMap和HashSet的区别和分析
- HashSet和HashMap源码实现分析
- HashMap和HashSet的源代码分析
- java-HashMap和HashSet源码分析
- HashMap HashSet源码分析
- HashSet 、HashMap 和 HashTable
- Hashtable、HashMap和HashSet
- HashSet和HashMap
- hashmap和hashset区别
- hashSet和hashmap
- HashMap和HashSet详解
- 【javaSE】HashSet和HashMap
- HashSet和HashMap
- HashSet和HashMap
- HashSet和HashMap比较
- HashMap和HashSet解析
- 彻底理解ThreadLocal
- java设计模式之创建型模式-工厂方法模式
- 社会化分享 使用as
- Activity的启动流程
- 【转载】循环结构中break、continue、return和exit的区别
- HashSet和HashMap分析
- Android Studio打包Android Library为jar方案
- js循环绑定事件解决方案
- iconfont使用
- Java —— Hibernate4 No Session found for current thread
- js中对字符串的操作
- Android 之ActivityThead、ActivityManagerService 与activity的管理和创建
- 实际运用类的封装、赋值
- C++访问限定符