关于HashSet的有序无序问题

Surrin1999 2018-08-23 11:29:06

package hashset.train;

import java.util.HashSet;
//import java.util.Random;
import java.util.Set;


public class HashSetDemo {
public static void main(String[] args) {
Set<Integer> hs =new HashSet<Integer>();

// Random rand=new Random();

hs.add(6);
hs.add(100);
hs.add(150);
hs.add(240);
hs.add(330);
hs.add(520); // 乱序 在table中位置是根据hash算法计算得出的 但只是会影响存储位置 不是一定会使元素变得自然排序(像依次添加那样 估计与table的长度的增加有关) 但是若用随机数添加又符合规律?

// for (int i=0;i<5000;i++){
// hs.add(rand.nextInt(1000));
// }

// for (int i=0;i<1000;i++) {
// System.out.println(i);
// }

for (int i : hs) {
System.out.println(i);
}
}
}


当HashSet像如今这样添加的话 输出是无序的 但当放在21~23行的随机注释 添加元素改为添加1000以内随机数 随机添加就会发现是自然排序的,,25~27行那样直接顺序添加也是一样自然排序的 这是为什么呢 之前翻阅资料和源码知道tHashMap 的table数组是根据hashcode的数值计算元素在table中的下标的 这也就间接导致了数据值会影响其在table中的位置 也知道[[0,2^32-1]内返回的都是整型值本身

如https://blog.csdn.net/tzhuwb/article/details/77757754
https://www.cnblogs.com/-jiang/p/5516973.html 所示

但那些资料举的例子都是随机添加或例子数据刚刚好符合的, 当我的例子这样时 本应该自然排序的为什么没有排序呢?
...全文
1140 23 打赏 收藏 转发到动态 举报
写回复
用AI写文章
23 条回复
切换为时间正序
请发表友善的回复…
发表回复
赵4老师 2018-08-28
  • 打赏
  • 举报
回复
理解讨论之前请先学会如何观察
梨花剑君 2018-08-28
  • 打赏
  • 举报
回复
是在构造方法里的另一个方法里
啊大1号 2018-08-27
  • 打赏
  • 举报
回复
源码之前,了无秘密
liulilittle 2018-08-27
  • 打赏
  • 举报
回复
HashSet 是无法自动排序的哈,它是由元素之间在插入时,hash 计算的 bucket 只要不具有冲突就直接往这个 map 地址插入其值否则放大关键链插入其值。
在迭代的时候是以当前 HashSet 容器包含可见有效的元素,这是一个无序的 bucket 关链。
这名字可真靓 2018-08-25
  • 打赏
  • 举报
回复
为什么我的显示结果是只有1-15是有序的.大于15虽然相邻的数字大小差不多,但是并不是有序的.也不是真正的随机,同样jdk1.8
Surrin1999 2018-08-25
  • 打赏
  • 举报
回复
好的 谢谢各位 我已经知道了 这个算法其实就是取容量最接近的(向上取)二次幂
licjd 2018-08-25
  • 打赏
  • 举报
回复
引用 16 楼 Surrin1999 的回复:
呃 好像是在构造方法里的另一个方法里


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;
}


不过不太看得懂


这个有点麻烦,虽然听过一次,但好像我自己推不出来,晚上试试...
上面代码最后的结果是2^n
一句结论:如果指定初始容量为X,并且X~(2^(n-1),2^n],最终容量肯定为2^n
Surrin1999 2018-08-24
  • 打赏
  • 举报
回复
引用 10 楼 nayi_224 的回复:
[quote=引用 9 楼 Surrin1999 的回复:]
[quote=引用 5 楼 nayi_224 的回复:]
跟随机数没关系。

HashSet里面是HashMap,HashMap里面是数组。如果新值的hash值小于数组最大下标,会正好添加到这个下标处。而数字的hash值就是它自己。
如果hash值大于下标最大值,会有别的算法来做添加(记得是取余数)。比如对 new HashSet(4) 添加5,它会加到下标1的位置。这个时候再添加1,它会在下标1处向后添加链表。当继续添加值以致大于0.75时,会触发HashMap的resize()方法。基本就是新生成一个1.5倍长度的新数组,将原来的值重新add一遍。
for-each循环就是把这个数组顺序输出。
也就是说,你这个只是巧合而已。

举个更精辟的例子
		HashSet<Integer> hs = new HashSet<Integer>(4);

hs.add(3);
hs.add(5);

无序

		HashSet<Integer> hs = new HashSet<Integer>(4);

hs.add(3);
hs.add(5);
hs.add(1);
hs.add(2);

有序


