java 集合类理解:
1.Collection接口定义了最基础的集合特性,有五个基本方法,包含了添加,删除,遍历数据项的能力,这五个方法分别是:
boolean add(E) / addAll()
boolean remove(Object) / removeAll()
Iterator iterator()
int size()
boolean contains(Object)
根据Collection衍生出三大类集合,List,Set,Queue
2.Map没有继承Collection,但是Map也是常见的一个集合类,存储方式是K,V对形式,具备五个基本方法
V put(K,V)
void putAll()
V get(Object)
Set keySet()
Collection values()
Set entrySet()
Java中的集合类,基本都是实现Collection和Map两个接口
3.List,List是一个列表形式的集合,其中的元素是有序的,可以重复的,可以在列表中查找元素位置,并且可以在指定位置插入,修改,删除,获取元素。
其基本方法包括:
boolean add(E) / addAll()
void add(int, E) / addAll()
boolean remove(Object) / removeAll()
E remove(int)
void set(int, E) / get(int)
int indexOf(Object) / lastIndexOf(Object)
int size()
Iterator iterator()
ListIterator listIterator()
boolean contains(Object)
3.1 ArrayList:
·基于数组实现,所有数据存储在一个Object[]中,这个数组名叫elementData。
·ArrayList最大的好处是动态数组长度。
·如果可以,最好在初始化时指定容量,避免多次扩容消耗性能。
·ArrayList默认初始容量是10
·在执行插入/删除时,会将elementData数组中操作位置后的全部元素进行后移/前移,这个位移操作使用System.arraycopy实现。
(也就是说,如果对数组中添加一个元素,则元素后所有的数据都要后移,如果删除一个元素,则删除元素后所有的元素都要前移)
·ArrayList获取指定位置元素的性能是List中最高的
·ArrayList插入删除时会对数组进行位移操作(拷贝)
·ArrayList添加元素时,可能会对数组进行扩容操作,数组扩容过程中,会占用冗余的内存,并且由于ArrayList每次扩容会扩容elementData的1.5倍,所以绝大部分情况下ArrayList都会占用额外的存储空间。
3.2 LinkedList
·LinkedList基于链表结构实现列表集合,其中没有数组elementData,只有两个Node对象引用Node first,Node last,其中Node是LinkedList的内部类
·LinkedList在追加新的元素时,会实例化一个新的Node对象,其next为null,并将当前的last的next指向新实例化的Node。
·LinkedList在指定位置(Node A,Node B之间插入C)插入元素时,会先通过遍历找到Node B,实例化Node C,prev指向A,next指向B,再将Node B的prev指向C,Node A的next指向C
(实际LinkedList的修改,就是创建删除Node对象,并修改与之相邻Node对象的next和prev)
·LinkedList在进行随机访问的(指定位置插入,删除元素或者获取指定位置元素)时,都需要对链表进行遍历
·如果访问发生在链表的头和尾,就不需要进行遍历了,LinkedList提供了一套直接操作头/尾元素的方法。
·注意:LinkedList的结构使其具备很高的顺序/逆序遍历性,但是一定要使用访问器方法遍历,如果使用下标遍历,速度会非常慢。
·普通的插入/删除操作时,ArrayList性能优于LinkedList,但是如果只对链表头尾进行访问的话,LinkedList的性能要优于ArrayList
·如果获得一个元素时,LinkedList只能遍历链表,所以获取指定元素时,ArrayList性能优于LinkedList
·如果使用迭代器遍历,ArrayList和LinkedList速度基本相同
·LinkedList适合对头尾操作频繁,并且插入频繁的情况。实际测试,在做删除时,LinkedList性能要优于ArrayList
3.3 Vector
·基于数组实现的。Vector是同步的,由于其实现同步的方式是使用synchronize加锁所有对外暴露的方法,所以性能远远低于ArrayList,如果不需要线程安全,不要使用Vector
3.4 CopyOnWriteArrayList
·CopyOnWriteArrayList 称为写时复制。当向集合中添加元素时,先将当前集合容器创建一个副本,向副本中添加元素,添加完元素后,再将原容器的引用指向新的副本。
这样就确保读操作不需要加锁也能读出正确的数据。大大提高了并发性能。
·CopyOnWriteArrayList 适合读多写少的环境。
·CopyOnWriteArrayList 写操作时,先拿到一个ReentrantLock,然后使用Arrays.copyOf复制一份elements数组,然后对这个数组进行写操作,最后把数组的引用指向新的副本,然后解锁
·CopyOnWriteArrayList 读操作时,直接使用getArray方法获得当前数组进行操作。
·由于每次都会创建副本,内存占用较大,。
·不会扩容,数组的length永远等于列表的size
注意:当一个写操作已经开始还未完成,所有的读操作读到的都是写操作发生之前的值。Vector中,如果一个写操作已经开始,那么所有的读操作都要等写操作完成才能执行,可以避免这种情况的发生。
4 Map
Map接口定义了Key-Value形式的集合,集合中每个元素拥有所属的key,可以通过key查找到对应的元素,Map中的key不可重复。
Map将key和value封装到一个叫做entry的对象中,Map中存储的元素实际是Entry。只有在KeySet()和values方法被调用时,Map才会将KeySet和values实例化。
每个Map的entry实现都不一样
4.1 HashMap
·HashMap基于数组 + 链表的数据结构存储元素。对于hashMap,其存储是一个Node[]名叫table
·HashMap在插入键值对时,先将键值对封装成entry对象,并对key进行一系列计算,得出该entry的hash。通过hash计算entry在table[]中的index,将entry存储在table[]中。
如果index上已经存在entry对象,那么先将entry的next引用到index上已经存在的entry,再将待插入entry置入table[]的index上。
4.1.1 HashMap插入源码:
·先初始换一个数组,然后计算key的hash,得到在table中存储的位置,
然后遍历这个位置上的entry链表,如果发现存在这个key,(判断规则:key的hash值相等,并且key的值相等)
那么替换这个entry的value。如果没有发现相同的key,那么就在该位置插入一个entry
·在插入时,先对hashMap的table大小进行判断,如果容量不足则扩容并rehash,然后在指定位置插入entry
4.1.2 HashMap查找
·先计算key的hash,得到在table中的存储位置。遍历该位置上的entry链表,直到找到目标key(在hash相等的情况下,判断key的引用是同一个地址,或者key的值相等)
也是就是说,自定义的对象,在重新equals方法后,如果equals相等,即可作为相同的key来处理。
4.1.3 HashMap - 哈希冲突
·多个不同的key存储在table的同一个index上的情况,称为哈希冲突,HashMap的设计目标之一就是尽可能减少哈希冲突。
·jdk1.7中主要通过 ①优秀的hash算法,②table扩容与rehash两种办法减少hash冲突。
·hash算法:hashMap基于key.hashCode()获得一个32位二进制数,然后与自己位移后的数做与运算,使产生的数字加大随机性。
·hashmap的容量取决于数组table的容量(capacity)和hashMap的负载因子(capacity),HashMap的极限容量,threshold=capacity * loadFactor
·默认capacity为16,loadFactor=0.75 默认容量为16 * 0.75 = 12
·当HashMap中的Entry数量超过threshold时,便会扩容table并进行rehash
·如果当前table[]的长度大于等于threshold,并且要插入的index已经有entry对象,则将table扩容为当前长度的两倍
java8 新特性,java8的新特性中HashMap是数组+链表+红黑树实现的,当一个table的一个index下的链表长度大于8时,会转换为红黑树