Java集合
概述
Java集合是Java类库中提供的一些现成的数据结构并实现了这些数据结构的常用的操作(e.g. 增删改查),主要分为两大类,一类是实现了Collection
接口的类,另一类是实现了Map
接口的类
实现了Collection接口的类
在Java中直接实现类Collection
接口的类是AbstractCollection
类,常见的数据结构例如:List,Set,Queue,这些数据结构都是继承了AbstractCollection类的,同时也实现了相对应的接口(List,Set,Queue
)这些接口也都继承了Collection
接口
ArrayList
ArrayList
是基于数组为原形,实现了自动扩容方法的List,继承了AbstractList
//构造方法
ArrayList<E> list = new ArrayList<>(int size);
这个构造方法会实例化一个Object
的数组,在未指定大小时,默认大小为10
//插入元素
list.add(E);
//指定位置插入元素
list.add(int index,E)
当调用add
方法时,会先基于当前的大小+1,产生一个叫minCapacity
的变量
当minCapacity
<Object
数组大小的时候,则在直接插入这个数组
当minCapacity
>Object
数组大小的时候,创建一个新的数组,大小为当前数组大小x1.5+1,如果还是小于minCapacity
,则将minCapacity
作为新的数组大小,扩容后调用Arrays.copyOf
来将原来数组中的数据复制到新的数组中
//删除元素
list.remove(E)
如果要删除的对象为null
则遍历数组中已有值的元素,比较其是否为null
,如果要删除的对象不为null
则通过元素的equals
方法来比较。找到后,使用fastRemove
方法来删除元素。
fastRemove
方法:将要删除元素后面的元素往前复制一位,并将最后一个元素值设为null
,释放引用
//删除指定位置元素
list.remove(int index,E)
该方法性能更好,需要检查index是否在数组容量范围内,但是不用再查询对象的位置
//获取单个对象(需要检查index是否在数组容量范围内)
list.get(int index)
//获取元素所在的位置(从前往后搜索)
list.indexOf(E)
//获取元素所在的位置(从后往后前搜索)
list.lastIndexOf(E)
//遍历元素(迭代器)
Iterator<E> it = list.iterator();
while(it.hasNext()){
Object e = it.next();
}
//遍历元素(加强forloop)
for(Object e:list){
//e.todo
}
当用调用next
方法时,会比较创建iterator
时当modCount
于当前的modCount
,如果不同,说明list
的大小发生了改变(增、删)并抛出ConcurrentModificationException
,如果相同iterator
则调用get方法来获取元素
//判断元素是否存在
list.contains(E)
注意要点
ArrayList
会在插入元素时扩容,在删除元素时并不会减小数组的容量(可以手动调用trimToSize()
方法来缩小容量)
ArrayList
是非线程安全的
LinkedList
LinkedList
是基于双向链表的实现的,继承了AbstractList
,在LinkedList
中用一个内部类Entry
来代表集合中的元素
//构造方法
LinkesList<E> list = new LinkedList<>();
会先创建一个element
,next
,previous
属性为null
的Entry
,并赋值给全局属性header
,并将header
的 next
和previous
都指向header
形成一个双向链表闭环
//插入元素
list.add(E);
先创建一个Entry
对象将next
指向header
,previous
指向header.previous
完成设置后,将当前元素的后一个元素的previous
指向新增的元素,前一个元素的next
指向新增元素,保存双向链表闭环
//删除元素
list.remove(E)
同样要遍历整个集合,遍历和匹配的元素的方法和ArrayList
基本相同,删除时直接当前元素的所有属性设置为null
并将后前的元素连接起来
//删除元素
list.get(int index)
先判断index是否在返回内,如果超过范围就抛出异常IndexOutOfBoundException
,在返回为则再次判断index是否小于集合大小的一半,如果小于则从头开始往后找(通过next
),反之则从尾往前找(通过previous
)
//遍历元素(迭代器)
Iterator<E> it = list.iterator();
while(it.hasNext()){
Object e = it.next();
}
//遍历元素(加强forloop)
for(Object e:list){
//e.todo
}
同样会检查modCount
,不同的是可以往前遍历通过hasPrevious
和previous
完成向前遍历
//判断元素是否存在
list.contains(E)
注意要点
LinkedList
同样是非线程安全的
Vector
Vector
和ArrayList
一样是基于数组方式实现的,功能也基本一直,不同的是Vector
是线程安全的,扩容机制也不同
如果capacityIncrement
大于0,扩容时新数组的大小等于当前的size
+capacityIncrement
如果capacityIncrement
小于0,扩容时新数组的大小等于当前的size
的两倍,可以通过传入capacityIncrement
来控制扩容
Stack
Stack
继承来Vector
并增加了pop()
,push()
,peek()
方法
push()
:通过Vector
的add
方法来完成
peek()
:获取当前数组的大小,并返回最后一个元素
pop()
:调用peek()
方法,并删除数组的最后一个元素
实现了Set接口的类
Set
与List
的区别是Set
不运行重复的元素存在
HashSet
基于HashMap
来实现的,构造HashSet
时,实际上构造出一个HashMap
,因此所有的操作都是基于的HashMap
的
add(E)
方法:调用了HashMap
的put(key,value)
方法来实现,key
和value
都是传入的对象
HashSet
中的元素是没有顺序的,因此不能使用get(int index)
来获取指定位置的元素
HashSet
是非线程安全的
TreeSet
TreeSet
是基于的TreeMap
实现的,TreeSet
支持排序,同样不支持get(int index)
方法
可以通过传入Comparator
来实现对TreeSet
的排序和按照(降序/升序)遍历
注意要点
TreeSet
非线程安全
实现了Map接口的类
Map
是一种存储键值对的数据结构
HashMap
是最常用的实现类Map
接口的类
//构造方法
HashMap<K,V> map = new HashMap<>();
默认的loadFactor
为0.75,threshold
为12,并创建大小的为16的Entry
对象数
//插入元素
map.put(Object key,Object value);
通过hash出来的值来确定存储的位置,确定位置后,获取相对应的Entry
对象,并遍历这Entry
数组,如果key相对或者equals
的Entry
对象,则替换对象的值,如果没有则创建一个新的Entry
对象
当Entry
数组中已用大小大于threshold
时,数组扩大为当前大小两倍,并对所有的元素重新hash,并填充数组,最后重新设置数组
HashMap
是通过链表的方式来解决hash冲突的
//获取元素
map.get(Object key);
与put方法过程类似,通过hash来找到位置,并遍历数组来找到元素
//删除元素
map.remove(Object key);
//判断元素是否存在
map.containsKey(Object key);
通过调用getEntry
方法来完成与get
方法过程基本相同
注意要点
HashMap
非线程安全
TreeMap
TreeMap
是基于红黑树实现的,是支持排序的Map
//插入对象
map.put(Object key,Object value);
先判断红黑树的root
是否为null
,如果是则新建一个Entry
并赋值给root
,如果不是,则通过Comparator
来比较key
来决定放在树的左边还是右边,如果key
已经存在则更新对应的value
,不存在则新建一个Entry
对象并将其parent
属性设置好
//获取元素
map.get(Object key);
基于红黑树的查找
注意要点
TreeMap
是非线程安全
小结
实现了List
接口的类在元素数量较大时,查询和删除元素性能较差,基于实现类Map
和Set
的类性能较好,因此对象查询和删除较多的的场景适合使用Map
和Set