Java 集合框架

一.概述

Java 集合框架Java Collection Framework ,又被称为容器 container,是定义在java. util 包下的一组接口 interfaces 和其实现类 classes。
其主要表现为将多个元素 element 置于一个单元中,用于对这些元素进行快速、便捷的存储store、 检索retrieve、管理manipulate ,即平时我们俗称的增删查改

Java集合主要有4个部分: List列表、Set集合、Map映射、工具类
( Arrays和Collections 等)。

Java 集合类主要由两个根接口 Collection 和 Map 派生出来的,Collection 派生出了三个子接口:List、Set、Queue ,因此Java集合大致也可分成 List、Set 、Queue 、Map 四种接口体系,(注意:Map不是Collection的子接口 )

二. Java 集合框架图

在这里插入图片描述

集合可以看作是一种容器,用来存储对象信息。所有集合类都位于 java.util 包下,但支持多线程的集合类位于 java.util.concurrent 包下。

数组与集合的区别如下:

1)数组长度不可变化而且无法保存具有映射关系的数据;集合类用于保存数量不确定的数据,以及保存具有映射关系的数据。

2)数组元素既可以是基本类型的值,也可以是对象;集合只能保存对象。

其中 List 代表了有序可重复集合,可直接根据元素的 索引 来访问;Set 代表无序不可重复的集合,只能根据元素本身来访问;Queue 是队列集合;Map 代表的是存储 key-value 对的集合,可根据元素的 key 来访问 value 。

三.常用类

1.Set 集合的几个常用实现类:

1)HashSet

HashSet 是 Set 集合最常用实现类,是其经典实现。HashSet 是按照 hash 算法来存储元素的,因此具有很好的存取和查找性能。

HashSet具有如下特点:

♦ 不能保证元素的顺序。

♦ HashSet不是线程安全的,如果多线程操作 HashSet 集合,则应通过代码来保证其同步。

♦ 集合元素值可以是 null。

HashSet存储原理如下

当向 HashSet 集合中存储一个元素时,HashSet 会调用该对象的 hashCode() 方法得到其 hashCode 值,然后根据 hashCode 值决定该对象的存储位置。
  
HashSet 集合判断两个元素相等的标准是:
  (1)两个对象通过equals()方法比较返回 true ;
  (2)两个对象的 hashCode() 方法返回值相等。
  因此,如果(1)和(2)有一个不满足条件,则认为这两个对象不相等

HashSet查找原理如下:

基于 HashSet 的存储原理,在查找元素时,HashSet 先计算元素的HashCode 值(也就是调用对象的 hashCode()方法的返回值),然后直接到hashCode 值对应的位置去取出元素即可,这就是 HashSet 速度很快的原因。

重写hashCode()方法的基本原则如下:

♦ 在程序运行过程中,同一个对象的 hashCode() 方法返回值应相同。

♦ 当两个对象通过 equals() 方法比较返回 true 时,这两个对象的hashCode() 方法返回值也应该相等。

2)LinkedHashSet

LinkedHashSet 是 HashSet 的一个子类,具有 HashSet 的特性,也是根据元素的 hashCode 值来决定元素的存储位置。但它使用 链表 维护元素的次序,遍历集合时,元素的顺序与添加顺序一致。由于LinkedHashSet需要维护元素的插入顺序,因此性能略低于HashSet。

3)TreeSet

TreeSet 可以保证元素处于排序状态,它采用红黑树的数据结构来存储集合元素。TreeSet支持两种排序方法:自然排序和定制排序,默认采用自然排序。

♦ 自然排序

TreeSet 会调用集合元素的 compareTo(Object obj) 方法来比较元素的大小,然后将元素按照升序排列,这就是自然排序。如果试图将一个对象添加到TreeSet 集合中,则该对象必须实现 Comparable 接口,否则会抛出异常。当一个对象调用方法与另一个对象比较时,例如 obj1.compareTo(obj2) ,如果该方法返回0,则两个对象相等;如果返回一个正数,则obj1大于obj2;如果返回一个负数,则obj1小于obj2。

Java 常用类中已经实现了 Comparable 接口的类有以下几个: BigDecimal、BigDecimal以及所有数值型对应的包装类;Charchter; Boolear;String; Date、Time;
  
对于 TreeSet 集合而言,它判断两个对象是否相等的标准是:两个对象通过compareTo(Object obj) 方法比较是否返回0,如果返回0则相等。

♦ 定制排序

想要实现定制排序,需要在创建 TreeSet 集合对象时,提供一个 Comparator 对象与该 TreeSet 集合关联,由 Comparator 对象负责集合元素的排序逻辑。

综上:自然排序实现的是Comparable接口,定制排序实现的是Comparator接口。

5)各Set 实现类的性能分析

