1、集合框架:用于存储数据的容器。任何集合框架都包含三大块内容,对外的接口,接口的实现和对集合运算的算法。
2、集合的特点:
- 对象封装数据,对象多了也需要存储,集合用户存储对象
- 对象的个数确定可以使用数组,对象的个数不确定的可使用集合,因为集合是可变长度的。
3、集合和数组的区别:
- 数组是固定长度的,集合是可变长度的
- 数组可以存储基本数据类型,也可以存储引用数据类型,集合只能存储引用数据类型。
- 数组存储的元素必须是同一种数据类型,集合存储的队形可以使不同数据类型。
4、使用集合框架的好处:
- 容器自增长,
- 提供了高性能的数据接构和算法,使编码更轻松,提高了程序的速度和质量;
- 允许不同API之间的互操作,API之间可以来回传递集合;
- 可以方便的扩展或改写集合,提高代码复用性和可操作性
- 通过使用JDK自带的集合类,可以降低代码维护和学习成本
5、常用的集合类有哪些
- Collection接口的子接口包括:set接口和list接口
- Map接口的实现类主要有:HashMap,TreeMap,Hashtable,ConcurrentHashMap以及Properties
- Set接口的实现类:HashSet,TreeSet,LinkedHashSet
- List接口的实现类主要有:ArrayList,LinkedList,Stach以及Vector等
6、Collection集合主要有List和Set两大接口
- List:有序容器,元素可以重复,可以插入多个null元素,元素都有引用。常用的有ArrayList(Object数组)、LinkedList(双向循环链表)和Vector(Object数组)
- Set:无需容器,元素不可以重复,只允许存在一个null元素,元素唯一性。Set接口常见实现类HashSet(基于HashMap),LinkedHashSet(基于HashSet)以及TreeSet(有序,唯一,红黑树)
7、Map
- HashMap: JDK1.8之前HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突).JDK1.8以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间
- LinkedHashMap:LinkedHashMap 继承自 HashMap,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。
- HashTable: 数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的
- TreeMap: 红黑树(自平衡的排序二叉树)
8、线程安全的容器
- vector:比ArrayList多个同步化机制(线程安全),效率比较低,前端需要优先考虑页面响应速度,因此不建议使用
- stack:堆栈类,先进后出
- hashtable:就比hashMap多个线程安全。
- enumeration:枚举,相当于迭代器
9、Java快速失败机制
- 例如,假设存在两个线程(线程A,线程B)线程A通过迭代器Iterator在便利集合A中的元素,在某个时间线程B修改了集合A的结构,那么
- 就会抛出 ConcurrentModificationException 异常,从而产生fail-fast机制。
- 原因:迭代器在遍历的时候直接访问集合中的内容,并且在遍历过程重使用一个modCount变量,集合在被遍历期间如果内容发生变化,就会
- 改变modCount的值,每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount是否为expectedmodCount值,是的话就返回遍历,否则抛出异常
- 终止遍历
- 解决方法
- 在遍历过程中,所有涉及到改变modCount值的地方全部加上synchronized.
- 使用CopyOnWriteArrayList来替换ArrayList
10、创建只读集合
- Collection<String> clist = Collections. unmodifiableCollection(list);
11、迭代器Iterator
- Iterator 接口提供遍历任何 Collection 的接口,特点是单向遍历,更加安全,并能确保在当前遍历元素被修改的时候,会抛出ConcurrentModificationException 异常
- 可以通过迭代器移除Collection集合中的元素
Iterator<Integer> it = list.iterator();
while(it.hasNext()){
*// do something*
it.remove();
}
一种最常见的错误代码如下:
for(Integer i : list){
list.remove(i)
}
12、Iterator和ListIterator有什么区别
- Iterator可以遍历Set和List集合,而ListIterator只能遍历List
- Iterator只能单向遍历,而ListIterator可以双向遍历
- ListIterator实现Iterator接口,然后添加了一些额外的功能,比如添加一个元素,替换一个元素,获取前面或后面元素的索引位置。
13、遍历List的方法和原理
- for循环遍历,基于计数器。在集合外部维护一个计数器,然后依次读取每一个位置的元素,当读取到最后一个元素后停止。
- 迭代器遍历,Iterator。Iterator是面向对象的一个设计模式,目的是屏蔽不同数据集合的特点,统一遍历集合的接口
- foreach循环遍历,也是采用了Iterator方式。使用时不需要显示声明Iterator和计数器,优点是代码简洁,不易出错。缺点是只能做简单的遍历。不能再遍历过程中操作数据集合
- 注意:Java Collections 框架中提供了一个 RandomAccess 接口,用来标记 List 实现是否支持 Random Access。
- 如果一个数据集合实现了该接口,就意味着它支持 Random Access,按位置读取元素的平均时间复杂度为 O(1),如ArrayList。
- 如果没有实现该接口,表示不支持 Random Access,如LinkedList。
- 推荐的做法就是,支持 Random Access 的列表可用 for 循环遍历,否则建议用 Iterator 或 foreach 遍历。
14、ArrayList的优缺点
- 优点:
- ArrayList底层以数组实现,是一种随机访问模式。实现了ReandomAccess接口,因此查找非常快。
- ArrayList在顺序添加一个元素的时候非常方便。
- 缺点:
- 删除元素的时候,需要做一次元素复制操作。如果要复制的元素很多,那么就会比较耗费性能。
- 插入元素的时候,也需要做一次元素复制操作,缺点同上。
- ArrayList 比较适合顺序添加、随机访问的场景。
15、数组和List之间的转换
- 数组转List:使用Arrays.asList(array)进行转换。
- List转数组:使用List自带的toArray()方法。
16、ArrayList和LinkedList的区别
- 数据结构实现:ArrayList是动态数组的数据结构实现,而LinkedList是双星链表的数据结构实现
- 随机访问效率:ArrayList比Linked在随机访问的时候效率更高,因为LinkedList是线性的数据存储方式,所以需要移动指针从前往后依次查找
- 增加和删除效率:在非首尾的增加和删除操作,LinkedList要比ArrayList效率更高。因为ArrayList增删操作要影响数组内的其他数据的下标。
- 内存空间占用:LinkedList比ArrayList更占内存。因为LinkedList的节点除了存储数据,还存储索引一个指向前一个元素,一个指向后一个元素1
- 线程安全:ArrayList 和 LinkedList 都不保证线程安全
- 在需要频繁读取集合中的元素时,更推荐使用 ArrayList,而在插入和删除操作较多时,更推荐使用 LinkedList
17、ArrayList和Vector的区别
- ArrayList和Vector都实现了List接口,都是有序集合
- 线程安全:Vector使用Synchronized来实现线程同步,是线程安全的,ArrayList是非线程安全的
- 性能:ArrayList在性能方便优于Vector
- 扩容:ArrayList和Vector都会根据实际的需要动态调整容量,只不过Vetor每次扩容都会增加一倍,而ArrayList只会增加50%。
18、阐述 ArrayList、Vector、LinkedList 的存储性能和特性
- ArrayList、LinkedList、Vector底层的实现都是使用数组方式存储数据,数组元素数大于实际存储的数据以便增加和插入元素。它们都允许直接按序号
- 索引元素,但是插入元素要设计数组元素移动等内存操作,所以索引数据快而插入数据慢
- Vector中的方法由于synchronized修饰,因此Vector是线程安全容器,但性能上较ArrayList差。
- LinkedList 使用双向链表实现存储,按序号索引数据需要进行前向或后向遍历,但插入数据时只需要记录当前项的前后项即可,所以 LinkedList 插入速度较快。
19、多线程下使用ArrayList
- ArrayList 不是线程安全的,如果遇到多线程场景,可以通过 Collections 的 synchronizedList 方法将其转换成线程安全的容器后再使用
List<String> synchronizedList = Collections.synchronizedList(list);
synchronizedList.add("aaa");
synchronizedList.add("bbb");
for (int i = 0; i < synchronizedList.size(); i++) {
System.out.println(synchronizedList.get(i));
}
20、List和Set的区别
-
List和Set都继承自Collection接口
-
List特点:有序(元素存入和取出的顺序一致),元素可重复,可以插入多个null元素,元素都有索引,常用的实现类有ArrayList,LinkedList和Vector
-
Set特点:无序(元素存入和取出顺序可能不一致),元素不可以重复,只允许存入一个null元素,元素唯一性,Set接口常用的实现类HashSet,LinkedHashSet以及TreeSet
-
List支持for循环,可以通过下标遍历,可以使用迭代器,set只能用迭代
-
Set:检索元素效率低下,删除和插入效率高,插入和删除不会引起元素位置改变。
List:和数组类似,List可以动态增长,查找元素效率高,插入删除元素效率低,因为会引起其他元素位置改变
21、HashSet实现原理
- HashSet基于HashMap实现的,HashSet的值存放于HashMap的key上,HashMap的value统一为PRESENT
- 向HashSet中添加元素时,判断元素是否存在的依据,不仅要比较hash值,同时还要结合equals方法。
- HashSet 中的add ()方法会使用HashMap 的put()方法。
- HashMap 的 key 是唯一的, HashSet 添加进去的值就是作为HashMap 的key,并且在HashMap中如果K/V相同时, 会用新的V覆盖掉旧的V,然后返回旧的V。所以不会重复。
22、hashCode()与equals()相关规定
- 如果两个对象相等,则hashcode一定也是相同的
- 两个对象相等,对两个equals返回true
- 两个独享有相同的hashcode值,它们也不一定是相等的
- equals方法被覆盖过,则hashcode方法必须被覆盖
- hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据
23、==与equals的区别
- ==是判断两个变量或实例是不是指向同一个内存空间,equals是判断两个变量或实例所指向的内存空间的值是不是相同
- ==是指对内存地址进行比较equals()是对字符串的内容进行比较
- ==指引用是否相同equals()指的值是否相同
24、HashSet与HashMap的区别
25、BlockingQueue
- Java.util.concurrent.BlockingQueue是一个队列,在进行检索或移除一个元素的时候,它会等待队列变为非空;当在添加一个元素时,它会等待队列中的可用空间。BlockingQueue接口是Java集合框架的一部分,主要用于实现生产者-消费者模式。我们不需要担心等待生产者有可用的空间,或消费者有可用的对象,因为它都在BlockingQueue的实现类中被处理了。Java提供了集中BlockingQueue的实现,比如ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue,、SynchronousQueue等
26、Queue 中 poll()和 remove()区别
- 相同点:都是返回第一个元素,并在队列中删除返回的对象
- 不同点:如果没有元素 poll()会返回 null,而 remove()会直接抛出 NoSuchElementException 异常。
27、HashMap的实现原理
- HashMap是基于哈希表的map接口的非同步实现
- HashMap的数据结构: 在Java编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体
28、哈希冲突和解决
- 当两个不同的输入值,根据同一散列函数计算出相同的散列值的现象,我们就把它叫做碰撞(哈希碰撞)
- 解决哈希冲突
- 1. 使用链地址法(使用散列表)来链接拥有相同hash值的数据;
2. 使用2次扰动函数(hash函数)来降低哈希冲突的概率,使得数据分布更平均;
3. 引入红黑树进一步降低遍历的时间复杂度,使得遍历更快
29、为什么String、Integer这样的包装类适合作为HashMap的Key?
-
String、Integer等包装类的特性能够保证Hash值的不可更改性和计算准确性,能够有效的减少Hash碰撞的几率
-
都是final类型,即不可变性,保证key的不可更改性,不会存在获取hash值不同的情况
内部已重写了equals()、hashCode()等方法,遵守了HashMap内部的规范(不清楚可以去上面看看putValue的过程),不容易出现Hash值计算错误的情况;
30、使用Object作为HashMap的key
- 重写hashcode()和equals()方法
- 重写
hashCode()
是因为需要计算存储数据的存储位置,需要注意不要试图从散列码计算中排除掉一个对象的关键部分来提高性能,这样虽然能更快但可能会导致更多的Hash碰撞; - 重写
equals()
方法,需要遵守自反性、对称性、传递性、一致性以及对于任何非null的引用值x,x.equals(null)必须返回false的这几个特性,目的是为了保证key在哈希表中的唯一性;
31、HashMap的长度是2的幂次方
- 为了能让 HashMap 存取高效,尽量较少碰撞,也就是要尽量把数据分配均匀,每个链表/红黑树长度大致相同。
- 这个实现就是把数据存到哪个链表/红黑树中的算法
32、Array 和 ArrayList 有何区别?
- Array 可以存储基本数据类型和对象,ArrayList 只能存储对象。
- Array 是指定固定大小的,而 ArrayList 大小是自动扩展的。
- Array 内置方法没有 ArrayList 多,比如 addAll、removeAll、iteration 等方法只有 ArrayList 有
33、HashMap的扩容操作
- 在jdk1.8中,resize方法是在hashmap中的键值对大于阀值时或者初始化时,就调用resize方法进行扩容;
- 每次扩展的时候,都是扩展2倍;
- .扩展后Node对象的位置要么在原位置,要么移动到原偏移量两倍的位置。
- HashMap的容量范围是在16(初始化默认值)~2 ^ 30