您的位置:首页 > 其它

HashSet,LinkedHashSet的数据存储原理

2017-12-30 21:12 239 查看
HashSet的底层使用的是HashMap结构。
LinkedHashSet是在HashSet的基础上记录数据存序列,以数据存储序列作为数组索引,其他操作与HashSet一致
插入元素方法:add(),调用方法,使用HashMap的map函数,
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
//以键入元素为键,新建一个常量对象作为值(同一个set集合的值都一样)
private static final Object PRESENT = new Object();
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
//如果重复无法输入
存储时HashMap定义初始数组长度为16,当元素个数小于16时,在数组中的位置顺序是由以下表达式求取:
int num = (16-1) & (h = aa[i].hashCode())^ (h >>> 16)
以15和元素的&运算得到的值作为数组的索引值
以数组{12,21,32,56,34,1,3}为例,
求得的索引值为:[12, 5, 0, 8, 2, 1, 3]
将数组按照索引值排列得到的结果为:
[32, 1, 34, 3, null, 21, null, null, 56, null, null, null, 12, null, null, null]
与使用HashSet存储的到的顺序结果一致:
[32, 1, 34, 3, 21, 56, 12]
HashMap的(map方法)存储原理
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
//Node理解为存储信息的类,有属性hash(元素对应的hash值),key(元素键值),value(元素值),next(下一个节点)
Node<K,V>[] tab; Node<K,V> p; int n, i;

//如果为空,数组tab扩容,如果数组已经存满,继续扩容
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;

//求取数组长度与加入值得hash值得与(&)的值,如果数组在求出值作为索引的位置元素为null,将插入值放在此位置。

//将p初始化为当前数组位置对应的Node
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);

//如果对应位置已经有Node,继续判断
else {
Node<K,V> e; K k;

//如果键相等并且hash值也相同取消插入
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);

//如果键值不同,hash相同
else {
for (int binCount = 0; ; ++binCount) {

//如果当前数组的下一个节点为空,直接链式存储,并且将下一个节点赋给e
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向后移动一个节点,继续循环,直到p的下一个节点为空
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;
}
示意图解释:首先创建初始数组,给定一定长度,将元素转换为Node类型的数据,首先存到数组中,如果下一个hash值相同,键值不同,在对应的Node后面横向存储,结构为链表。当数组长度不足时,进行扩容处理。



删除元素:
删除元素,如果集合中有元素,返回True,否则返回False。调用如下方法
public boolean remove(Object o) {
return map.remove(o)==PRESENT;
}
同样将待删除的元素转换成对应hash值。
public V remove(Object key) {
Node<K,V> e;
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}
通过hash和键值遍历数组,查找到后,直接将数组值赋值null
final Node<K,V> removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable) {
Node<K,V>[] tab; Node<K,V> p; int n, index;
if ((tab = table) != null && (n = tab.length) > 0 &&
//首先将数组中的值赋给p
(p = tab[index = (n - 1) & hash]) != null) {
Node<K,V> node = null, e; K k; V v;
//如果hash值和键值匹配,要删除的节点node就是p
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
node = p;
else if ((e = p.next) != null) {
if (p instanceof TreeNode)
node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
else {

// 如果hash值相同,键值不同,遍历链表,查找元素,直到找出键值相等的元素,将节点赋值给node
do {
if (e.hash == hash &&
((k = e.key) == key ||
(key != null && key.equals(k)))) {
node = e;
break;
}
p = e;
} while ((e = e.next) != null);
}
}
if (node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v)))) {
if (node instanceof TreeNode)
((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
else if (node == p)

//删除数组中的节点
tab[index] = node.next;
else

//删除链表中的节点
p.next = node.next;
++modCount;
--size;
afterNodeRemoval(node);
return node;
}
}
return null;
}
查找的原理与删除一致,将node对应的键值返回即可
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息