集合入门
jdk1.2之前java官方提供的集合解决方案:
- Enumeration(枚举:对集合迭代接口)
- Vector(向量)
- Stack(栈)
- Hashtable(哈希表)
- Dictionary(字典)
- Properties(属性表)
以上集合工具在jdk1.2之前就已经存在,但是由于没有一个统一的标准,因此组织混乱,而且也存在部分bug.
从JDK1.2开始java中新增了集合API,用于将所有集合进行统一归纳,形成了两种集合的解决方案:
- 单例集合
- 双列集合
单列集合有一个顶层的接口:Collection
双列集合有一个顶层接口:Map
Collection
Collection是所有单列集合的顶层接口,在java中存在的有序集合(List)和无需集合(Set)接口都从Collection接口继承
由于Collection是一顶层集合接口,因此对于不同类型的集合也存在两个子接口分别进行处理:
- List:是一个有序的集合,并且允许重复的元素出现
- Set:是一个无序集合,并且不允许重复元素出现
Set和List区别?
List,Set都是继承自Collection接口。都是用来存储一组相同类型的元素的。
List特点:元素有放入顺序,元素可重复 。
有顺序,即先放入的元素排在前面。
Set特点:元素无放入顺序,元素不可重复。
无顺序,即先放入的元素不一定排在前面。 不可重复,即相同元素在set中只会保留一份。所以,有些场景下,set可以用来去重。
不过需要注意的是,set在元素插入时是要有一定的方法来判断元素是否重复的。这个方法很重要,决定了set中可以保存哪些元素。
List集合
List接口是一个有序的集合,内部允许重复(e1.equals(e2))的元素出现,并且元素的存储顺序是按照添加顺序存储
List接口有几个常见的实现类:
- ArrayList
- LinkedList
- Vector
ArrayList
ArrayList内部基于数组+数据拷贝的实现,初始容量是10,当添加的元素位置超出容量时,会在原数组的容量基础上扩充为1.5倍,ArrayList适合做查询不适合做修改(查快改慢)
LinkedList
LinkedList是基于双向链表的实现,在元素进行增删操作时,只需要修改链表的首尾指针即可轻松实现,LinkedList改快查慢
Vector
Vector是在JDK1.0就已经存在的基于动态数组(数组+数组拷贝)的集合实现,Vector是一个线程安全(关键方法上都使用了synchronized修饰)的集合实现,Vector在容量扩充时,增长为原理啊的2倍(ArrayList是1.5倍);
ArrayList和LinkedList和Vector的区别?
List主要有ArrayList、LinkedList与Vector几种实现。
这三者都实现了List 接口,使用方式也很相似,主要区别在于因为实现方式的不同,所以对不同的操作具有不同的效率。
ArrayList
是一个可改变大小的数组.当更多的元素加入到ArrayList中时,其大小将会动态地增长.内部的元素可以直接通过get与set方法进行访问,因为ArrayList本质上就是一个数组.
LinkedList
是一个双链表,在添加和删除元素时具有比ArrayList更好的性能.但在get与set方面弱于ArrayList.
当然,这些对比都是指数据量很大或者操作很频繁的情况下的对比,如果数据和运算量很小,那么对比将失去意义.
Vector
和ArrayList类似,但属于强同步类。如果你的程序本身是线程安全的(thread-safe,没有在多个线程之间共享同一个集合/对象),那么使用ArrayList是更好的选择。
Vector和ArrayList在更多元素添加进来时会请求更大的空间。Vector每次请求其大小的双倍空间,而ArrayList每次对size增长50%.
而 LinkedList 还实现了 Queue 接口,该接口比List提供了更多的方法,包括 offer(),peek(),poll()等.
注意:
默认情况下ArrayList的初始容量非常小,所以如果可以预估数据量的话,分配一个较大的初始值属于最佳实践,这样可以减少调整大小的开销。
Set集合
Set集合特点如下:
- 元素的存储顺序与添加顺序无关(无序)
- 内部不允许重复元素
Set由于是一个接口,因此对于该接口,集合框架内部提供了一些常见的实现类:
- HashSet
- LinkedHashSet
- TreeSet
HashSet
HashSet是基于哈希表的实现(内部实际就是一个HashMap),内部的元素存储根据调用元素的hashCode方法实现,由于对象的存储基于hashCode算法,因此如果多个对象的的hashCode值是一致的,则集合中只会存储一个(在重写hashCode方法时也必须要同时重写equals)
TreeSet
TreeSet内部实现原理是基于二叉树中的红黑树实现,使用TreeSet的前提:
- 元素必须是同一种数据类型
- 元素必须实现过Comparable接口
注意事项:
TreeSet内部对于元素的去除重复根据重写的comparaTo方法来决定,如果多个对象的comparaTo方法返回值是一致的,则集合中只会存储一个:
TreeSet中的元素对应的类如果未实现Comparable接口,则会抛出运行时异常:
LinkedHashSet
LinkedHashSet是从HashSet继承而来,内部的实现原理实际是基于LinkedHashMap.
在Java的Set体系中,根据实现方式不同主要分为两大类。HashSet和TreeSet。
1、TreeSet 是二叉树实现的,TreeSet中的数据是自动排好序的,不允许放入null值
2、HashSet是哈希表实现的,HashSet中的数据是无序的,可以放入null,但只能放入一个null,两者中的值都不能重复,就如数据库中唯一约束
在HashSet中,基本的操作都是有HashMap底层实现的,因为HashSet底层是用HashMap存储数据的。当向HashSet中添加元素的时候,首先计算元素的hashcode值,然后通过扰动计算和按位与的方式计算出这个元素的存储位置,如果这个位置位空,就将元素添加进去;如果不为空,则用equals方法比较元素是否相等,相等就不添加,否则找一个空位添加。
TreeSet的底层是TreeMap的keySet(),而TreeMap是基于红黑树实现的,红黑树是一种平衡二叉查找树,它能保证任何一个节点的左右子树的高度差不会超过较矮的那棵的一倍。
TreeMap是按key排序的,元素在插入TreeSet时compareTo()方法要被调用,所以TreeSet中的元素要实现Comparable接口。TreeSet作为一种Set,它不允许出现重复元素。TreeSet是用compareTo()来判断重复元素的。
Map集合
Map集合是由键值对结构组成,通常由一个唯一键对应一个值,集合中存储的元素键不允许重复,但是值可以重复,Map集合是一个键值对集合的顶层接口,常见的实现类主要有以下:
- HashMap
- TreeMap
- LinkeHashMap
- ConcurrentHashMap
虽然Map集合是一个键值对结构,但是实际内部存储的每一个元素都是一个Entry对象,而Entry内部包含两个属性,一个是Key,另一个是Value
HashMap
HashMap是Map中最常用的一个实现类,内部实现是基于数组+链表(JDK1.8之前;JDK1.8之后改为使用数组+链表+红黑树实现);元素的存储按照键值对的方式,存储顺序根据键的hashCode(包括equals)计算之后存储
HashMap的初始容量是16,默认的加载因子是0.75(降低hash碰撞的概率),HashMap的扩容方式为原来的2倍;实现原理:
- 在1.8之前使用的是数组和链表实现,默认情况下通过计算元素的hash值,然后和16取余(实际: hash & (length-1)),再根据计算结果将元素(Map.Entry)存储对应的数组中,如果该位置已经存在元素,则此时引入链表,采用头插法将最新的元素插入到链表头部。
- 在JDK1.8之后使用数组+链表+红黑树(平衡排序二叉树)实现,红黑树的加入有特定前提
- 数组的长度必须超过64
- 链表深度必须大于8
TreeMap
Map集合另外针对于排序的需求还有一个TreeMap的实现类,该类内部基于红黑树(平衡排序二叉树)实现;内部的元素存储顺序,由键对应的类型实现Comparable接口后,通过重写comparaTo方法实现;TreeMap的使用需要满足以下两个条件:
- key的类型必须一致
- key对应的类必须实现Comparable接口
TreeMap不允许空键出现
TreeMap会使用键对应的类型中的comparaTo方法来完成对于元素的排序存储
TreeMap中去除重复的原则是根据key类型中实现的comparaTo方法来实现,如果该方法返回值为0,则认为比较的两个对象重复,则将参数对象舍弃
Hashtable
Hashtable也是键值对结构的集合解决方案,从jdk1.0之后就已经存在,从老式的集合类java.util.Dictionary继承而来,初始长度是11(HashMap是16),Hashtable是线程安全实现(HashMap是线程不安全的实现);Hashtable不允许空键值出现(HashMap允许)
LinkedHashMap
LinkedHashMap是基于链表的HashMap实现,本身也是从HashMap继承而来,通过链表实现内部元素的存储顺序保持与添加顺序一致。
HashMap、HashTable、ConcurrentHashMap区别?
HashMap和HashTable有何不同?
线程安全:
HashTable中的方法是同步的,而HashMap中的方法在默认情况下是非同步的。在多线程并发的环境下,可以直接使用HashTable,但是要使用HashMap的话就要自己增加同步处理了。
继承关系:
HashTable是基于陈旧的Dictionary类继承来的。
HashMap继承的抽象类AbstractMap实现了Map接口。
允不允许null值:
HashTable中,key和value都不允许出现null值,否则会抛出NullPointerException异常。
HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。
默认初始容量和扩容机制:
HashTable中的hash数组初始大小是11,增加的方式是old*2+1。
HashMap中hash数组的默认大小是16,而且一定是2的指数。
哈希值的使用不同 :
HashTable直接使用对象的hashCode。
HashMap重新计算hash值。
遍历方式的内部实现上不同 :
Hashtable、HashMap都使用了Iterator。
Hashtable还使用了Enumeration的方式 。
HashMap 实现Iterator,支持fast-fail,
Hashtable的 Iterator 遍历支持fast-fail,用 Enumeration不支持 fast-fail
HashMap 和 ConcurrentHashMap 的区别?
ConcurrentHashMap和HashMap的实现方式不一样,虽然都是使用桶数组实现的,但是还是有区别,ConcurrentHashMap对桶数组进行了分段,而HashMap并没有。
ConcurrentHashMap在每一个分段上都用锁进行了保护。HashMap没有锁机制。所以,前者线程安全的,后者不是线程安全的。
PS:以上区别基于jdk1.8以前的版本。
Iterator(迭代器)
Iterator接口从jdk1.5之后新增的对集合进行快速遍历的接口,内部实现通常是由集合是类自身通过内部类的形式来完成,比如ArrayList:
常用方法
public boolean hashNext()
:判断迭代器是否存在下一个可以迭代的元素public E next()
:获取下一个迭代到的元素public void remove()
:删除当前迭代到的元素
ListIterator
ListIterator从Iterator实现而来,在Iterator的基础上扩充了一些方法:
public boolean hasPreviouse()
:是否存在上一个可迭代元素public E previouse()
:获取上一个迭代到的元素public void add(E e)
:通过迭代器向元素中添加一个元素public void set(E e)
:通过迭代器,修改当前位置的元素
Collections
Collections是从jdk1.2之后新增用于对集合进行操作的工具类
集合排序
Collections中用于实现集合排序的方法有如下两个:
- sort(List list)
- sort(List list,Comparator c)
深入研究以上两个方法的实现,不难看出集合中元素的排序主要依靠一下两个接口实现:
- Comparable
- Comparator
Comparable & Comparator
Comparable接口中提供了一个compareTo方法,该方法需要由排序类进行实现,根据方法内部的实现,Collections中的sort方法会依赖该实现对元素进行排序。
在Collections接口中还提供了另一种排序实现方式,即Collection.sort(list,comparator)
只需要传入集合与对应的排序比较器对象即可
扩展:中文排序(Pinyin4j)
这里可以使用开源的对中文字符处理的框架(插件):Pinyin4j(pinyin for java);这个插件提供了用于将中文汉字转换为汉语拼音的API,具体使用方式分为以下步骤:
-
将插件构建到项目中
pinyin4j-2.5.0.jar
-
使用pinyin4j
//将中文字符转换为拼音 String[] s = PinyinHelper.toHanyuPinyinStringArray('中'); for (String string : s) { System.out.println(string); } //执行结果: //zhong1 //zhong4
使用前需要导入:
import net.sourceforge.pinyin4j.PinyinHelper;
排序方式
思路:
- 将中文字符的每一位获取,并转换为拼音拼接陈新的字符串
- 直接使用String类中已实现的
comparaTo
方法进行比较返回整数值即可
集合框架总结
-
Iteratable:迭代器
-
Collection:所有单列集合的顶层接口
- List:有序允许重复元素的集合
- ArrayList:基于数组的实现
- LinkeList:基于链表(双向)实现
- CopyOnWriterArrayList(并发安全的动态数组)
Vector:基于数组的实现
- Set:不允许重复元素,无序的集合
- HashSet:基于HashMap实现
- LinkedHashSet:基于LinkedHashMap实现
- TreeSet:基于TreeMap的实现
- CopyOnWriterArraySet
- HashSet:基于HashMap实现
- List:有序允许重复元素的集合
-
Map:键值对的结构,内部的元素为Entry对象(Entry由key和value组成)
- HashMap:1.8之前使用数组+链表(单链表);1.8之后使用数组+链表+红黑树实现
- LinkedHashMap:按照添加顺序有序存储(数组+链表)
- TreeMap:基于红黑树的实现
Hashtable:键值对结构
- HashMap:1.8之前使用数组+链表(单链表);1.8之后使用数组+链表+红黑树实现
-
Comparable:由需要进行排序的类实现
-
Comparator:独立的比较器
-
Collections:对于集合操作的工具类型(内部很多实现是基于Arrays的)
-
Arrays:对于数组操作的工具类型