集合框架总结
所有的集合类位于java.util包下【注意juc包】,集合类主要是由两个接口派生出来的,Collection和Map接口是整个框架的根接口
-
6个集合接口,用于表示不同的集合类型
-
5个抽象类,对集合接口的部分实现
-
8个实现类,是对接口的具体实现
-
Collection接口:无序、允许重复。包含了集合的基本操作,可以分为List和Set两大分支
- List有序,每个元素都有一个下标索引,从0开始,具体实现LinkedList、ArrayList、Vector
- Set不允许重复,具体实现HashSet和TreeSet,特殊的时LinkedHashSet
-
Set接口继承Collection接口:无序(不维护插入数据的顺序)、不允许重复
-
List接口继承Collection接口:有序(维护插入数据的顺序)、允许重复
-
Map接口存储key-value对数据,与Collection接口无关
- 具体实现HashMap、Hashtable、TreeMap、LinkedHashMap
-
Iterator接口是用于遍历集合的工具,Collection接口继承于Iterable接口,要求所有的Collection接口的实现类中提供Iterator接口的实现。针对List有一个ListIterator专门遍历List,提供双向遍历功能,注意fail-fast问题
-
Enumeration是JDK1.0引入的抽象类,作用和Iterator一致,用于实现集合的遍历,已过时
可以将List、Set和Map看作集合的三大类
-
List有序,允许重复,访问集合中的元素可以根据元素的索引来访问
- Iterable和Iterator foreach结构
- for
-
Set无序,不可以重复,访问集合中的元素只能根据元素本身进行访问
- Iterable和Iterator foreach结构
-
Map存储key-value对元素,访问时可以通过key访问value
- keySet
- values
- entrySet
两个特殊的工具类
-
Arrays是操作数组的工具类
-
Collections是操作集合的工具类
Collection接口
Collection接口继承于Iterable接口
-
size():int 获取元素个数
-
add(E e):boolean 新增元素
-
remove(E e):boolean 删除指定元素
-
iterator():Iterator 获取迭代器对象
-
contains(E e):boolean 判断集合中是否包含指定元素
-
clear():void 清空集合
-
toArray():Object[]/toArray(T[]):T[]
List接口
集合中的每个元素都有一个顺序索引,允许通过索引访问指定位置上的集合元素
List接口继承于Collection接口,同时提供了关于索引相关的操作方法
-
sort(Comparator<? super E> c)
用于针对集合中的所有元素按照c比较器进行排序,具体的底层实现是通过Arrays.sort(a, (Comparator) c);
-
E get(int index);
根据下标获取指定位置上的元素 -
E set(int index, E element);
修改指定位置上的元素为element -
void add(int index, E element);
向指定位置上添加元素element -
E remove(int index);
删除指定位置上的元素,并返回删除的元素 -
int indexOf(Object o);int lastIndexOf(Object o);
查找指定元素o的下标位置 -
ListIterator<E> listIterator();
提供双向遍历的具体实现类对象 -
List<E> subList(int fromIndex, int toIndex)
获取指定范围的子集合
具体实现类有ArrayList、LinkedList、Vector、Stack(Deque)
ArrayList
底层实现是一个可变长数组【java中的数组都是定长的】,允许插入null值,默认初始化容积为10,默认采用的是延迟加载的方式进行初始化数组操作,随着元素的不管增加,集合会调用grow方法自动进行扩容处理,增长扩容比例为50%。如果可以评估的化最好能够指定一个合理的初始化容积值,尽量避免过多的扩容操作而浪费时间
-
size、isEmpty、get、set操作都是O(1)
-
add操作由于可能会涉及扩容处理,但是考虑到分摊固定运行时间和数组拷贝使用System.arrayCopy方法,所以也可以近似认为O(1)
ArrayList擅长随机访问,非同步
LinkedList
底层实现是一个双向链表,没有容积的概念【int size()】,不能随机访问,当通过下标访问元素时会判断具体first和last的远近,从而决定从头还是从尾开始逐一访问[p=p.next p=p.prev]
由于链表中插入和删除数据没有数据移动问题,所以增删的效率较高,但是注意:定位位置的时间复杂度还是O(n)
LinkedList擅长增删,非同步
Vector
底层实现是一个可变长数组,但是大部分方法上都进行synchronized同步处理,所以线程安全。因为同步处理会影响并发访问性能还有加锁的代价问题,所以已经不再推荐使用。默认初始化容积为10,默认立即执行数组的初始化操作。扩容增长比例为100%
Stack
Stack继承于Vector,实现了一个后进先出的堆栈,基本方法就是push压栈和pop弹栈,建议使用Deque
总结List
-
ArrayList变长数组,线程不安全,有扩容处理,随机访问速度快,增加删除数据速度慢
-
LinkedList双向链表,线程不安全,没有扩容处理,根据下标索引访问速度慢,增加删除数据速度快
-
Vector变长数组,线程安全,基本上已经被ArrayList所替代
-
Stack是堆栈的实现,继承Vector,建议通过双向队列的方式替代
Set接口
不包含重复元素的集合,它维持自己内部的顺序,无序,随机访问没有任何意义;传入set集合中的元素不能相等【equals为true或者compareTo为0】。具体的实现类HashSet散列集、LinkedHashSet和TreeSet
HashSet类
没有重复元素的集合,底层实现是HashMap【利用key特性,value值是一个常量值】,不保证元素的顺序,允许null值,非同步的,HashSet利用hash算法存储集合中的元素,所以具有很好的存取和查找性能
-
hashset允许存放null值
-
hashset存储数据是无序的
-
小心操作可变对象mutable object
LinkedHashSet
LinkedHashSet继承于HashSet,底层是基于LinkedHashMap实现的,有序【插入序和访问序】,非同步;集合中同样是根据元素的hashCode值决定对应的存储位置;在HashMap的基础上附加了一个链表,用于维护元素的访问次序。
TreeSet
TreeSet底层是通过TreeMap实现的,具体存储采用的是红黑树
- TreeSet集合不是通过hashCode和equals函数来实现的,是通过comparaTo或者compare判断大小和排序,和hashCode和equals方法无关
强调
存储数据到HashSet中要求数据对应的类型必须重写了equals就需要重写hashCode
规则:要求equals为true时hashCode必须相等
实际上这只是一条规范,如果不这样做程序也可以执行,只不过会隐藏bug。一般一个类的对象如果会存储在Hashtable\HashSet\HashMap等散列存储结构中,那么重写equals后最好也重写hashCode,否则会导致存储数据的不唯一性(存储了两个equals相等的数据)。而如果确定不会存储在这些散列结构中,则可以不重写hashCode。
但是加建议还是重写比较好一点,谁能保证后期不会存储在这些结构中呢,况且重写了hashCode也不会降低性能,一般重写又是可以通过IDE工具生成,因为在线性结构(如ArrayList)中是不会调用hashCode,所以重写了也不要紧,也为后期的修改打了补丁。
equals和==的对比?
==针对简单类型是比较具体的值,针对复杂类型比较的是引用值,在Java中不允许重载运算符,所以没有办法重新定义
equals针对引用类型可以按照业务规则自定义比较内容,如果不定义则从Object类中可以继承到equals方法,这个方法默认和==等价,java中允许覆盖定义Object类中继承到equals方法
Map接口
Map用于存储key-value对的集合,提供了key到value的映射。不允许key值重复,添加数据时如果key值重复,则后盖前;但是针对value没有任何特殊要求
具体的实现类HashMap【ConcurrentHashMap】、Hashtable、LinkedHashMap、TreeMap
HashMap
面试重点
HashMap底层实现采用的是拉链法的哈希表结构,具体实现 JDK1.7采用【数组+链表】,JDK1.8采用【数组+链表+红黑树】。是为了快速查找而设计的,内部定义了一个Node[]数组用于存储数据,元素会通过哈希函数将key转换为数组中存放的索引值,如果有冲突【key值不相同但是映射位置相同】则采用单向链表存储数据;如果单向链表长度过长则会影响查询效率,所以大于树化阈值,可以将链表转换为红黑树;红黑树的平衡处理比较繁琐,所以当红黑树中节点个数小于退化阈值时会自动转换为单向链。
默认初始化数组长度为16,加载因子为0.75【减少hash碰撞的概率】,树化阈值为8和树化总节点数64,退化阈值6。当元素个数大于【容积*加载因子】时会进行扩容,扩容比例为增加100%。如果设置初始化容积值会自动转换为2的n次方【大于等于初始值】,数组扩容会涉及rehash计算。插入数据时采用的是尾插法
线程不安全,在多线程并发操作中会出现rehash操作出现死循环、脏读导致的数据丢失和size值不精确等问题
存储数据允许null值和null键,但是作为key时null只能出现一次
遍历数据:
-
keySet
-
values
-
entrySet
解决方案为:Collections.synchronizedMap或者ConcurrentHashMap
LinkedHashMap
LinkedHashMap是HashMap的子类,存储方法和HashMap一致,但是引入一个额外的双向链表记录数据的顺序,顺序可以分为2种:插入序(默认)和访问序,特别适合作为缓存的实现。
LinkedHashMap<String,Integer> map=new LinkedHashMap<>(16,0.75f,true);
map.put(null,111);
map.put("aaa", 999); //ConcurrentModificationException
for(String tmp:map.keySet())
System.out.println(tmp+"-->"+map.get(tmp));
允许null键和null值
非同步线程不安全
由于需要维护元素的顺序,所以性能略低于hashMap性能,但是采用插入序迭代访问全部元素时性能较好
TreeMap
TreeMap内部实现是红黑树,存储元素时会根据key值进行排序,其中排序方法有自然排序和定制排序两种
TreeMap一般要求key必须实现Comparable接口,从而实现自然排序;如果key没有实现Comparable接口或者需要自定义比较器则应该构建TreeMap时指定比较器
TreeMap判断key值相等不是使用equals方法,而是使用compareTo或者自定义比较器中比较方法
自定义类充当key值一般要求实现Comparable接口,并定义equals。要求两个key值equals为true时,必须compareTo返回为零,避免二义性
TreeMap不允许null键和null值,如果希望使用null键则需要自定义比较规则
非同步线程不安全
Hashtable
古老的实现,基于【数组+链表】实现数据存储,没有要求初始化容积值必须为2的n次方,默认初始化容积值为11,负载因子为0.75,不允许null键和null值,扩容后的容器为【旧有容积*2+1】
同步处理,线程安全
总结
HashMap基于拉链法实现的散列表,一般用于单线程编程中
Hashtable基于拉链法实现的散列表,线程安全,使用中性能低下,不建议使用
TreeMap实现了SortedMap接口,要求按照key值进行排序,底层采用红黑树
LinkedHashMap在HashMap存储数据的基础上添加了额外的双向链表记录数据的访问顺序
Iterator和ListIterator接口
Iterator接口
Iterator是一个集合的迭代访问接口,集合对象可以通过Iterator去遍历访问集合中的所有元素
public interface Iterator<E> {
boolean hasNext(); //判断集合中是否存储下一个元素,如果有返回true,否则false
E next(); //获取集合中的下一个元素,同时指针后移
default void remove() { //删除集合中上一次next方法返回的元素
throw new UnsupportedOperationException("remove");
}
default void forEachRemaining(Consumer<? super E> action) { //用于针对 lambda表达式的方式访问集合中的元素
Objects.requireNonNull(action);
while (hasNext())
action.accept(next());
}
}
由于Collection接口继承于Iterable接口,所以要求Collection的实现类中必须提供一个Iterator的实现ArrayList中的iterator方法的实现
public Iterator<E> iterator() {
return new Itr();
}
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;//实现快死异常,不允许遍历集合的同时,有其它线程修改了集合结构,例如add新增元素,则集合结构发生变化
基本使用
ArrayList<String> arr=new ArrayList<>();
arr.add("abc");
arr.add("bbb");
Iterator<String> it=arr.iterator();
while(it.hasNext()){
String tmp=it.next();
System.out.println(tmp);
}
fail-fast异常
ArrayList<String> arr = new ArrayList<>();
arr.add("abc");
arr.add("bbb");
arr.add("ccc");
Iterator<String> it = arr.iterator();
while (it.hasNext()) {
String tmp = it.next();
if("abc".equals(tmp))
arr.remove("abc"); //ConcurrentModificationException--- modCount
System.out.println(tmp);
}
如何解决?
解决方案1:使用juc包中提供的CopyOnWriteArrayList
解决方案2:使用迭代器中提供的remove
ArrayList<String> arr = new ArrayList<>();
arr.add("abc");
arr.add("bbb");
arr.add("ccc");
Iterator<String> it = arr.iterator();
while (it.hasNext()) {
String tmp = it.next();
if("abc".equals(tmp))
it.remove();
System.out.println(tmp);
}
System.out.println(arr.size());
总结:
Iterator只能单向移动 next
Iterator中提供的remove方法是唯一安全的可以在迭代访问的同时修改集合
ListIterator接口
ListIterator继承于Iterator接口,支持双向迭代访问,主要是针对List实现可以通过调用listIterator方法获取
接口定义
public interface ListIterator<E> extends Iterator<E> {
boolean hasNext();//判断集合中是否存储下一个元素,如果有返回true,否则false
E next();//获取集合中的下一个元素,同时指针后移
boolean hasPrevious();//判断集合中是否存储上一个元素,如果有返回true,否则false
E previous();//获取集合中的上一个元素,同时指针前移
int nextIndex(); //获取当前指针所指向的元素的下一个索引值
int previousIndex();//获取当前指针所指向的元素的上一个索引值
void remove();//删除当前元素
void set(E e);//修改当前元素
void add(E e);//在next之前或者previous之后插入一个元素
}
可以双向移动【向前或者向后】
可以获取前一个后者后一个元素的索引值
基本使用
ArrayList<String> arr = new ArrayList<>();
arr.add("aaa");
arr.add("bbb");
arr.add("ccc");
ListIterator<String> lit = arr.listIterator(); // 从前向后遍历所有元素
while (lit.hasNext()) {
String tmp = lit.next();
int pindex = lit.previousIndex();
int nindex = lit.nextIndex();
System.out.println(tmp+"\tpreindex:"+pindex+"\tnextIndex:"+nindex);
}// 从后向前遍历所有元素
while (lit.hasPrevious()) {
System.out.println(lit.previous()+":"+lit.nextIndex());
}
lit = arr.listIterator(1);
while (lit.hasNext()) {
String str=lit.next();
System.out.println(str);
if("ccc".equals(str)){
lit.set("ppp");
}else
lit.add("kkk");
}
System.out.println(arr);
常见的比较
ArrayList和LinkedList
ArrayList基于动态数组、LinkedList基于链表
随机访问get和set,ArrayList
新增和删除add和remove,LinkedList
如果针对单条数据插入或者删除ArrayList优于LinkedList,如果插入删除LinkedList优于ArrayList
Hashtable和HashMap
都是实现了Map、Cloneable、Serializable接口
都是存储key-value对的散列表,都是采用链表法解决哈希冲突
-
历史原因
-
同步
-
null值
-
遍历方式:hashtable支持Iterator迭代器和Enumeration枚举器两种遍历方式,但是hashmap只有iterator
Collection和Collections
java.util.Collection是一个集合接口,提供了对集合对象进行基本操作的通用接口方法。Collection接口在java类库种有很多的具体的实现,Collection接口的作用就是为所有具体实现提供了最大化的统一操作方式,直接子类常见的有List、Set和Queue
st,如果插入删除LinkedList优于ArrayList
Hashtable和HashMap
都是实现了Map、Cloneable、Serializable接口
都是存储key-value对的散列表,都是采用链表法解决哈希冲突
-
历史原因
-
同步
-
null值
-
遍历方式:hashtable支持Iterator迭代器和Enumeration枚举器两种遍历方式,但是hashmap只有iterator
Collection和Collections
java.util.Collection是一个集合接口,提供了对集合对象进行基本操作的通用接口方法。Collection接口在java类库种有很多的具体的实现,Collection接口的作用就是为所有具体实现提供了最大化的统一操作方式,直接子类常见的有List、Set和Queue
java.util.Collections是一个包装类(工具类、帮助类),包含了各种有关集合操作的静态多态方法,类不能实例化,用于实现对集合种的元素进行排序、搜索以及线程线程安全等各种操作