java集合类
集合类存放于java.util包中。
集合类存放的都是对象的引用,而非对象本身,出于表达上的便利,我们称集合中的对象就是指集合中对象的引用(reference)。
集合类型主要有3种:set(集)、list(列表)和map(映射)。
集合接口分为:Collection和Map,list、set实现了Collection接口
(来自百度百科-Java集合类)
这里我们谈一谈list。
list表示的是列表,可以存储一组有重复对象的元素,且能维护元素之间的顺序。
根据上图我们可以看到list下我们常用的有Vector、ArrayList、LinkedList,
它们各自的类继承结构如下:
Collection<--List<--Vector<--Stack
Collection<--List<--ArrayList
Collection<--List<--LinkedList
Vector、ArrayList、LinkedList简单的异同:
异:
1. Vector: 内部是通过数组实现的,且它是支持线程同步,但实现同步需要花费很高的代价,因此,访问它比访问ArrayList慢。如果不考虑到线程的安全因素,一般用Arraylist效率比较高。
2. ArrayList:同Vector一样都是通过通过数组实现,但底层对操作没有加锁。
3. LinkedList:用双向链表结构存储数据的,适合数据的动态插入和删除,随机访问和遍历速度比较慢。还提供了List接口中没有定义的方法,专门用于操作表头和表尾元素,可以当作堆栈、队列和双向队列使用。
同:
1. Vector和ArrayList:由于二者都是基于数组实现的,所以不是从最后插入或者删除元素时,需要对数组进行复制、移动、代价比较高。因此,它适合随机查找和遍历,不适合插入和删除。
2. LinkedList和ArrayList:底层都没有对操作加锁,都是线程不安全的!
下面会对不同点进行具体分析。
Vector和ArrayList的区别(基于JDK1.8):
1. 虽然Vector和ArrayList底层都是Object数组,但它们的关键字却不一样
可以看到在ArrayList中,Object[]的关键字是transient的,而在Vector中只是protected。
那ArrayList的elementData数据不会在序列化后丢失吗?
被transient修饰的变量在序列化时将不会被序列化。
答案当然是不会。elementData之所以要设计为用transient修饰,是因为ArrayList内部有自动扩容机制,每当最小所需的空间大于elementData的长度时就会进行扩容,而且每次扩容为原来的1.5倍。对扩容机制不了解的可以参考ArrayList的扩容机制这篇博客。
因此有时候ArrayList的Object数组会存在一些未使用的区域,这些区域是不必序列化的,而且ArrayList中自己实现了write/readObject方法,仅仅去序列化有效的数据。
但至于为什么Vector的elementData不用transient修饰目前还不知道,希望知道的朋友能为我解惑 ( p_q)
2. 扩容的大小不一样
ArrayList每次扩容为原来的1.5倍;
Vector的扩容可以由用户在构造器中赋予capacityIncrement,没有给的话默认为0,这样就是每次扩容为原来的两倍。
注意它们的扩容都是调用的Arrays.copyOf()函数,对这个不清楚的可以看看Arrays.copyOf()方法详解。
总之就是会重新申请一个长度为newCapacity的数组,然后再把旧的数据拷贝过去,之前旧的数组则由会被jvm的垃圾收集器回收,在数据量很大的时候还是很耗资源的,所以最好还是避免它们频繁的自动扩容。
3. elementData的初始化大小默认值不同
可以发现ArrayList初始化大小是为空的Object数组,然后在你第一次往里面添加元素时会扩容到DEFAULT_CAPACTIY(为10)
而Vector则是初始化为大小为10的Object[]数组。
4. 对多线程的支持不一
Vector中的方法都是synchronized线程同步的,所以可以在多线程的场景下使用;
ArrayList中没有对操作加锁,所以是线程不同步的。
ArrayList和LinkedList的区别:
1. 底层的数据结构不同
由上面的分析已经可以知道ArrayList底层的数据结构是Object数组;
从上面我们可以看出LinkedList的底层数据结构是双向链表(在JDK 1.6之前是循环双向链表),LinkedList中通过first指针和last指针(也不知道叫指针恰不恰当,但感觉指针最好描述它)得到其首尾节点。
由于它们的底层数据结构的不同,所以在功能上就会有各自更擅长的方面,数组的话对于指定位置的查询可以做到O(1),而链表则只能一个个遍历,所以时间复杂度是O(n);但对于增加和删除操作,如果不是从末尾改变的话,如果链表在 i 位置操作,则需要将后面 n - i 个元素往前移动,所以时间复杂度为 O(n - i),而链表则可以通过改变next和prev的指向做到近似O(1)的时间复杂度。
所以当我们遍历LinkedList的时候需要特别注意!如果用for循环通过LinkedList的get()来取数据的话:
可以看到get()底层是通过for循环遍历获得数据的,这样的效率非常低,每次都要从头开始遍历一遍,所以最好要用iterator遍历(或者foreach,但foreach底层也是通过iterator实现的)
iterator是集合类帮我们封装的一个统一的遍历接口,因为集合类中有ArrayList、LinkedList、Map等多种数据结构,对他们的遍历方法都是不同的,所以为了让容器的使用者能够不用考虑遍历的实现问题,所以统一一个遍历接口以供容器使用者使用。
2. ArrayList实现了RandomAccess接口,而LinkedList没有
但是点进去可以发现RandomAccess什么也没有
我认为这个就像是Serializable接口一样,起到一个标识的作用
所以说这个只是一个标志,用来标识是否支持随机访问功能。
3. 占用空间的不同
ArrayList的底层为Object数组,且由于扩容机制,一般后面都会有预留一些空间。
LinkedList则是为了维护一条链表,需要在每个节点加上指向前驱和后继节点的指针。