HashSet的性能比TreeSet的性能好(特别是添加,查询元素时),因为TreeSet 需要额外的红黑树算法维护元素的次序,如果需要一个保持排序的Set 时才用 TreeSet,否则应该使用 HashSet。

LinkedHashSet 是 HashSet的子类,由于需要链表维护元素的顺序,所以插入和删除操作比 HashSet 要慢,但遍历比 HashSet快。

以上几个Set实现类都是线程不安全的,如果多线程访问,必须手动保证集合的同步性。

2. List集合

List 集合代表一个有序、可重复集合,集合中每个元素都有其对应的顺序索引。List 集合默认按照元素的添加顺序设置元素的索引,可以通过索引(类似数组的下标)来访问指定位置的集合元素。

实现List接口的集合主要有:ArrayList、LinkedList、Vector、Stack。

1)ArrayList

ArrayList 是一个动态数组,也是我们最常用的集合,是 List类 的典型实现。它允许任何符合规则的元素插入甚至包括 null。每一个 ArrayList 都有一个初始容量(10),该容量代表了数组的大小。随着容器中的元素不断增加,容器的大小也会随着增加。在每次向容器中增加元素的同时都会进行容量检查,当快溢出时,就会进行扩容操作。所以如果我们明确所插入元素的多少,最好指定一个初始容量值,避免过多的进行扩容操作而浪费时间、效率。

ArrayList擅长于随机访问。同时ArrayList是非同步的。

2)LinkedList

LinkedList 是 List 接口的另一个实现,除了可以根据索引访问集合元素外,LinkedList 还实现了 Deque 接口,可以当作双端队列来使用。

LinkedList 的实现机制与 ArrayList 的实现机制完全不同,ArrayLiat 内部以数组的形式保存集合的元素,所以随机访问集合元素有较好的性能;LinkedList 内部以链表的形式保存集合中的元素,所以随机访问集合中的元素性能较差,但在插入删除元素时有较好的性能。

3)Vector

与 ArrayList 相似,但是 Vector 是同步的。所以说 Vector 是线程安全的动态数组。它的操作与ArrayList几乎一样。

4)Stack

Stack 继承自 Vector,实现一个后进先出的堆栈。Stack 提供5个额外的方法使得Vector 得以被当作堆栈使用。基本的 push 和 pop 方法,还有 peek 方法得到栈顶的元素,empty 方法测试堆栈是否为空,search 方法检测一个元素在堆栈中的位置。Stack 刚创建后是空栈。

3.Map集合

Map 接口采用键值对 Map<K,V> 的存储方式,保存具有映射关系的数据,因此,Map 集合里保存两组值,一组值用于保存 Map 里的 key,另外一组值用于保存 Map 里的 value,key 和value 可以是任意引用类型的数据。key 值不允许重复,可以为null。如果添加key-value对时Map中已经有重复的key,则新添加的value 会覆盖该 key 原来对应的 value。常用实现类有HashMap、LinkedHashMap、TreeMap等。

1)HashMap与Hashtable

HashMap 与 Hashtable 是 Map 接口的两个典型实现,它们之间的关系完全类似于 ArrayList 与 Vertor。HashTable 是一个古老的 Map 实现类,它提供的方法比较繁琐,目前基本不用了。
  
HashMap 与Hashtable 主要存在以下两个典型区别:

♦ HashMap 是线程不安全,HashTable 是线程安全的。

♦ HashMap 可以使用 null 值为 key 或 value;Hashtable 不允许使用 null 值作为 key和 value,如果把 null放进 HashTable中,将会发生空指针异常。

为了成功的在HashMap和Hashtable中存储和获取对象,用作key的对象必须实现hashCode()方法和equals()方法。

HashMap如何解决冲突:

HashMap 使用链表来解决碰撞问题,当发生碰撞了,对象将会存储在链表的下一个节点中。

2)LinkedHashMap

LinkedHashMap 使用双向链表来维护 key-value 对的次序,该链表负责维护 Map 的迭代顺序,与插入顺序一致,因此性能比 HashMap 低,但在迭代访问 Map 里的全部元素时有较好的性能。

3)TreeMap

TreeMap 是 SortedMap 的实现类,是一个红黑树的数据结构,每个 key-value 对作为红黑树的一个节点。TreeMap 存储 key-value 对时,需要根据key 对节点进行排序。TreeMap也有两种排序方式:

♦ 自然排序:TreeMap 的所有 key 必须实现 Comparable 接口,而且所有的key 应该是同一个类的对象,否则会抛出ClassCastException。

♦ 定制排序:创建 TreeMap 时,传入一个 Comparator 对象,该对象负责对TreeMap 中的所有 key 进行排序。

4)各Map实现类的性能分析

