Java集合
集合、数组都是对多个数据进行存储操作的结构,简称java容器
-
数组具有局限性:
-
数组的长度是固定的
-
数组无法同时存储多个不同的数据类型
-
数组提供的方法非常有限,对于CRUD操作等操作非常不便且效率不高
-
-
集合的架构
集合简单理解就是一个长度可以改变,可以保持任意数据类型的动态数组。Java中的集合不是由一个类来完成的,而是一组接口和类构成了一个框架体系,大致可分为3层,最上层时一组接口,继而是接口的实现类。
-
集合框架的接口
-
Collection:集合框架最基础的接口、最顶层的接口;单列合集,用来存储一个一个的对象;集合框架最基础的接口、最顶层的接口。
-
List(最常用的接口):Collection的子接口,存储有序的、可重复的数据(动态数组)
-
ArrayList
-
LinkedList
-
vetor
-
-
Set:Collection的子接口,存储有序的、不可重复的数据(数学上所讲的集合)
-
HashSet
-
LinkedHashSet
-
TreeSet
-
-
-
Map:独立于Collection的另外一个接口;双列集合,用来存储一对一对的数据(key - value)(函数:y = f(x));
-
HashMap
-
LinkedHashMap
-
TreeMap
-
Hashtable
-
Properties
-
-
Iterator:输出集合元素的接口,一般适用于无序集合,从前往后输出。
-
Listiterator:Iterator的子接口,可以双向输出
-
-
Collection接口
Collection是集合框架中最基础的父接口,可以存储一组无序、不唯一的对象;一般不直接使用该接口,也不能被实例化,只是用来提供规范。
Collection是Iterable接口的子接口。
Collection接口的方法:
| 方法 | 描述 |
|---|---|
| int Size() | 获取添加的元素的个数 |
| void clear() | 清空集合元素 |
| boolean isEmpty() | 判断当前集合是否为空 |
| boolean add(E e) | 将元素e添加到集合coll中 |
| boolean addAll(Collection coll) | 将coll集合中的元素添加到当前的集合中 |
| boolean remove(Object obj) | 从当前集合中移除obj元素 |
| boolean removeAll(Collection coll) | 从当前集合中移除coll中所有元素 |
| boolean contains(Object obj) | 判断当前集合是否包含obj |
| boolean containsAll(Collection coll) | 判断形参coll中的所有元素是否都存在于当前的集合中 |
| coll.retainAll(Collection coll) | 差集:从当前集合中移除coll中所有的元素 |
| boolean equals(Object obj) | 要想返回true,需要当前集合和形参集合的元素都相同 |
| int hashCode() | 返回当前对象的哈希值 |
| Object[] toArray() | 将集合转为数组 |
| T[] toArray(T[] a) | 将集合转换为一个指定数据类型的数组 |
| iterator<E> iterator() | 返回Iterator接口的实例,用于遍历集合元素 |
Collection的子接口:
-
List:有序,按对象进入的顺序保存对象,可重复,允许多个Null元素对象,可以使用iterator取出所有元素,再逐一遍历,还可以使用get(int index)获取指定下标的元素
-
Set:无序,不可重复,最多允许有一个Null元素对象,取元素时只能用iterator接口取得所有元素,再逐一遍历各个元素
List接口
List是Collection最常用的一个子接口,可以存储有序,可重复的对象。
List常用的扩展方法:
| 方法 | 描述 |
|---|---|
| T get(int index) | 通过下标返回集合中对应位置的元素 |
| T set(int index, T element) | 在集合中的指定位置存入对象 |
| int indexOf(Object o) | 从前向后,查找某个对象在集合中的位置 |
| int lastIndexOf(Object o) | 从后向前,查找某个对象在集合中的位置 |
| Listiterator<E> listIterator() | 实例化ListIterator接口,用来遍历List集合 |
| List<E> subList(int fromIndex, int toIndex) | 通过下标截取List集合 |
List接口的实现类:
-
ArrayList:
ArrayList是开发中使用频率最高的List实现类,实现了长度可变的数组,在内存分配连续空间,底层使用Object[] elementData存储,所以读取快,增删慢;线程不安全,效率高;
ArrayList的底层实现:
// JDK8以前:
ArrayList list = new ArrayList(); // 底层创建了长度是10的Object[] 数组 elementData
list.add(123) // elementData[0] = new Integer(123);
...
list.add(123) // 如果此次添加导致底层的elementData数组容量不够,则扩容
// 默认情况下,扩容为原来的1.5倍,同时需要将原有数组的数据复制到新的数组中
// 建议开发中使用带参的构造器:ArrayList() = new ArrayList(int capacity);// JDK8以后:
ArrayList() list = new ArrayList(); // 底层Object[] elementData初始化为{},并没有创建长度为10的数组
list.add(123); // 当第一次调用add()时,底层才创建了长度10的数组,并将数据添加到elementData数组中
// 扩容机制同 jdk7jdk8之前的ArrayList的对象的创建类似于单例的饿汉式,而jdk8以后的ArrayList的对象的创建类似于单例的懒汉式延迟了数组的创建,节省内存
-
LinkedList:对于频繁地插入和删除操作,使用此类效率比ArrayList高;底层使用双向链表存储,实现了先进先出的队列
LinkedList的底层实现:
LinkedList list = new LinkedList(); // 内存声明了Node类型的first和last属性,默认值为null
list.add(123) // 将123封装到Node中,创建了Node对象
// 其中,node定义为:
private static class Node<E>{
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next){
this.item = element;
this.next = next;
this.prev = prev;
}
} -
Vector:作为List接口的古老实现类;线程安全,效率低
-
Stack:Vector的子类,实现类栈的数据结构
-
ArrayList和LinkedList的区别:
-
ArrayList基于动态数组,连续内存存储,适合下标访问(随机访问),扩容机制:因为数组长度固定,超出长度存数据时需要新建数组,然后将老数组的数据拷贝到新数组,如果不是尾插法插入数据还会涉及到元素的移动(往后复制一份,插入新元素),使用尾插法并指定初始容量可以极大提升性能、甚至超过linkedlist(需要创建大量的node对象)
-
LinkedList基于链表,可以存储在分散的内存中,适合做数据插入及删除操作,不适合查询,需要逐一遍历;便利Linkedlist必须使用iterator不能使用for循环,因为每次for循环体内通过get(i)取得某一元素时都需要对list重新进行遍历,性能消耗极大;另外不要试图使用indexOf等返回元素索引,并利用其进行遍历,使用indexOf对list进行了遍历,当结果为空时会遍历整个列表
Set接口
跟List一样,Set也是Collection的子接口,Set集合是以散列的形式存储数据,所以元素时没有顺序的,可以存储一组无序且唯一的数据;Set接口中没有额外定义的新方法,都是Collection中声明过的方法;向Set中添加的数据,其所在类一定要重写hashCode()和equals(),且二者尽可能保持一致性(相等的对象必须具有相等的散列码)。
hashCode与equals:
hashcode()作用是获取哈希码,也称为散列码,它实际上是返回一个int整数。这个哈希码的作用是去确定该函数对象在哈希表中的索引位置。hashCode()定义在JDK的Object.java中,java中的任何类都包含由hashCode()函数。
在HashSet中判断两个值是否相等,会先判断二者的hashCode,若hashCode不等,则判定二者不等;若hashCode相等,再使用equals判断二者是否相等。这样能大大地减少equals的次数,提高执行速度。要注意的是,如果equals()方法被重写,则hashCode()方法也必须被重写。
Set接口的实现类:
-
HashSet
HashSet是开发中经常使用的一个实现类,存储一组无序且唯一的对象;线程不安全;可以存储null值。
-
无序:不等于随机性;存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值决定的(即存储顺序和比那里顺序不同)
-
不可重复性:保证添加的元素按照equals()判断时,不能返回true;即相同的元素只能添加一个
HashSet元素添加过程:我们向HashSet添加元素a,首先调用a所在类的hashCode()方法,计算元素a的哈希值,此哈希值接着通过某种计算方法算出HashSet底层数组中的存放位置(即为:索引位置),判断数组此位置上是否已经有元素:
如果此位置上没有其他元素,则元素a添加成功;→ 情况1
如果此位置上有其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的hash值:
如果hash值不相同,则元素a添加成功;→ 情况2
如果hash值相同,进而需要调用元素a所在类的equals()方法:
equals()返回true,元素a添加失败
equals()返回false,则元素a添加成功---> 情况3
对于添加成功的情况2和情况3而言:元素a 与已经存在指定索引位置上数据以链表的方式存储:
-
jdk 7:元素a放到数组中,指向原来的元素
-
jdk 8:原来的元素在数组中,指向元素a
-
-
LinkedHashSet
作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序遍历;在添加数据的同时,每个数据还维护了两个引用,记录此数据前驱和后继;对于比较频繁的遍历操作,LinkedHashSet效率高于HashSet
-
TreeSet
可以按照添加对象的指定属性,进行排序;所以向TreeSet中添加的数据,要求是相同类的对象;
值得注意的是HashSet和TreeSet都是有序的,但是有序的含义不同;HashSet中有序的含义是存储顺序和遍历顺序一致,TreeSet的有序是指集合内部会自动对所有的元素按照升序进行排列,无论存入的顺序是什么,遍历的时候一定按照升序输出
Map接口
Map是本质上就是一个 key-value 数据字典。
Map中的key是无序的、不可重复的,使用Set存储所有的key;Map中的value是无序的、可重复的,使用Collection存储所有的value
一个键值对:key-value构成了一个entry对象,其是无序的、可重复的,使用Set存储所有的entry。
Map 接口定义时使用了泛型,并且定义两个泛型 K 和 V,K 表示 key,规定键元素的数据类型,V 表示 value,规定值元素的数据类型。
Map接口的方法:
| 方法 | 描述 |
|---|---|
| int size() | 获取集合长度 |
| boolean isEmpty() | 判断集合是否为空 |
| boolean containsKey(Object key) | 判断集合中是否存在某个 key |
| boolean containsValue(Object value) | 判断集合中是否存在某个 value |
| V get(Object key) | 取出集合中 key 对应的 value |
| V put(K key,V value) | 向集合中存入一组 key-value 的元素 |
| V remove(Object key) | 删除集合中 key 对应的 value |
| void putAll(Map map) | 向集合中添加另外一个 Map |
| void clear() | 清除集合中所有的元素 |
| Set keySet() | 取出集合中所有的 key,返回一个 Set |
| Collection values() | 取出集合中所有的 value,返回一个 Collection |
| Set<Map.Entry<K,V>> entrySet() | 将 Map 以 Set 的形式输出 |
| int hashCode() | 获取集合的散列值 |
| boolean equals(Object o) | 比较两个集合是否相等 |
Map的实现类:
-
HashMap:为Map的主要实现类,存储一组无序,key不可以重复,value可以重复;线程不安全,效率高;可以存储null的key和value
HahsMap底层原理:
// jdk8之前
HashMap map = new HashMap() // 在实例化后,底层创建了长度是16的一维数组Entry[] table
//...可能已经执行过多次put...
map.put(key1, value1);
// 首先,调用key1所在类的hashCode()计算key1哈希值,此哈希值经过某种算法计算后,得到在Entry数组中的存放位置
// 如果此位置上的数据为空,此时的key1-value1添加成功 ---> 情况1
// 如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(以链表形式存在)),比较key1和已经存在额一个或多个数据的哈希值:
// 如果key1的哈希值与已经存在的哈希值都不相同,此时key1-value1添加成功 ---> 情况2
// 如果key1的哈希值和已经存在的某个数据(key1-value2)的哈希值相同,继续比较,调用key1所在类的equals()方法,比较:
// 如果equals()返回false,此时key1-value1添加成功 ---> 情况3
// 如果equals()返回true,使用value1替换value2
// 情况2和情况3,key1-value1和原来的数据以链表方式存储
// 默认的扩容方式:扩容为原来容量的2倍,并将原有的数据复制过来// jdk8之后
HashMap map = new HashMap(); // 底层没有创建一个长度为16的数组
map.put(key1, value1); // 首次调用put()方法时,底层创建长度为16的 Node[] 数组
// 底层结构为:数组 + 链表 + 红黑树
// 当数组的某一个索引位置上的元素以链表形式的存在数据个数 > 8,且当前数组的长度 > 64时,此时此索引位置上的所有的数据改为使用红黑树
// DEFAULT_INITAL_CAPACITY :HashMap的默认容量,16
// DEFAULT_LOAD_FACTOR :HashMap的默认加载因子,0.75
// threshold :扩容的临界值,= 容量 * 加载因子,16 * 0.75 = 12
// TREEIFY_THRESHOLD :Bucket中链表长度大于该默认值,转化为红黑树
// MIN_TREEIFY_CAPACITY :桶中的Node被树化时最小的hash表容量,64 -
LinkedHashMap:在原有的HashMap底层结构基础上,添加了一对指针,指向前驱和后继;保证在遍历map元素时,可以按照添加的顺序实现遍历;对于频繁的遍历操作,此类执行效率要高于HashMap
-
TreeMap:底层使用的是红黑树;保证按照添加的key-value对进行排序,实现排序遍历;此时考虑key的自然排序和定制排序
-
HashTable:
-
HashTable:作为古老的实现类;线程安全,效率低;不能存储null的key和value
-
Properties:常用来处理配置文件;key和value都是String类型
Properties pros = new Properties();
FileInputStream fis = new FileInputStream("jdbc.properties");
pros.load(fis) // 加载对应的流文件
String name = pros.getProperty("name");
fis.close();
-
HashMap和HashTable的区别:
-
区别:
-
HashMap方法没有synchronized修饰,线程不安全,HahsTable线程安全
-
HashMap允许key和value为null,HashTable不允许
-
-
底层实现:数组 + 链表
jdk8开始链表高度到8、数组长度超过64,链表转变为红黑树,元素以内部类Node节点存在;
存储元素的步骤:
-
计算key的hash值,二次hash然后对数组长度取模,对应到数组下标
-
如果没有产生hash冲突(下标位置没有元素),则直接创建Node存入数组
-
如果产生hash冲突,先进行equal比较,相同则取代该元素;不同则判断链表高度插入链表,链表高度达到8,并且数组长度到64则转变为红黑树,长度低于6则将红黑树转回链表
-
key为null,存在下标0的位置
数组扩容:默认扩容方式为扩容为原来容量的2倍,并将原有的数据复制过来
-
ConcurrentHashMap原理,jdk7和jdk8版本的区别:
-
jdk7:
数据结构:ReentranLock+Segment+HashEntry,一个Segment中包含一个HashEntry数组,每个HashEntry又是一个链表结构
元素查询:二次hash,第一次查询定位到Segment,第二次Hash定位到元素所在的链表的头部
锁:Segment分段锁,Segment继承了ReentranLock,锁定操作的Segment,其他的Segment不受影响,并发度为segment个数,可以通过构造函数指定,数组扩容不会影响其他的Segment
get方法无需加锁,volatile保证
-
jdk8
数据结构:synchronized+CAS+Node+红黑树,Node的val和next都用volatile修饰,保证可见性
查找,替换,赋值操作都是使用CAS
锁:锁链表的head节点,不影响其他元素的读写,微粒度更细,效率更高,扩容时,阻塞所有的读写操作、并发扩容
读操作无锁:
Node的val和next使用volatile修饰,读写线程对该变量互相可见
数组用volatile修饰,保证扩容时被读线程感知
Collections 工具类
Collections不是接口,它是一个工具类,专门提供了一些对集合的操作,方便开发者去使用,完成相应的业务功能。
Colletions 针对集合的工具类,Collection
Arrays 针对数组的工具类,Array
| 方法 | 描述 |
|---|---|
| public static sort() | 对集合进行排序 |
| public static int binarySearch(List list,Object v) | 查找 v 在 list 中的位置,集合必须是升序排列 |
| public static get(List list,int index) | 返回 list 中 index 位置的值 |
| public static void reverse(List list) | 对 list 进行反序输出 |
| public static void swap(List list,int i,int j) | 交换集合中指定位置的两个元素 |
| public static void fill(List list,Object obj) | 将集合中所有元素替换成 obj |
| public static Object min(List list) | 返回集合中的最小值 |
| public static Object max(List list) | 返回集合中的最大值 |
| public static boolean replaceAll(List list,Object old,Object new) | 在 list 集合中用 new 替换 old |
| ublic static boolean addAll(List list,Object… obj) | 向集合中添加元素 |

浙公网安备 33010602011771号