您这个解释是根据JDK7的数组+链表来讲的吗 以前的还能看懂 现在的JDK8写的好复杂 以及能说说您当时是怎么学习知道的吗 文档英文看的头皮发麻看不下去[/quote]

1.8依然有数组+链表,红黑树只是一个补充。上面这些个例子正好不会触发红黑树的代码。
也没什么学习经历,我也是今天看了你的问题才第一次打开jdk1.8HashSet的源码。跟了断点之后发现跟老版的套路一样罢了。
全面的了解一套代码真的很令人羡慕,但是时间太有限了,只关注当前用上的代码也不失为一种好方法。[/quote]

您是根据这个来看的吧 能给一些关键注释吗 我刚开始学习集合框架 老版的还行 新版的看不懂。。


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;
}
Surrin1999 2018-08-24
  • 打赏
  • 举报
回复
呃 好像是在构造方法里的另一个方法里


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;
}


不过不太看得懂
Surrin1999 2018-08-24
  • 打赏
  • 举报
回复
引用 10 楼 nayi_224 的回复:
[quote=引用 9 楼 Surrin1999 的回复:]
[quote=引用 5 楼 nayi_224 的回复:]
跟随机数没关系。

HashSet里面是HashMap,HashMap里面是数组。如果新值的hash值小于数组最大下标,会正好添加到这个下标处。而数字的hash值就是它自己。
如果hash值大于下标最大值,会有别的算法来做添加(记得是取余数)。比如对 new HashSet(4) 添加5,它会加到下标1的位置。这个时候再添加1,它会在下标1处向后添加链表。当继续添加值以致大于0.75时,会触发HashMap的resize()方法。基本就是新生成一个1.5倍长度的新数组,将原来的值重新add一遍。
for-each循环就是把这个数组顺序输出。
也就是说,你这个只是巧合而已。

举个更精辟的例子
		HashSet<Integer> hs = new HashSet<Integer>(4);

hs.add(3);
hs.add(5);

无序

		HashSet<Integer> hs = new HashSet<Integer>(4);

hs.add(3);
hs.add(5);
hs.add(1);
hs.add(2);

有序


您这个解释是根据JDK7的数组+链表来讲的吗 以前的还能看懂 现在的JDK8写的好复杂 以及能说说您当时是怎么学习知道的吗 文档英文看的头皮发麻看不下去[/quote]

1.8依然有数组+链表,红黑树只是一个补充。上面这些个例子正好不会触发红黑树的代码。
也没什么学习经历,我也是今天看了你的问题才第一次打开jdk1.8HashSet的源码。跟了断点之后发现跟老版的套路一样罢了。
全面的了解一套代码真的很令人羡慕,但是时间太有限了,只关注当前用上的代码也不失为一种好方法。[/quote]

话说 我想问最后一个问题 那个当继续添加值以致大于负载因子0.75时重新造个数组是在哪看到的 是源码中的

if (++size > threshold)
resize();


是这句吗 threshold貌似没有初始值? 我没发现哪里有涉及到负载因子
Surrin1999 2018-08-24
  • 打赏
  • 举报
回复
引用 11 楼 Surrin1999 的回复:
[quote=引用 10 楼 nayi_224 的回复:]
[quote=引用 9 楼 Surrin1999 的回复:]
[quote=引用 5 楼 nayi_224 的回复:]
跟随机数没关系。

HashSet里面是HashMap,HashMap里面是数组。如果新值的hash值小于数组最大下标,会正好添加到这个下标处。而数字的hash值就是它自己。
如果hash值大于下标最大值,会有别的算法来做添加(记得是取余数)。比如对 new HashSet(4) 添加5,它会加到下标1的位置。这个时候再添加1,它会在下标1处向后添加链表。当继续添加值以致大于0.75时,会触发HashMap的resize()方法。基本就是新生成一个1.5倍长度的新数组,将原来的值重新add一遍。
for-each循环就是把这个数组顺序输出。
也就是说,你这个只是巧合而已。

举个更精辟的例子
		HashSet<Integer> hs = new HashSet<Integer>(4);

hs.add(3);
hs.add(5);

无序

		HashSet<Integer> hs = new HashSet<Integer>(4);

hs.add(3);
hs.add(5);
hs.add(1);
hs.add(2);

有序


您这个解释是根据JDK7的数组+链表来讲的吗 以前的还能看懂 现在的JDK8写的好复杂 以及能说说您当时是怎么学习知道的吗 文档英文看的头皮发麻看不下去[/quote]

