Java集合
Collection
List、Set都是单列数据的集合,Map是存储键值(key:value)的双列数据集合
1.集合只能存放对象。比如你存入一个int型数据66放入集合中,其实它是自动转换成Integer类后存入的(自动装箱),Java中每一种基本数据类型都有对应的引用类型。
2.集合存放的都是对象的引用,而非对象本身。所以我们称集合中的对象就是集合中对象的引用。对象本身还是放在堆内存中。
3.集合可以存放不同类型,不限数量的数据类型。
https://www.cnblogs.com/lixiansheng/p/11348050.html
极客学院wiki:
http://wiki.jikexueyuan.com/project/java-collection/
List接口
特征:有序,可重复
以下是实现类:
底层数据结构 | 线程安全 | |
---|---|---|
ArrayList | 数组 | 不安全 |
Vector | 数组 | 安全 |
LinkedList | 循环双向链表 | 不安全 |
ArrayList
-
动态数组,查找效率高
-
怎么动态扩容的?add(E e)方法里会先判段实际元素个数+1后的值与当前容量谁大,后者大就进入grow()方法,用 Arrays.copyOf(elementData, newCapacity) 扩容,扩容量是50%,oldCapacity>>1, 除以2的操作
https://blog.csdn.net/weixin_39040059/article/details/78995043
- Array(数组)和ArrayList(列表)
Array可以包含基本类型和引用类型,ArrayList只能存放后者
-
线程不安全
多线程下:elementData[size++] = e 设置值的操作同样会导致线程不安全,不是原子操作,多线程下,A线程放入数据,还没执行size++,但是B线程进来了,同样位置放入数据,size++。 https://blog.csdn.net/u012859681/article/details/78206494
添加 synchronized关键字;
使用Collections.synchronizedList(); 此方法适合不需要使用Iterator、对性能要求也不高的情况。 https://www.cnblogs.com/hongdada/p/10931891.html
数组大小固定,列表可以动态扩容
列表方法多,addAll()
LinkedList
循环双向链表,增加/删除效率高
LinkedList还实现了Queue接口
Vector
线程安全,扩容量为100%
Set接口
特征:唯一,由hashCode()方法和equals()方法得到唯一的元素。
是否有序:是否按照元素添加的顺序来存储对象?
以下是实现类:
底层数据结构 | 顺序 | 线程安全 | |
---|---|---|---|
HashSet | 哈希表 | 无序 | 不安全 |
TreeSet | 红黑树 | 自然排序 | |
LinkedHashSet | 链表+哈希表 | FIFO,自然排序 |
HashSet
底层是HashMap,无重复元素,没有顺序,线程不安全,只能有一个Null
在存储元素时,调用HashMap的put()方法前首先会调用hashcode()计算出存储位置,然后调用equals()检查是否有相同元素,如果有就无法存储,所有的Key都有一个默认value(Object对象)。
https://blog.csdn.net/qq_32575047/article/details/78901492
TreeSet
是基于TreeMap实现的,无重复,写入的顺序与实际存储(会排序)不相同
TreeSet的排序方法:1.自然排序(自定义对象时重写) 2.比较器排序
LinkedHashSet
根据元素的hashCode值来决定元素的存储位置,但是它同时使用链表维护元素的次序。 也就是插入和实际排序一致。
Queue
Map
顺序 | 效率 | 线程安全 | 允许Null | |
---|---|---|---|---|
TreeMap | 有序 | |||
HashMap | 无序 | 高 | 不安全 | 允许 |
HashTable | 无序 | 低 | 安全 | 不允许 |
TreeMap:红黑树,是否允许为null要看情况,如下图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SYB9mKWQ-1575097689103)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\1569388283197.png)]
除了Vector和HashTable是线程安全,其他都不是
TreeMap
HashMap
HashMap实际上是一个“链表散列”的数据结构,用链地址法(还有二次探测法,线性探测法)解决碰撞( 根据同一散列函数计算出的散列值相同的现象 ),即数组和链表的结合体。 用来存储Key-Value键值对的集合。
并发性
低,Collections.sychronizedXXX
中,concurrentHashMap
高且排序,concurrentSkipListMap
变量 | 含义 |
---|---|
size | 当前Map里的KV对个数 |
loadFactor | 加载因子: 表示Hsah表中元素的填满的程度 ;默认0.75。 |
threshold | 临界值=capacity*loadFactor |
capacity | 容量(2的幂),默认初始容量为16。 |
https://mp.weixin.qq.com/s?__biz=MzI3NzE0NjcwMg==&mid=2650121339&idx=1&sn=7b6bfd4b16b65972271cdb929134496b&chksm=f36bb95ac41c304cace48901fc1be5fd6a13825b6443c331182f636997a05c792a82a9cb27aa&scene=21#wechat_redirect
面试题
https://www.jianshu.com/p/939b8a672070
-
如何存储元素?
当我们往 HashMap 中 put 元素的时候,先根据 key 的 hashCode 重新计算 hash 值,根据 hash 值位运算得到这个元素在数组中的位置(即下标,哈希桶),如果数组该位置上已经存放有其他元素了,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。如果数组该位置上没有元素,就直接将该元素放到此数组中的该位置上。
在HashMap里存储自定义对象,如果不重写hashcode()和equals()会直接调用Object类的这俩方法, 比较的是内存地址 。
-
为什么不是线程安全?
在扩容操作里,多线程的环境可能存在其他元素也在put操作,如果hash值相同,可能出现同一位置下用环形链表表示,造成闭环,导致get操作死循环。
具体就是 线程a 一个节点插入完成后,还没有执行 e = next ,线程 b 执行 next = e.next , 线程a此时执行 e = next,此时就变成刚刚插入节点的下一个节点了,然后调用 e.next = newTable[i] , 这样第二个线程就会变成一个死循环了。
https://mp.weixin.qq.com/s?__biz=MzI2NjA3NTc4Ng==&mid=2652079766&idx=1&sn=879783e0b0ebf11bf1a5767933d4e61f&chksm=f1748d73c6030465fe6b9b3fa7fc816d4704c91bfe46cb287aefccee459153d3287172d91d23&scene=21#wechat_redirect
-
为什么初始容量是16?
太小的话,容易发送扩容会影响性能,所以在使用的时候,要是预知HashMap中需要存放的个数,预设元素的个数能够有效的提高HashMap的性能 ; 太大浪费空间,也影响效率
-
HashMap数组大小为什么是2的幂次方?
&位运算,内存中计算, 该位运算只能用于除数是2的n次方的数的求余
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wQK001dG-1575097689133)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20191104204744968.png)]
是为了减少散列碰撞和避免空间浪费吧,java的散列函数是通过hashcode和length-1计算的,他的容量必须是2的n次方。如果一开始是14二进制1110,最后一位为0,导致散列函数计算后0001,0101等永远不会出现,位置不能存放元素,空间浪费,增加了散列碰撞。散列函数的设计就是围绕减少散列碰撞和使元素分布均匀(整个数组都能存放)而设计的。
在计算数组下标时,是通过hash & (length-1)得到的,把length设计成2^n,相当于取模运算,同时最后一位为1,则位运算的结果最后一位可能为1或0, 保证了散列的均匀,同时也提升了效率
https://www.nowcoder.com/discuss/29000
https://blog.csdn.net/sidihuo/article/details/78489820
-
如何扩容?为什么要扩容?
why?如果我们没有设置初始容量大小,随着元素的不断增加,HashMap会发生多次扩容,而HashMap中的扩容机制决定了每次扩容都需要重建hash表,是非常影响性能的。
-
1.8的改进?
JDK1.8中加入了红黑树是为了防止哈希表碰撞攻击,当链表链长度为8时,及时转成红黑树,提高map的效率。
扩容改进https://www.cnblogs.com/Xieyang-blog/p/8886921.html
-
为什么是链表大小超过8才转换成树?
根据泊松分布计算出桶中元素的个数和概率的对照表, 链表中元素个数为8时的概率已经非常小
HashTable
Key/Value都不能为null
concurrentHaspMap
面试题
-
如何为集合排序?
如果是基础数据类型和String,直接调用CollectionUtils.sort(x)方法;自定义对象则需要实现Compareable接口,然后重写compareTo()方法