2.1集合
Java的集合类被定义在Java.util包中,主要分为两类:Collection、Map,其次又可以被分为List、Queue、Set和Map四类集合
Collection集合主要用来存储的是独立的元素,其中set、list和Queue,其中list是按照插入的顺序保存元素,而set中则不能有重复的元素,而Queue按照排队规则来处理容器中的元素。Map的则是用来存储<键,值>对,这个容器允许通过键来查找值。
List集合:ArrayList、LinkedList和Vector
Queue:ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue、DelayQueue、SynchronousQueue、LinkedTransferQueue、LinkedBlockingQueue
Set:HashSet、TreeSet和LinkedHashSet
Map:HashMap、HashTable、TreeMap
2.1.1 List:可以重复的集合
List是一种线性的列表结构,它继承自Collection接口,是一种有序集合,List中的元素可以根据索引进行检索、删除或者插入操作。在Java中List接口中主要以下常用的List实现类。
1.ArrayList:基于数组实现的,查询快、增删慢,线程不安全
ArrayList是用数组来实现的List,主要提供了对List的增加add、删除remove和访问get功能。其主要的特点是随机访问效率高(相对于顺序访问来说),读快写慢(主要由于写的过程中需要涉及元素的移动,因此写操作的效率比较低)
ArrayList的缺点是对元素必须连续存储,因此ArrayList不适合随机插入和删除的操作,更适合随机查找和遍历的操作
ArrayList不需要再定义时指定数组的长度,在数组长度不能满足存储要求时,ArrayList会创建一个更大的数组并将数组中已有的数据复制到新的数组中
ArrayList的实现原理
(1)父类和接口
java.util.AbstractList.该抽象类是大部分List的共同子类,它提供了一些基本的方法以及通用迭代器的实现
java.util.List。列表标准接口,列表是一个有序集合,又被称为序列,该忌口对它内部的每一个元素的插入位置都有精确控制,用户可以使用整数索引来查询
java.util.RandomAccess.这是一个标记性质的随机访问接口,它没有提供任何方法。如果一个类实现了这个接口,那么表示这个类使用索引遍历比迭代器要更快
java.lang.Cloneable。用于标记可克隆对象,是一个常见接口,没有实现该接口的对象在调用Object.clone()方法时会抛出异常
java.io.Serializable。序列化标记接口,是一个常见接口,被此接口标记的类可以实现Java序列化和反序列化。该接口没有任何内容。
(2)成员变量和常量
ArrayList有三个重要多的成员和两个常量
1)成员变量
private transient Object[] elementData,elementData是该List的数据域,其中被transient修饰表示这个变量不会被序列化,它提供给Serializable接口使用
private int size,size表示当前List的长度,需要注意的是,elementData的length必然是大于或等于size的。这是因为elementData是存放数据的数组提供了size变量来标识真正的List的大小
protected transient int modCount=0,该成员变量继承自AbstractList,记录了ArrayList结构性变化的次数。
2)常量
private static final long serialVersionUID=2343424244L
序列化版本UID,根据这个名字能判断出它是提供给序列化接口使用的,该UID是为了维持序列化版本一致性的
private static final int Max——ARRAY_SIZE=Integer.MAX_VALUE-8
数组长度的上限,这里设置的是最大整数-8
Vector:基于数组实现的,增删慢、查询快,线程安全
Vector的实现与ArrayList基本是一致的,都是基于数组实现的,不同的是Vector支持线程同步,即同一时刻只允许一个线程对Vector进行写操作,以保证多线程环境下数据的一致性,但需要频繁地对Vector实例进行加锁和释放锁操作,因此Vector的读写效率在整体上比ArrayList低。
Vector所有的public方法是使用synchronized关键字,Vector多了一个成员变量capacityIncrement,用于标明扩容的增量,与ArrayList固定扩容50%相比,Vector根据capacityIncrement的数值来扩容
需要注意的是stack是Vector的子类,因此它的实现和Vector是一致的
LinkedList:基于双向列表实现的,增删快,查询慢,线程不安全
LinkedList采用双向链表结构存储元素,在对LinkedList进行插入和删除操作时,只需要在对应的节点上插入或删除元素,并将上一个节点的下一个节点的指针指向该节点即可,数据改动比较小,因此随机插入和删除效率很高。但是在对LinkedList进行随机访问时,需要从链表头部一直遍历到该节点位置,因此随机访问很慢。初次以外,LinkedList还提供了在List接口未定义的方法,用于操作链表头部和尾部的元素,因此有时可以被当做堆栈、队列和双向队列使用
2.1.2 Queue
Queue是队列结构,本身是一种先入先出的模型,Queue是一个接口,有的类采用线性表来实现的,有的则基于链表实现的,有些类是多线程安全的,有些则不是。
PriorityBlockingQueue:基于优先级排序的无界阻塞队列
该队列并不遵循陷入先出的原则,它会根据队列元素的优先级来调整顺序,优先级最高的元素最先出,PriorityQueue提供了一个comparator成员变量来对元素进行比较
transient Object[] queue;//存储数组
privateint size =0;//元素数量
private final Comparator<? super E> comparator;//比较器
ArrayBlockingQueue:基于数组结构实现的有界阻塞队列
LinkedBlockingQueue:基于链表数据结构的有界阻塞队列
DelayQueue:支持延迟操作的无界阻塞队列
SynchronousQueue:用于线程同步的阻塞队列
LinkedTransferQueue:基于链表数据结构实现的无界阻塞队列
LinkedBlockingDeque:基于链表数据结构实现的双向阻塞队列
2.1.3 Set:不可重复的集合
Set的核心是独一无二的性质,适用于存储无序且值不相等的元素。对象的相等性本质上是对象的HashCode值相同,Java依据对象的内存地址计算出对象的HashCode值。如果想要比较两个对象是否相等,则必须同时覆盖对象的hashCode方法和equal方法,并且hashCode方法和equals方法的返回值必须相同
1.HashSet:HashTable实现,无序
HashSet存放的是散列值,它是按照元素的散列值来存取元素的。元素的散列值是通过hashCode方法计算得到的,HashSet首先判断两个元素的散列值是否相当,如果散列值相等,则接着通过equals方法进行比较,如果equals方法返回的结果也为true,HashSet就将其视为同一个元素;如果equals方法返回的结果为false,HashSet就不将其视为同一个元素
2.TreeSet:二叉树实现
TreeSet基于二叉树的原理对新添加的元素按照指定的顺序排序,每添加一个对象都会进行排序,并将对象插入指定的位置。
Integer和String等基础对象类型可以直接根据TreeSet的默认排序进行存储,而自定义的数据类型必须实现Comparable接口,并且覆写其中的compareTo函数才可以按照预定义的顺序存储。若覆写compare函数,则在升序时在this.对象小于指定对象的条件下返回-1
3.LinkHashSet:HashTable实现数据存储,双向链表记录顺序
LinkedHashSet在底层使用额LinkedHashMap存储元素,它继承了HashSet,所有的方法和操作都与HashSet相同,因此其实现比较简单,只提供了四个构造方法,并通过传递一个标识参数调用父类的构造器,在底层构造一个LinkedHashMap来记录数据访问,其它相关操作与父类HashSet相同,直接调用父类多的HashSet的方法即可
2.1.4 Map
Map是一种由多组key-value(键值对)集合在一起的结构,其中key值是不能重复的,而value值则无此限定。
HashMap
HashMap是最长用的Map结构,Map的本质是键值对。它使用数组来存放这些键值对,HashMap基于键的HashCode值唯一标识一条数据,同时基于键的HashCode值进行数据的存取,因此可以快读地更新和查询数据,但其每次遍历的顺序无法保证相同。HashMap的key和value允许为null
在HashMap中,当且仅当hashCode一致且equals比对一致的对象才会被认为是同一个对象
Java8之前的HashMap
HashMap的底层实现是数组和链表
(1)成员变量
transient Entry<K,V> [] table;//存储数据的核心成员变量
transient int size;//HashMap中键值对的数量
final float loadFactor;//加载因子,用于决定table的扩容量
table是HashMap的核心成员变量,该数组用于记录HashMap的所有数据,它的每一个下标都对应一条链表,换言之所有哈希冲突的数据都会被存放到同一条链表中。Entry<K,V>则是该链表的节点元素
final K key存放键值对中的关键字
V value存放键值对中的值
Entry<K,V> next指向下一个节点的引用
int hash key所对应的hashcode
HashMap的核心实现就是一个单向链表数组(Entry<K,V>[] table),HashMap规定了该数组的两个特性:
1)会在特定时刻根据需要来扩容2)其长度始终保持为2的幂次方
思考一个问题,如果一个Object的对象对应的hashcode为51,上面有个字符串同时存入HashMap值也是51,那在存入的时候会怎么处理/
答案是它会被存入链表里和之前的字符串同时存在,当需要查找指定的对象的时候,首先会找到hashcode 的下标,然后遍历链表,调用对象的equals方法进行比对从而找到对应的对象。
由于数组的查找是比链表要快的,因此尽可能使得键值的hashcode分散,这样就可以提高hashmap的查询效率
HashMap是非线程安全的,即在同一时刻有多个线程同时写HashMap时将可能导致数据的不一致。如果需要满足线程安全的条件,则可以使用Collections的synchronizedMap方法使得HashMap具有线程安全的能力,或者使用ConcurrentHashMap
为了减少链表遍历的开销,Java8对HashMap进行了优化,将数据结构修改为数组加链表或红黑树。在链表结构中的元素超过8个以后,HashMap会将链表结构转换为红黑树结构以提高查询效率,因此其时间复杂度为O(logN)
在Java8中数据类型发生了变化,代表的链表节点的Entry<K,V>换成了Node<K,V>,Node本身具备链表节点的特性,同时它还有一个子类TreeNode<K,V>,从名字可以看出是一个树节点
ConcurrentHashMap:分段锁实现,线程安全
与HashMap不同是ConcurrentHashMap采用分段锁的思想实现并发操作,因此是线程安全的。ConcurrentHashMap由多个Segment组成(Segment的数量也是锁的并发度),每个Segment均继承自ReentrantLock并单独加锁,所以每次进行加锁操作时锁住的都是一个Segment,这样也就保证每个Segment都是线程安全的,也就实现了全局的线程安全。
在ConcurrentHashMap中有个concurrencyLevel参数表示并行级别,默认是16,也就是说ConcurrentHashMap默认由16个Segments组成,在这种情况下最多同时支持16个线程并发执行写操作,只要它们的操作分布在不同的Segment上即可。并行级别concurrencyLevel可以在初始化时设置,一旦初始化就不可更改。ConcurrentHashMap的每个内部的数据结构都和HashMap相同。
Java 8中在ConcurrentHashMap中引入了红黑树
HashTable:线程安全
HashTable的实现与HashMap很类似,整体流程也是没有太大的变化的,不同的是它继承自Dictionary类,并且是线程安全的,同一时刻只有一个线程能写HashTable,并发性不如ConcurrentHashMap
从源码可以看出HashTable的实现方式被synchronized修饰,由此可见HashTable是线程安全的,而HashMap是线程不安全的;此外Hashtable不能存放null作为key值
虽然HashTable是线程安全的但是在多线程环境下并不推荐使用。因为采用synchronized方式实现的多线程安全的容器在大并发量的情况下效率比较低下
TreeMap:基于二叉树数据结构
TreeMap基于二叉树数据结构存储数据,同时实现了SortedMap接口以保障元素的顺序存取,默认按键值的升序排序,也可以自定义排序比较器
TreeMap常用于实现排序的映射列表。在使用TreeMap时其key必须实现Comparable接口采用自定义的比较器,否则会抛出java.lang.ClassCastException异常
LinkedHashMap:基于HashTable数据结构,使用链表保存插入顺序
LinkedHashMap为HashMap的子类,其内部使用链表保存元素的插入顺序,在通过Iterator遍历LinkedHashMap时,会按照元素的插入顺序访问元素。LinkedHashMap主要有两个成员变量需要注意
private transient Entry<K,V> header;
private final boolean accessOrder;//true为顺序访问,false为逆序
LinkedHashMap中包含了一个额外的双向链表结构,header既是头又是尾,可以视作一个环状链表,LinkedHashMap可以像HashMap一样的使用,同时它为每个数据结点的引用多维护一个链表,从而可以达到有序访问的目的。