HashSet中的add方法
源代码如下:
由源代码(HashSet类中add方法)可以看出PRESENT是常量类型(由fina修饰,全部大写),map是HashMap类型的:public boolean add(E e) { return map.put(e, PRESENT)==null; }
private transient HashMap<E,Object> map; private static final Object PRESENT = new Object(); public HashSet() { map = new HashMap<>(); }
public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted }
执行代码:
HashSet<String> set = new HashSet<>(); set.add("Tom");
执行第一行代码时,调用了无参构造方法---->上面的HashSet()HashMap()
执行第二行代码时,调用add方法存储数据。证明了HashSet存储的数据实质存储的是HashMap中key的值。HashSet存储的值不允许重复实际上是因为HashMap中key的值不允许重复。
hash方法中不同数据类型的参数的区别:
hashSet中add方法源码:
public boolean add(E e) { return map.put(e, PRESENT)==null; }
hashMap中put方法源码:
public V put(K key, V value) { return putVal(hash(key), key, value, false, true); }
hashMap中hash方法
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
一层层剖析,可知:添加的Tom分别传到了参数 E e、K key、hash(key)中。
引用类型
将String类型的值传到参数中,代码如下:
import java.util.HashSet; public class Test { static int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); } public static void main(String[] args) { HashSet<String> set = new HashSet<String>(); // hashSet存储的数据实质上存储了hashMap的key set.add("Tom"); int hash = hash("Tom"); System.out.println(hash); hash = hash(new String("Tom")); System.out.println(hash); } }
运行结果:84275
84275结果说明:字符串类型的值传给参数(Object key)中,实际上是一个上转型是对象。h = key.hashCode()) 实际上运行的是String类中的hashCode,源代码如下:
public int hashCode() { int h = hash; if (h == 0 && value.length > 0) { char val[] = value; for (int i = 0; i < value.length; i++) { h = 31 * h + val[i]; } hash = h; } return h; }
由代码可知:字符串的内容将其变成字符数组,然后遍历将其换成数值。String类型如果内容相同,则返回值相同
基本数据类型包装类
将Integer类型的值传到参数中,代码如下:
hash = hash(10000); System.out.println(hash); hash = hash(new Integer(10000)); System.out.println(hash);
运行结果:10000
10000结果说明:字符串类型的值传给参数(Object key)中,实际上是一个上转型是对象。h = key.hashCode()) 实际上运行的是Integer类中的hashCode,源代码如下:
@Override public int hashCode() { return Integer.hashCode(value); }
代码说明:该无参hashCode()方法调用了静态有参方法hashCode(value):
public static int hashCode(int value) { return value; }
由代码可知:基本数据类型包装类,如果值相同,则返回值相同
自定义类型
hash = hash(new Student()); System.out.println(hash); hash = hash(new Student()); System.out.println(hash);
运行结果:5433712
2430314结果说明:因为新建的学生类中没有hashCode()方法, 因此会调用继承的Object类中的hashCode()方法:---------->public native int hashCode();--------->native:原生的,调用的c语言 中的方法。自定义类型如果地址不同,则返回结果不同
hashSet中值是如何存储的(底层源码)
hashSet中add方法源码:
public boolean add(E e) { return map.put(e, PRESENT)==null; }
hashMap中put方法源码:
public V put(K key, V value) { return putVal(hash(key), key, value, false, true); }
hashMap中putVal方法源码:
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; }
执行下列代码:
HashSet<String> set = new HashSet<String>(); set.add("Tom"); set.add("Tom"); set.add("Sun");
1.参数列表中hash值是hash(key)方法中key调用的hashCode。
2.为什么成员变量table为null?因为当HashMap对象创建后,默认值是null(transient Node<K,V>[] table);,且在之后的的所有构造方法中未给table赋值,因此table为null;此前创建的全局变量table未赋值,因此为null( transient Node<K,V>[] table;),因此(tab = table) == null 成立,继续执行下一条语句;
3.值不允许重复:
(1)执行第一个set.add("Tom");添加第一个数据时,此时hash即为"Tom","i = (n - 1) & hash"表示i为n-1按位与hash的随机的一个角标,由于tab中所有元素都为null,不可能和hash相同,因此(p = tab[i = (n - 1) & hash]) == null成立,继续执行下一条语句; 将数据存入tab[i]中,且由于tab和table指向一个地址,因此数据也存入到了table中;
(2)执行第二个set.add("Tom");添加第二个数据时,执行语句:if ((tab = table) == null || (n = tab.length) == 0),由于上一步执行过程中tab数组中存入了第一个"Tom"数据,则(tab = table)不为null,且(n = tab.length)必然不为零,所以if条件不成立,直接执行下一个if语句:------>if ((p = tab[i = (n - 1) & hash]) == null),该条件中hash为第一个"Tom"存储的位置这个"i"与上一步执行过程的"i"相同,即tab[i]已经有了"Tom"数据,因此p= tab[i = (n - 1) & hash])不为null,执行else语句;------>if (p.hash == hash && ((k = p.) == key || (key != null && key.equals(k)))),由于p.hash=tab[i]存储"Tom"数据,因此p.hash=hash;又因为p.key为String类型"Tom",与key相同,因此if条件成立,执行下一条语句;------>然后返回原值,即"Tom"数据。因此添加第二个数据失败
(3)执行第二个set.add("Sun");添加第二个数据时,执行语句:if ((tab = table) == null || (n = tab.length) == 0),由于此前执行过程中tab数组中存入了第一个"Tom"数据,则(tab = table)不为null,且(n = tab.length)必然不为零,所以if条件不成立,直接执行下一个if语句-------->if ((p = tab[i = (n - 1) & hash]) == null),该hash中存储的int类型"Lucy"随机生成了在n-1中的一个i值,且由于该hash存储的数据与此前存储"Tom"数据的hash不同,因此该i与此前的i不同,该tab[i]为null,因此p = tab[i = (n - 1) & hash]为null,执行下一个语句;-------> tab[i] = newNode(hash, key, value, null); 存入"Lucy"数据;