数据结构
Java Collections框架
Java Collections框架包含大量集合接口以及这些接口的实现类,和操作他们的算法(如排序/查找/反转/替换/复制/取最小元素/取最大元素等)。
Java Collections框架包括 List(列表)、Set(集合)、Map(映射表)、Queue(队列)、Stack(栈) 等数据结构。
其中 List/Queue/Set/Stack 都继承自Collection接口。
List
- 有序容器,保持了每个元素的进入顺序,输出的顺序就是插入的顺序。因为按对象进入的顺序保存对象,故能对列表中每个元素的插入和删除位置进行精确的控制。
- 允许重复的对象。可以插入多个null元素。
Set
- 无序容器,你无法保证每个元素的存储顺序,但TreeSet实现了SortedSet接口,因此TreeSet容器中的元素是有序的。
- 不允许重复对象,故存入Set的每个元素都必须定义equals()方法来确保对象的唯一性。只允许一个 null 元素。
- Set接口最流行的几个实现类是 HashSet、TreeSet以及 LinkedHashSet 。最流行的是基于 HashMap 实现的 HashSet;TreeSet 还实现了 SortedSet 接口,因此 TreeSet 是一个根据其 compare() 和 compareTo() 的定义进行排序的有序容器。
Map
- Map不是collection的子接口或者实现类。Map是一个接口。
- Map的每个Entry都持有两个对象,也就是一个键一个值,Map可能会持有相同的值对象但键对象必须是唯一的。
- Map里你可以拥有随意个null值但最多只能有一个null键。
什么场景下使用list/set/map?
- 如果你经常会使用索引来对容器中的元素进行访问,那么List是你的正确的选择。如果你已经知道索引了的话,那么List的实现类比如 ArrayList可以提供更快速的访问,如果经常添加删除元素的,那么肯定要选择LinkedList。
- 如果你想容器中的元素能够按照它们插入的次序进行有序存储,那么还是List,因为List是一个有序容器,它按照插入顺序进行存储。
如果你想保证插入元素的唯一性,也就是你不想有重复值的出现,那么可以选择一个Set的实现类,比如HashSet、TreeSet或者LinkedHashSet。如果插入的元素唯一且有序可以使用TreeSet,TreeSet中的元素可以使用Java里的Comparator或者Comparable进行排序。 - 如果你以键和值的形式进行数据存储那么Map是你正确的选择。
List的实现类:ArrayList/Vector/LinkedList
这三个类在java.util包中。
ArrayList和Vector为可伸缩数组(即可以动态改变长度的数组)。基于数组实现的列表。他们会在内存中开辟一块连续的空间来存储数据,故支持用下标访问元素,同时索引数据的速度比较快。但是插入元素时需要移动数组中的元素,所以对数据的插入操作执行地比较慢。
ArrayList和Vector都有一个初始化的容量大小,当数组中存储的元素超过这个大小时,就需要扩充他们的存储空间。为了提高程序的效率,每次扩充容量会一次增加多个存储单元,而不是一个存储单元。ArrayList默认扩充为原来的1.5倍(没有提供方法来设置空间扩充的方法),Vector默认扩充为原来的2倍(每次扩充空间的大小是可以设置的)。
ArrayList中没有一个方法是同步的(synchronization),Vector的绝大多数方法(add/insert/remove/set/equals/hashcode等)都是直接或者间接同步的,所以Vector是线程安全的,ArrayList不是线程安全的。故Vector性能略逊于ArrayList。
LinkedList是基于双向链表实现的。对数据的索引需要从列表头开始遍历,因此索引数据的速度较慢。但插入元素不需要对数据进行移动,因此插入效率较高。LinkedList是非线程安全的容器。
因此,当对数据的主要操作为索引或只在集合的末端增加、删除元素时,使用ArrayList或Vector效率比较高;当对数据的操作主要为指定位置的插入或删除操作时,使用LinkedList效率比较高;当在多线程中使用时(即多个线程会同时访问该容器),选用Vector较为安全。
Map的实现类:HashMap/Hashtable/TreeMap/LinkedHashMap/WeakHashMap
java.util.Map
在List中通过下标来对其内容进行索引。在Map中通过对象(key)对其内容(value)进行索引。
HashMap继承AbstractMap同时实现Map
Hashtable继承Dictionary同时实现Map
TreeMap继承AbstractMap同时间接实现SortedMap
LinkedHashMap继承HashMap同时实现Map
WeakHashMap继承AbstractMap同时实现Map
HashMap和Hashtable的区别
- HashMap最多允许一个空键(key为null),Hashtable不允许。
- HashMap非线程安全,Hashtable线程安全。故HashMap性能略高。
- HashMap的hash数组默认大小是16,且一定是2的指数。Hashtable的hash数组默认大小是11,增加的方式是old * 2 + 1。
- HashMap使用Iterator迭代器获取对象。Hashtable使用Enumeration获取集合(容器)中的对象。
Enumeration接口中定义了一些方法,通过这些方法可以枚举(一次获得一个)集合中的元素。 - HashMap使用containsKey和contatinsValue。Hashtable使用contains。
TreeMap和LinkedHashMap
HashMap里面存入的键值对在取出时没有固定的顺序,是随机的。
TreeMap实现了SortMap接口,可以把它保存的键值对根据key排序。因此,如果需要按自然顺序或自定义顺序遍历key,则TreeMap好用。
LinkedHashMap是HashMap的一个子类,如果需要输出的顺序和输入的顺序相同,则LinkedHashMap好用。
WeakHashMap
WeakHashMap与HashMap类似,二者不同在于WeakHashMap中key采用的是弱引用的方式,只要key不再被外部引用,他就可以被GC回收。
而HashMap中key采用强引用的方式,当key没有外部引用时,只有这个key从HashMap中删除后,才可以被GC回收。
Map注意事项
- Hashtable的线程安全指什么?
线程安全(同步)指在一个时间点,只能有一个线程可以修改hash表,任何线程在执行Hashtable的更新操作前都需要获取对象锁。 - 如何实现HashMap的同步?
通过如下方式:
Map map = Collections.synchronizedMap(new HashMap())
该方法返回一个同步的HashMap,该HashMap封装了底层的所有方法来保证该HashMap线程安全。 - 为什么HashMap可以存为null的key?
这是因为HashMap对null进行了特殊处理,将null的hashCode值定为了0,从而将其存放在哈希表的第0个存储空间(bucket)。
数组和链表
数组
- 数组是连续的内存空间。
- 数组必须事先定义固定的长度。
- 数组无需初始化,因为数组的元素在内存的栈区,系统自动申请空间,即静态分配内存。
- 访问数据,可以通过下标读取,时间复杂度O(1),随机访问性强。
- 添加删除数据需要移动操作数据所在位置后的所有数据,时间复杂度O(N)。
链表
- 链表是非连续的内存空间。
- 链表的结点元素在内存的堆区,每个元素须手动申请空间,如malloc。堆操作灵活性更强(添加删除数据)。堆中分配空间,自由度大但申请管理比较麻烦。
- 访问数据,需要从头遍历整个链表直到找到要访问的数据,随机访问性弱。时间复杂度O(N)。
- 添加删除数据只需要知道操作位置的指针。时间复杂度O(1)。
线性表
线性表:零个或多个数据元素的有限序列。
注:序列是指元素之间是有顺序的。
线性表包括:
- 顺序存储结构的线性表
- 链式存储结构的线性表(单向链表/双向链表/循环链表)
栈:
用顺序线性表实现(可用两栈共享空间)
用链式线性表实现
队列:
用顺序线性表实现(可用循环队列)
用链式线性表实现
栈和队列是线性表的一种。