集合的特点
集合的特点主要有如下两点:
①对象封装数据,对象多了也需要存储。集合用于存储对象
②对象的个数确定可以使用数组,对象的个数不确定的可以用集合。因为集合是可变长度的
集合和数组的区别
①数组是固定长度的,集合可变长度的
②数组可以存储基本数据类型,也可以存储引用数据类型,集合只能存储引用数据类型
③数组存储的元素必须是同一个数据类型,集合存储的对象可以是不同数据类型
Iterator接口
Iterator接口,用于遍历集合元素的接口。
在Iterator接口中定义了三个方法:
Iterator接口:统一了遍历集合的方式(每个集合都有自己的数据结构),使得对容器的遍历操作与其具体的底层实现相隔离,达到解耦的效果
使用:
①集合实现类对象通过 .iterator() 的方式获取迭代器
②迭代器对象通过 .hasNext() 的方式判断是否有下一个元素
③迭代器对象通过 .next() 的方式返回下一个元素
ListIterator接口
概念:
ListIterator是一个功能更加强大的迭代器, 它继承于Iterator接口,只能用于各种List类型的访问。可以通过调用listIterator()方法产生一个指向List开始处的ListIterator
还可以调用listIterator(n)方法创建一个一开始就指向列表索引为n的元素处的ListIterator
特点:
①允许我们向前、向后两个方向遍历 List
②在遍历时修改 List 的元素
③遍历时获取迭代器当前游标所在位置
常用API:
Iterator 和 ListIterator 有什么区别?
①Iterator 可以遍历 Set 和 List 集合,而 ListIterator 只能遍历 List。
②Iterator 只能单向遍历,而 ListIterator 可以双向遍历(向前/后遍历)。
③ListIterator 实现 Iterator 接口,然后添加了一些额外的功能,比如添加一个元素、替换一个元素、获取前面或后面元素的索引位置
常用的集合类有哪些
Map接口和Collection接口是所有集合框架的父接口:
Collection接口的子接口包括:Set、List、Queue三种子接口
Map接口的实现类主要有:HashMap、TreeMap、Hashtable、ConcurrentHashMap以及Properties等
Set接口的实现类主要有:HashSet、TreeSet、LinkedHashSet等
List接口的实现类主要有:ArrayList、LinkedList、Stack以及Vector等
Collection接口主要有List和Set两大子接口
List
List:有序容器(元素存入集合的顺序和取出的顺序一致),元素可以重复,可以插入多个null元素,元素都有索引。常用的实现类有 ArrayList、LinkedList 和 Vector
ArrayList、LinkedList、Vector 的区别 :
要点:
①LinkedList 不会出现扩容的问题,所以比较适合随机位置增、删。但是其基于链表实现,所以在定位时需要线性扫描,效率比较低
②当操作是在一列数据的后面添加数据而不是在前面或中间,并且需要随机地访问其中的元素时,使用ArrayList会提供比较好的性能
③当你的操作是在一列数据的前面或中间添加或删除数据,并且按照顺序访问其中的元素时,就应该使用LinkedList了
总结:ArrayList 和 Vector 基于数组实现,对于随机访问get和set,ArrayList优于LinkedList,因为LinkedList要移动指针。
Set
Set:无序容器(存入和取出顺序有可能不一致),不可以存储重复元素,只允许存入一个null元素,必须保证元素唯一性。Set 接口常用实现类是 HashSet、LinkedHashSet 以及 TreeSet
HashSet、TreeSet、LinkedHashSet的区别
HashSet如何检查重复
当你把对象加入HashSet时,HashSet会先计算对象的hashcode值来判断对象加入的位置,同时也会与其他加入的对象的hashcode值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同hashcode值的对象,这时会调用equals()方法来检查hashcode相等的对象是否真的相同。如果两者相同,HashSet就不会让加入操作成功。
hashCode()与equals()的相关规定:
①如果两个对象相等,则hashcode一定也是相同的
②两个对象相等,equals方法返回true
③两个对象有相同的hashcode值,它们也不一定是相等的
④综上,equals方法被覆盖过,则hashCode方法也必须被覆盖
hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)
总结:HashSet是一个通用功能的Set,而LinkedHashSet 提供元素插入顺序保证,TreeSet是一个SortedSet实现,由Comparator 或者 Comparable指定的元素顺序存储元素
Map接口
Map是一个键值对集合,存储键、值和之间的映射。 Key无序,唯一;value 不要求有序,允许重复。Map没有继承于Collection接口,从Map集合中检索元素时,只要给出键对象,就会返回对应的值对象
Map 的常用实现类:HashMap、TreeMap、HashTable、LinkedHashMap、ConcurrentHashMap
现在可以使用 ConcurrentHashMap 来支持线程安全,并且 ConcurrentHashMap 的效率会更高,因为 ConcurrentHashMap 引入了分段锁。
LinkedHashMap:使用双向链表来维护元素的顺序,顺序为插入顺序或者最近最少使用(LRU)顺序
HashMap、HashTable、TreeMap的区别
HashMap在JDK1.7和JDK1.8中有哪些不同
注:链表长度大于阈值(默认为8),但是数组长度小于64时会首先进行扩容,否则才会转化为红黑树
集合框架底层数据结构
Collection
1. List
Arraylist: Object数组
Vector(线程安全): Object数组
LinkedList: 双向循环链表
2. Set
HashSet(无序,唯一):基于 HashMap 实现的,底层采用 HashMap 来保存元素
LinkedHashSet: LinkedHashSet 继承与 HashSet,并且其内部是通过 LinkedHashMap 来实现的。有点类似于我们之前说的LinkedHashMap 其内部是基于 Hashmap 实现一样,不过还是有一点点区别的
TreeSet(有序,唯一): 红黑树(自平衡的排序二叉树)
Map
HashMap: JDK1.8之前HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突).JDK1.8以后在解决哈希冲突时有了较大的变化,链表长度大于阈值(默认为8),但是数组长度小于64时会首先进行扩容,否则转化为红黑树,以减少搜索时间
LinkedHashMap:LinkedHashMap 继承自 HashMap,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。
HashTable(线程安全): 数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的
TreeMap: 红黑树(自平衡的排序二叉树)
哪些集合类是线程安全的?
vector:就比arraylist多了个同步化机制(线程安全),因为效率较低,现在已经不太建议使用。
statck:堆栈类,先进后出。
hashtable:就比hashmap多了个线程安全。
enumeration:枚举,相当于迭代器。
集合工具类Collections
Collections:集合工具类,方便对集合的操作。这个类不需要创建对象,内部提供的都是静态方法
Collection 和 Collections的区别
①Collections是java.util下的类,是针对集合类的一个工具类,提供一系列静态方法,实现对集合的查找、排序、替换、线程安全化(将非同步的集合转换成同步的)等操作
②Collection是java.util下的接口,它是各种集合结构的父接口,继承于它的接口主要有Set和List,提供了关于集合的一些操作,如插入、删除、判断一个元素是否其成员、遍历等
常见的面试问题:
1. 遍历一个 List 有哪些不同的方式?每种方法的实现原理是什么?
遍历方式有以下几种:
①for 循环遍历,基于计数器。在集合外部维护一个计数器,然后依次读取每一个位置的元素,当读取到最后一个元素后停止。
②迭代器遍历,Iterator。Iterator 是面向对象的一个设计模式,目的是屏蔽不同数据集合的特点,统一遍历集合的接口。Java 在 Collections 中支持了 Iterator 模式。
③for-each 循环遍历。foreach 内部也是采用了 Iterator 的方式实现,使用时不需要显式声明 Iterator 或计数器。优点是代码简洁,不易出错;缺点是只能做简单的遍历,不能在遍历过程中操作数据集合,例如删除、替换
2. 说一下 ArrayList 的优缺点
ArrayList的优点如下:
①ArrayList 底层以数组实现,是一种随机访问模式。ArrayList 实现了 RandomAccess 接口,因此查找的时候非常快
②ArrayList 在顺序添加一个元素的时候非常方便
ArrayList 的缺点如下:
①删除元素的时候,需要做一次元素复制操作,如果要复制的元素很多,那么就会比较耗费性能
②插入元素的时候,也需要做一次元素复制操作,如果要复制的元素很多,那么就会比较耗费性能
ArrayList 比较适合顺序添加、随机访问的场景
3. 如何实现数组和 List 之间的转换?
①数组转 List:使用 Arrays. asList(array) 进行转换
②List 转数组:使用 List 自带的 toArray() 方法
4. ArrayList 和 LinkedList 的区别是什么?
①数据结构实现:ArrayList 底层是动态数组,而 LinkedList 是双向链表的数据结构实现
②随机访问效率:ArrayList 比 LinkedList 在随机访问的时候效率要高,因为 LinkedList 是线性的数据存储方式,所以只需要移动指针从前往后依次查找
③增加和删除效率:在非首尾的增加和删除操作,LinkedList 要比 ArrayList 效率要高,因为 ArrayList 增删操作要影响数组内的其他数据的下标
④内存空间占用:LinkedList 比 ArrayList 更占内存,因为 LinkedList 的节点除了存储数据,还存储了两个引用,一个指向前一个元素,一个指向后一个元素
⑤线程安全:ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全
综合来说,在需要频繁读取集合中的元素时,更推荐使用 ArrayList,而在插入和删除操作较多时,更推荐使用 LinkedList
5. ArrayList 和 Vector 的区别是什么?
这两个类都实现了 List 接口(List 接口继承了 Collection 接口),他们都是有序集合
①线程安全:Vector 使用了 Synchronized 来实现线程同步,是线程安全的,而 ArrayList 是非线程安全的
②性能:ArrayList 在性能方面要优于 Vector。
③扩容:ArrayList 和 Vector 都会根据实际的需要动态的调整容量,只不过在 Vector 扩容每次会增加 1 倍,而 ArrayList 只会增加 50%
Vector类的所有方法都是同步的。可以由两个线程安全地访问一个Vector对象、但是一个线程访问Vector的话代码要在同步操作上耗费大量的时间
Arraylist不是同步的,所以在不需要保证线程安全时时建议使用Arraylist
6. 多线程场景下如何使用 ArrayList?
ArrayList 不是线程安全的,如果遇到多线程场景,可以通过 Collections 的 synchronizedList 方法将其转换成线程安全的容器后再使用
7. List 和 Set 的区别
Set:检索元素效率低下,删除和插入效率高,插入和删除不会引起元素位置改变。
List:和数组类似,List可以动态增长,查找元素效率高,插入删除元素效率低,因为会引起其他元素位置改变
另外 List 支持for循环,也就是通过下标来遍历,也可以用迭代器,但是set只能用迭代,因为他无序,无法用下标来取得想要的值
8. 说一下 HashSet 的实现原理?
HashSet 是基于 HashMap 实现的,HashSet的值存放放的是HashMap的key,HashMap的value统一为PRESENT,因此 HashSet 的实现比较简单,相关 HashSet 的操作,基本上都是直接调用底层 HashMap 的相关方法来完成,HashSet 不允许重复的值
9. HashSet与HashMap的区别
10. BlockingQueue是什么?
BlockingQueue接口是Java集合框架的一部分,主要用于实现生产者-消费者模式Java.util.concurrent.BlockingQueue是一个阻塞队列,在进行检索或移除一个元素的时候,它会等待队列变为非空;当在添加一个元素时,它会等待队列中的可用空间。我们不需要担心等待生产者有可用的空间,或消费者有可用的对象,因为它都在BlockingQueue的实现类中被处理了PriorityBlockingQueue,、SynchronousQueue等。
11. 在 Queue 中 poll()和 remove()有什么区别?
相同点:都是返回第一个元素,并在队列中删除返回的对象
不同点:如果没有元素 poll()会返回 null,而 remove()会直接抛出 NoSuchElementException 异常