1.8依然有数组+链表,红黑树只是一个补充。上面这些个例子正好不会触发红黑树的代码。
也没什么学习经历,我也是今天看了你的问题才第一次打开jdk1.8HashSet的源码。跟了断点之后发现跟老版的套路一样罢了。
全面的了解一套代码真的很令人羡慕,但是时间太有限了,只关注当前用上的代码也不失为一种好方法。[/quote]

您是根据这个来看的吧 能给一些关键注释吗 我刚开始学习集合框架 老版的还行 新版的看不懂。。


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;
}
[/quote]

话说 我想问最后一个问题 那个当继续添加值以致大于负载因子0.75时重新造个数组是在哪看到的 是源码中的

if (++size > threshold)
resize();

这句吗 threshold貌似没有初始值? 我没发现哪里有涉及到负载因子
verejava 2018-08-24
  • 打赏
  • 举报
回复
Surrin1999 2018-08-24
  • 打赏
  • 举报
回复
引用 10 楼 nayi_224 的回复:
[quote=引用 9 楼 Surrin1999 的回复:]
[quote=引用 5 楼 nayi_224 的回复:]
跟随机数没关系。

HashSet里面是HashMap,HashMap里面是数组。如果新值的hash值小于数组最大下标,会正好添加到这个下标处。而数字的hash值就是它自己。
如果hash值大于下标最大值,会有别的算法来做添加(记得是取余数)。比如对 new HashSet(4) 添加5,它会加到下标1的位置。这个时候再添加1,它会在下标1处向后添加链表。当继续添加值以致大于0.75时,会触发HashMap的resize()方法。基本就是新生成一个1.5倍长度的新数组,将原来的值重新add一遍。
for-each循环就是把这个数组顺序输出。
也就是说,你这个只是巧合而已。

举个更精辟的例子
		HashSet<Integer> hs = new HashSet<Integer>(4);

hs.add(3);
hs.add(5);

无序

		HashSet<Integer> hs = new HashSet<Integer>(4);

hs.add(3);
hs.add(5);
hs.add(1);
hs.add(2);

有序


您这个解释是根据JDK7的数组+链表来讲的吗 以前的还能看懂 现在的JDK8写的好复杂 以及能说说您当时是怎么学习知道的吗 文档英文看的头皮发麻看不下去[/quote]

1.8依然有数组+链表,红黑树只是一个补充。上面这些个例子正好不会触发红黑树的代码。
也没什么学习经历,我也是今天看了你的问题才第一次打开jdk1.8HashSet的源码。跟了断点之后发现跟老版的套路一样罢了。
全面的了解一套代码真的很令人羡慕,但是时间太有限了,只关注当前用上的代码也不失为一种好方法。[/quote]

不用了不用了 我自己看懂了 那个node就是个链表。。Node [ ]就是这个数组 当bigCount等于7也就是链表长度8,将链表转化为红黑树存储 。。
Surrin1999 2018-08-23
  • 打赏
  • 举报
回复
另外 我用的是JDK8
nayi_224 2018-08-23
  • 打赏
  • 举报
回复
引用 9 楼 Surrin1999 的回复:
[quote=引用 5 楼 nayi_224 的回复:]
跟随机数没关系。

HashSet里面是HashMap,HashMap里面是数组。如果新值的hash值小于数组最大下标,会正好添加到这个下标处。而数字的hash值就是它自己。
如果hash值大于下标最大值,会有别的算法来做添加(记得是取余数)。比如对 new HashSet(4) 添加5,它会加到下标1的位置。这个时候再添加1,它会在下标1处向后添加链表。当继续添加值以致大于0.75时,会触发HashMap的resize()方法。基本就是新生成一个1.5倍长度的新数组,将原来的值重新add一遍。
for-each循环就是把这个数组顺序输出。
也就是说,你这个只是巧合而已。

举个更精辟的例子
		HashSet<Integer> hs = new HashSet<Integer>(4);

hs.add(3);
hs.add(5);

无序

		HashSet<Integer> hs = new HashSet<Integer>(4);

hs.add(3);
hs.add(5);
hs.add(1);
hs.add(2);

有序


您这个解释是根据JDK7的数组+链表来讲的吗 以前的还能看懂 现在的JDK8写的好复杂 以及能说说您当时是怎么学习知道的吗 文档英文看的头皮发麻看不下去[/quote]

1.8依然有数组+链表,红黑树只是一个补充。上面这些个例子正好不会触发红黑树的代码。
也没什么学习经历,我也是今天看了你的问题才第一次打开jdk1.8HashSet的源码。跟了断点之后发现跟老版的套路一样罢了。
全面的了解一套代码真的很令人羡慕,但是时间太有限了,只关注当前用上的代码也不失为一种好方法。
Surrin1999 2018-08-23
  • 打赏
  • 举报
