目录
Collections.sort和Arrays.sort的实现原理
为何Collection不从Cloneable和Serializable接口继承?
Iterator 和 ListIterator 有什么区别?
快速失败(fail-fast)和安全失败(fail-safe)的区别是什么?
讲一讲Java集合吧
所有集合类都位于Java.util包下,Java的集合类主要由两个接口派生而出 : Collection和Map, Collection和Map是Java集合框架的根接口,两个接口又包含了子接口和实现类
对于Collection接口,下面又有三个主要的子接口 : List,Set,Queue
List主要实现类 :
- ArrayList: 底层是Object[] 数组
- LinkedList: 底层是 双向链表(JDK1.6 之前为循环链表,JDK1.7 取消了循环)
- Vector:底层是Object[] 数组
- Stack : 继承自Vector
Set主要实现类 :
- HashSet(无序,唯一): 基于 HashMap 实现的,底层采用 HashMap 来保存元素
- LinkedHashSet: LinkedHashSet 是 HashSet 的子类,并且其内部是通过 LinkedHashMap 来实现的。有点类似于我们之前说的 LinkedHashMap 其内部是基于 HashMap 实现一样,不过还是有一点点区别的
- TreeSet(有序,唯一): 红黑树(自平衡的排序二叉树
Queue接口主要实现类 :
- PriorityQueue: Object[] 数组来实现二叉堆--->完全二叉树
- ArrayQueue: Object[] 数组 + 双指针
对于Map接口,下面又有主要的实现类有 :HashMap,LinkedHashMap,HashTable,TreeMap
- HashMap: JDK1.8 之前 HashMap 由数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突)。JDK1.8 以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间
- LinkedHashMap: LinkedHashMap 继承自 HashMap,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。
- Hashtable: 数组+链表组成的,数组是 Hashtable 的主体,链表则是主要为了解决哈希冲突而存在的
- TreeMap: 底层是由红黑树实现的,保证有序
List,Set,Queue,Map接口的特点
- List: 存储的元素是有序的、可重复的。
- Set: 存储的元素是无序的、不可重复的。
- Queue: 按特定的排队规则来确定先后顺序,存储的元素是有序的、可重复的。
- Map: 用于存储键值对(key-val形式)的元素, 根据key获取val,key是无序的,不可重复,val是无序的,可重复
集合的使用
为什么要使用集合?
先说一下使用数组的缺点 :
- 一旦初始化以后,其长度就不可修改.
- 数组中提供的方法非常限,对于添加、删除、插入数据等操作,非常不便,同时效率不高。
- 数组只能存放一组类型相同的元素,单一.实际开发中存储的数据类型可能是多种多样的
- 获取数组中实际元素的个数的需求,数组没有现成的属性或方法可用
- 数组存储数据的特点:有序、可重复。对于无序、不可重复的需求,不能满足。
- 数组不能存放键值对
为啥使用集合 ?
集合提高了数据存储的灵活性,Java 集合不仅可以用来存储不同类型不同数量的对象,还可以保存具有映射关系的数据,还可以提供相应的方法来提高操作容器的效率
如何选用集合?
主要是根据集合的特点来进行选择
- 如果你想要存放键值对,根据key获取到val,那么就可以选用Map接口下的集合
- 需要排序,选择TreeMap
- 不需要排序就选择HashMap
- 想要保证线程安全就选用ConcurrentHashMap
- 否则就是指需要存储单个元素,那么可以选用Collection接口下的集合
- 需要保证元素唯一,需要排序选择TreeSet , 否则可以选择HashSet
- 需要按照某种排队规则可以选用Queue接口下的集合如PriorityQueue,ArrayDeque
- 只需存放单一元素,有序,可重复 就可以选择List接口下的集合,如果需要保证线程安全,可以使用vector,SynchronizedList(使用Collections.synchronizedList(list); 将list包装成SynchronizedList),copyOnWriteList, 否则可以选择比如 ArrayList,LinkedList ,增删多:linkedList,查询多 : ArrayList
Collection
Collection与Collections的区别是什么?
collection 是一个接口 主要的接口如 List, Set, Queue,这三个主要的接口有实现类来帮助我们操作元素.
collections是集合类的一个工具类/帮助类,其中提供了一系列静态方法,用于对集合中元素进行排序(sort)、搜索、反转(reverse)、拷贝(copy)以及线程安全等各种操作
Collections 工具类常用方法:
- 排序
void reverse(List list)//反转
void shuffle(List list)//随机排序
void sort(List list)//按自然排序的升序排序
void sort(List list, Comparator c)//定制排序,由Comparator控制排序逻辑
void swap(List list, int i , int j)//交换两个索引位置的元素
void rotate(List list, int distance)//旋转。当distance为正数时,将list后distance个元素整体移到前面。当distance为负数时,将 list的前distance个元素整体移到后面
- 查找,替换操作
int binarySearch(List list, Object key)//对List进行二分查找,返回索引,注意List必须是有序的
int max(Collection coll)//根据元素的自然顺序,返回最大的元素。 类比int min(Collection coll)
int max(Collection coll, Comparator c)//根据定制排序,返回最大元素,排序规则由Comparatator类控制。类比int min(Collection coll, Comparator c)
void fill(List list, Object obj)//用指定的元素代替指定list中的所有元素
int frequency(Collection c, Object o)//统计元素出现次数
int indexOfSubList(List list, List target)//统计target在list中第一次出现的索引,找不到则返回-1,类比int lastIndexOfSubList(List source, list target)
boolean replaceAll(List list, Object oldVal, Object newVal)//用新元素替换旧元素
- 同步控制(不推荐,需要线程安全的集合类型时请考虑使用 JUC 包下的并发集合)
Collections 提供了多个synchronizedXxx()方法·,该方法可以将指定集合包装成线程同步的集合,从而解决多线程并发访问集合时的线程安全问题。
我们知道 HashSet,TreeSet,ArrayList,LinkedList,HashMap,TreeMap 都是线程不安全的。Collections 提供了多个静态方法可以把他们包装成线程同步的集合。
synchronizedCollection(Collection<T> c) //返回指定 collection 支持的同步(线程安全的)collection。
synchronizedList(List<T> list)//返回指定列表支持的同步(线程安全的)List。
synchronizedMap(Map<K,V> m) //返回由指定映射支持的同步(线程安全的)Map。
synchronizedSet(Set<T> s) //返回指定 set 支持的同步(线程安全的)set。
Collections.sort和Arrays.sort的实现原理
为何Collection不从Cloneable和Serializable接口继承?
Collection是集合的根接口,可以存储一组对象,因为对象的存储多种多样.所以如何存储对象是由接口的具体实现来决定的,比如,比如 list允许重复而set不允许重复, 对于克隆和序列化而言,只对具体的实体和对象才有意义,不能把一个接口,抽象类克隆,序列化甚至反序列化. 所以具体的Collection实现类 是否可以克隆,序列化由其自身来决定,不能由超类强行赋予.
举一个反例吧 : 如果Collection继承了clone和serializable,那么它的所有实现类都会实现这两个接口,但是如果某个实现类不需要被克隆,甚至不允许序列化,那么就与collection矛盾了
线程安全集合类
哪些集合类是线程安全的?哪些不安全?
- String
- Integer
- StringBuffer
- Random
- Vector
- Hashtable
- java.util.concurrent 包下的类
剩下的不是线程安全的
怎么确保一个集合不能被修改?
iterator
迭代器 Iterator 是什么?怎么用,有什么特点?
Iterator 和 ListIterator 有什么区别?
- Iterator 可以遍历 Set 和 List 集合,而 ListIterator 只能遍历 List。
- Iterator 只能单向遍历,而 ListIterator 可以双向遍历(向前/后遍历).(ListIterator和Iterator都有hasNext()和next()方法,可以实现顺序向后遍历,但是ListIterator有hasPrevious()和previous()方法,可以实现逆向(顺序向前)遍历。Iterator就不可以)
- ListIterator有add()方法,可以向List中添加对象,而Iterator不可以;
- ListIterator可以定位当前的索引位置,nextIndex()和previousIndex()可以实现。Iterator没有此功能;
- 都可实现删除对象,但是ListIterator可以实现对象的修改,set()方法可以实现,Iterator仅能遍历,不能修改。
Enumeration和Iterator接口的区别?
快速失败(fail-fast)和安全失败(fail-safe)的区别是什么?
fail-fast 和fail-safe 是在不允许在遍历集合的时候去修改的两种应对措施,因为在遍历的时候可能会有其他线程来修改,这时fail-fast是立刻抛出异常,而fail-safe则是会有应对策略,比如牺牲一致性来保证整个遍历的运行完成
- ArrayList是fail-fast的典型代表,当在遍历的时候有其他线程修改,则立刻抛出异常ConcurrentModificationException (遍历的同时不能修改)
- 原理 : 迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历.
- copyOnWirteArrayList是fail-safe的典型代表, 当在遍历的时候有其他线程修改 , 会有应对策略,比如牺牲一致性让整个遍历运行完成 (遍历的同时可以修改)
- 原理 : fail-safe集合中的所有对集合的修改都是先复制一份副本,然后在副本集合中进行修改,并不是直接对原集合进行修改
参考:
博客园 一些博客
csdn一些博客
https://www.cnblogs.com/williamjie/p/11158588.html
Javaguide : Java集合常见面试题总结(下) | JavaGuide(Java面试+学习指南)