集合
集合是日常使用和面试当中占比非常大的一块,所以将一些面试遇到的问题和学习的经验总结在此:
为什么要用集合?集合和数组的区别是什么?
在我们需要保存一组数据时,我们需要一个容器,一般情况下这个容器是数组;但是数组本身有一些弊端,如:
1、数组是静态的,一个数组的实例具有固定的大小,无法改变容量;
2、数组是结构固定的,可重复的,使用不够灵活、方便;
3、声明数组时固定了存储的类型;
为了方便开发,我们使用集合来存储数据,集合有如下特点:
1、长度动态扩展;
2、结构不固定(list,set,map),各有不同的特点,使用方便;
3、可以不规定存储的类型;
4、可以保存映射关系的数据;
5、速度没有数组快(?待测试)
集合的分类:
List:存储的数据是可重复的和有序的
Arraylist: Object[] 数组
Vector:Object[] 数组
LinkedList: 双向链表(JDK1.6 之前为循环链表,JDK1.7 取消了循环)
Set:存储的数据是不可重复的和无序的
HashSet(无序,唯一): 基于 HashMap 实现的,底层采用 HashMap 来保存元素
LinkedHashSet: LinkedHashSet 是 HashSet 的子类,并且其内部是通过 LinkedHashMap 来实现的。有点类似于我们之前说的 LinkedHashMap 其内部是基于 HashMap 实现一样,不过还是有一点点区别的
TreeSet(有序,唯一): 红黑树(自平衡的排序二叉树)
Queue:按照特定的排队规则确定先后顺序,先进先出等等;存储的数据是可重复的和有序的
PriorityQueue: Object[] 数组来实现二叉堆
ArrayQueue: Object[] 数组 + 双指针
再来看看 Map 接口下面的集合。
Map:用于存储映射关系的数据
HashMap: JDK1.8 之前 HashMap 由数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突)。JDK1.8 以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间
LinkedHashMap: LinkedHashMap 继承自 HashMap,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。详细可以查看:《LinkedHashMap 源码详细分析(JDK1.8)》
Hashtable: 数组+链表组成的,数组是 Hashtable 的主体,链表则是主要为了解决哈希冲突而存在的
TreeMap: 红黑树(自平衡的排序二叉树)
List:存储的数据是可重复的和有序的
ArrayList:
1、存在于java.util包中。
2、内部用数组存放数据,封装了数组的操作,每个对象都有下标。
3、内部数组默认初始容量是10。如果不够会以1.5倍容量增长。
4、查询快,增删数据效率会降低。
5、底层是Object数组,属于线性结构。
6、线程不安全。
LinkedList:
1、存在于java.util包中。
2、底层使用的是 双向链表 数据结构。
3、内存占用大,需要多存储后驱或前驱;
4、增删快,在头尾插入和删除的时间复杂度是O(1),其他位置的复杂度是O(n),查询速度慢;
5、线程不安全。
Vector:
1、存在于java.util包中。
2、是List的古老实现类;
3、线程安全,速度慢,基本不使用;
遍历方式:
遍历方式:
准备一个List:
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
list.add(6);
list.add(7);
1、迭代器:
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
2、foreach:
list.forEach(System.out::println);
3、stream:
list.stream().sorted().forEach(System.out::println);
4、for循环:
for (Integer i:
list) {
System.out.println(i);
}
ArrayList和Vector的区别:
ArrayList是List的主要实现类,线程不安全,用于频繁查询的工作,底层是Object[];
Vector是List的古老实现类,线程安全,因为方法都用synchronized修饰,速度慢;
ArrayList和LinkedList的区别:
底层实现: ArrayList底层用Object[]实现,是线性结构;LinkedList底层是双向链表;
是否随机读取: ArrayList有下标支持随机读取,LinkedList不支持;
增删速度: LinkedList用链表实现,增删只需要修改前驱和后继即可,速度更快;ArrayList需要移动位置,速度更慢;
但并非是一定的,ArrayList制定初始容量并使用尾插法,可以极大提升性能(甚至超过LinkedList,维护node);
**线程安全:**都不是线程安全的。
内存空间占用: LinkedList占用空间大,需要多存储后驱或前驱;
如何解决List的多线程不安全问题:
在多线程情况下,ArrayList和LinkedList都会出现线程不安全的问题,我知道以下几个解决办法:
CopyOnWriteArrayList
cow机制,jdk1.5后引入,写时复制(添加元素时不直接往容器中添加,现将当前容器复制一份,再将元素添加到新容器,最后将原容器的引用指向新容器),有点副本的意味
线程安全,适合读>写
缺点也比较明显,第一是内存占用,可能导致yong gc或full gc;第二是无法保证数据的实时一致性,只能保存数据的最终一致性。
使用Vector 不推荐
使用Collections.synchronized系列方法进行强同步
LinkedList<Integer> list = new LinkedList<>();
List<Integer> integers = Collections.synchronizedList(list);
和Vector相比那个速度快有待测试;
Set:存储是无序且不可重复的
无序不代表随机,而是根据哈希值来确定位置;
HashSet:
1、是Set接口的实现类;
2、底层实现是哈希表,实际上是HashMap,相当于向HashSet中存入数据时,会把数据作为K,存入内部的HashMap中。当然K仍然不许重复。
3、元素无序且唯一;
4、线程不安全,效率高,可以存储null元素;
5、元素的唯一性靠所存储元素类型是否重写了hashcode()和equals()方法来保证;
6、默认初始化容量16,加载因子0.75;
LinkedHashSet :
1、是HashSet的子类;
2、哈希表和链表实现;
3、链表保证了元素的顺序与存储顺序一致,哈希表保证了元素的唯一性;
4、线程不安全,效率高,可以存储null元素;
TreeSet:
1、底层实现是TreeMap,即红黑树;
2、元素是唯一且已经排好序的;
HashSet、LinkedHashSet 、TreeSet 的区别:
HashSet是Set实现类,存储没有顺序且不能重复,可以存储null;
LinkedHashSet 是HashSet的子类,可以保证有序的存储;
TreeSet底层是红黑树,元素有序,排序方式有自然排序和定制排序;
遍历:参考List即可
HashSet查重:
当你把对象加入HashSet时,HashSet 会先计算对象的hashcode值来判断对象加入的位置,同时也会与其他加入的对象的 hashcode 值作比较,如果没有相符的 hashcode,HashSet 会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用equals()方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让加入操作成功。
Map:用于存储映射关系的数据
K - 此映射所维护的键的类型----V - 映射值的类型,其中的键不能重复,值可以重复;
HashMap:
1、基于Hash表的Map实现类;
2、K、V可以为Null;
3、初始容量是16,加载因子1;
4、线程不安全效率高;
5、底层是链表实现,当链表长度大于阈值(默认8)转换成红黑树;
6、是HashSet的底层实现;
7、给定初始值会扩充为2的幂次;
TreeMap:
1、底层是红黑树;
2、根据构造函数的不同进行自然排序或自定义排序;
3、线程不安全;
HashTable:
1、线程安全,效率低,不要在代码中使用;
2、不能存储Null;
3、默认大小是11,每次扩容后变成原来的2n+1;
4、底层是链表;
5、可以给定初始值(长度);
HashMap和HashTable的区别:
HashMap线程不安全,速度快,可以存储Null;HashTable相反;
HashMap底层使用红黑树,HashTable没有;
遍历方式:
https://mp.weixin.qq.com/s/zQBN3UvJDhRTKP6SzcZFKw
如何解决HashMap线程不安全的问题:
ConcurrentHashMap,符合与Hashtable相同的功能规范,并包括与Hashtable每种方法对应的方法 。