回复
引用 5 楼 nayi_224 的回复:
跟随机数没关系。

HashSet里面是HashMap,HashMap里面是数组。如果新值的hash值小于数组最大下标,会正好添加到这个下标处。而数字的hash值就是它自己。
如果hash值大于下标最大值,会有别的算法来做添加(记得是取余数)。比如对 new HashSet(4) 添加5,它会加到下标1的位置。这个时候再添加1,它会在下标1处向后添加链表。当继续添加值以致大于0.75时,会触发HashMap的resize()方法。基本就是新生成一个1.5倍长度的新数组,将原来的值重新add一遍。
for-each循环就是把这个数组顺序输出。
也就是说,你这个只是巧合而已。

举个更精辟的例子
		HashSet<Integer> hs = new HashSet<Integer>(4);

hs.add(3);
hs.add(5);

无序

		HashSet<Integer> hs = new HashSet<Integer>(4);

hs.add(3);
hs.add(5);
hs.add(1);
hs.add(2);

有序


您这个解释是根据JDK7的数组+链表来讲的吗 以前的还能看懂 现在的JDK8写的好复杂 以及能说说您当时是怎么学习知道的吗 文档英文看的头皮发麻看不下去
Surrin1999 2018-08-23
  • 打赏
  • 举报
回复
引用 6 楼 nayi_224 的回复:
		HashSet<Integer> hs = new HashSet<Integer>(4);

hs.add(3);

hs.add(9);
hs.add(1);


hs.add(2);


		HashSet<Integer> hs = new HashSet<Integer>(4);

hs.add(3);


hs.add(1);
hs.add(9);

hs.add(2);


要是能看懂1和9的顺序为什么会变,估计你也就懂了。


卧槽 卧槽 卧槽 你也太特么猛了兄弟 我懂了 你咋知道的啊??? 现在JDK8底层数据结构变成了数组+链表+红黑树 看得头都晕了
maradona1984 2018-08-23
  • 打赏
  • 举报
回复
引用 4 楼 Surrin1999 的回复:
[quote=引用 3 楼 maradona1984 的回复:]
[quote=引用 2 楼 maradona1984 的回复:]
或许你可以尝试把注释的那段代码的循环次数改成100试试
hashset是基于hashmap的,5000次随机很大概率使得set的size扩容到大于1000,set的size也是影响hash方法的元素之一

set的size应该会影响key落在数组上的位置[/quote]

改为100后 确实是无序了 那么从源码的角度有序无序到底是怎么回事呢? 比如如果你for循环依次添加1到1000 最后结果肯定是自然排序的 因为table数组位置是由hashcode计算 而hashcode又是数值本身 但随意添加无序数 如我这个例子 最后结果是无序的[/quote]
因为你随机5000次,随机数范围是1000,基本上等同于for循环1000了,虽然put的顺序不一样

但hashset的无序不能这么理解,只能理解为你不能指定元素顺序,treeset能指定元素顺序
nayi_224 2018-08-23
  • 打赏
  • 举报
回复
		HashSet<Integer> hs = new HashSet<Integer>(4);

hs.add(3);

hs.add(9);
hs.add(1);


hs.add(2);


		HashSet<Integer> hs = new HashSet<Integer>(4);

hs.add(3);


hs.add(1);
hs.add(9);

hs.add(2);


要是能看懂1和9的顺序为什么会变,估计你也就懂了。
nayi_224 2018-08-23
  • 打赏
  • 举报
回复
跟随机数没关系。

HashSet里面是HashMap,HashMap里面是数组。如果新值的hash值小于数组最大下标,会正好添加到这个下标处。而数字的hash值就是它自己。
如果hash值大于下标最大值,会有别的算法来做添加(记得是取余数)。比如对 new HashSet(4) 添加5,它会加到下标1的位置。这个时候再添加1,它会在下标1处向后添加链表。当继续添加值以致大于0.75时,会触发HashMap的resize()方法。基本就是新生成一个1.5倍长度的新数组,将原来的值重新add一遍。
for-each循环就是把这个数组顺序输出。
也就是说,你这个只是巧合而已。

举个更精辟的例子
		HashSet<Integer> hs = new HashSet<Integer>(4);

hs.add(3);
hs.add(5);

无序

		HashSet<Integer> hs = new HashSet<Integer>(4);

hs.add(3);
hs.add(5);
hs.add(1);
hs.add(2);

有序
加载更多回复(3)

62,614

社区成员

发帖
与我相关
我的任务
社区描述
Java 2 Standard Edition
社区管理员
  • Java SE
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