♦ HashMap 通常比 Hashtable(古老的线程安全的集合)要快

♦ TreeMap 通常比 HashMap、Hashtable 要慢,因为 TreeMap 底层采用红黑树来管理 key-value。

♦ LinkedHashMap 比 HashMap 慢一点,因为它需要维护链表来保证 key-value的插入顺序。

四.异同点

1.ArrayList和LinkedList

(1)ArrayList 是实现了基于动态数组的数据结构,LinkedList 基于链表的数据结构。
(2)对于随机访问 get 和set,ArrayList 绝对优于 LinkedList,因为 LinkedList要移动指针。
(3)对于新增和删除操作 add和 remove,LinedList 比较占优势,因为 ArrayList要移动数据。

2.HashTable与HashMap

相同点:

(1)都实现了Map、Cloneable、java.io.Serializable 接口。
(2)都是存储"键值对( key-value )"的散列表,而且都是采用 拉链法实现的。

不同点:

(1)历史原因: HashTable 是基于陈旧的 Dictionary 类的,HashMap是Java 1.2引进的 Map 接口的一个实现 。
(2)同步性:HashTabl e是线程安全的,也就是说是同步的,而 HashMap是线程序不安全的,不是同步的 。
(3)对null值的处理:HashMap 的key、value都可为null,HashTable 的key、value都不可为null 。
(4)基类不同:HashMap继承于 AbstractMap,而 Hashtable继承于Dictionary。
(5)支持的遍历种类不同:HashMap 只支持 Iterator(迭代器)遍历。而Hashtable 支持 Iterator(迭代器)和 Enumeration(枚举器) 两种方式遍历。

3.HashMap、Hashtable、LinkedHashMap 和TreeMap 比较

Hashmap 是一个最常用的 Map,它根据键的 HashCode 值存储数据,根据键可以直接获取它的值,具有很快的访问速度。遍历时,取得数据的顺序是完全随机的。HashMap 最多只允许一条记录的键为 Null;允许多条记录的值为Nul l; HashMap 不支持线程的同步,即任一时刻可以有多个线程同时写 HashMap;可能会导致数据的不一致。如果需要同步,可以用 Collections 的 synchronizedMap方法使HashMap具有同步的能力。

Hashtable 与 HashMap 类似,不同的是:它不允许记录的键或者值为空;它支持线程的同步,即任一时刻只有一个线程能写 Hashtable,因此也导致了 Hashtale 在写入时会比较慢。

LinkedHashMap 保存了记录的插入顺序,在用 Iterator 遍历 LinkedHashMap时,先得到的记录肯定是先插入的。如果需要输出的顺序和输入的相同,那么用LinkedHashMap 可以实现。LinkedHashMap 实现与 HashMap 的不同之处在于,后者维护着一个运行于所有条目的 双向链表。此链接列表定义了迭代顺序,该迭代顺序可以是插入顺序或者是访问顺序。

TreeMap 实现 SortMap 接口,内部实现是红黑树。能够把它保存的记录根据键 排序,默认是按键值的升序排序,也可以指定排序的比较器,当用Iterator 遍历TreeMap时,得到的记录是排过序的。TreeMap不允许key的值为null。非同步的。

4. HashMap 和 ConcurrentHashMap

(1)HashMap

HashMap 是一个用于存储 Key-Value 键值对的集合,每一个键值对也叫做一个Entry。这些 Entry 分散存储在一个数组当中,这个数组就是 HashMap 的主干。

因为table数组的长度是有限的,再好的hash函数也会出现index冲突的情况,所以我们用链表来解决这个问题,table数组的每一个元素不只是一个Entry对象,也是一个链表的头节点,每一个Entry对象通过Next指针指向下一个Entry节点。当新来的Entry映射到冲突数组位置时,只需要插入对应的链表即可。

需要注意的是:新来的Entry节点插入链表时,会插在链表的头部,因为HashMap的发明者认为,后插入的Entry被查找的可能性更大。

HashMap中的table数组如下所示:
在这里插入图片描述

所以,HashMap是数组+链表+红黑树(在Java 8中为了优化Entry的查找性能,新加了红黑树部分)实现的。

(2)ConcurrentHashMap

分段锁技术:ConcurrentHashMap 相比 HashTable 而言解决的问题就是它不是锁全部数据,而是锁一部分数据,这样多个线程访问的时候就不会出现竞争关系。不需要排队等待了。
在这里插入图片描述
从图中可以看出来 ConcurrentHashMap 的主干是个 Segment 数组。
这就是为什么 ConcurrentHashMap 支持允许多个写操作同时并发进行,原因就是采用的 Segment 分段锁功能,每一个 Segment 都相当于小的 hashtable并且都有自己锁,只要修改不在同一个段上的内容就不会引起并发问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值