简单了解集合容器
Conllection接口基本结构
Map接口基本结构
集合容器:存储多个不同类型数据的容器
快速了解集合的一些接口
序号 | 接口描述 |
---|---|
1 | Collection 接口 Collection 是最基本的集合接口,一个 Collection 代表一组 Object,即 Collection 的元素, Java不提供直接继承自Collection的类,只提供继承于的子接口(如List和set)。Collection 接口存储一组不唯一,无序的对象。 |
2 | List 接口 List接口是一个有序的 Collection,使用此接口能够精确的控制每个元素插入的位置,能够通过索引(元素在List中位置,类似于数组的下标)来访问List中的元素,第一个元素的索引为 0,而且允许有相同的元素。List 接口存储一组不唯一,有序(插入顺序)的对象。 |
3 | Set Set 具有与 Collection 完全一样的接口,只是行为上不同,Set 不保存重复的元素。Set 接口存储一组唯一,无序的对象。 |
4 | SortedSet 继承于Set保存有序的集合。 |
5 | Map Map 接口存储一组键值对象,提供key(键)到value(值)的映射。 |
6 | Map.Entry 描述在一个Map中的一个元素(键/值对)。是一个 Map 的内部接口。 |
7 | SortedMap 继承于 Map,使 Key 保持在升序排列。 |
8 | Enumeration 这是一个传统的接口和定义的方法,通过它可以枚举(一次获得一个)对象集合中的元素。这个传统接口已被迭代器取代。 |
// 集合
由一些类和接口组成的数据容器
集合的特点时:不同类,不同长
类:Collections(集合的工具类)
Java 集合框架主要包括两种类型的容器,
一种是集合(Collection),存储一个元素集合,
另一种是图(Map),存储键/值对映射。
Collection 接口又有 3 种子类型,List、Set 和 Queue,再下面是一些抽象类,最后是具体实现类,常用的有 ArrayList、LinkedList、HashSet、LinkedHashSet、HashMap、LinkedHashMap 等等
- List
- ArrayList , LinkedList Vector Stack
- Set
- HashSet TreeSet LinkedHashSet
- Map
- HashTable HashMap Treemap ConcurrentHashMap
Iterable (for-each)
Iterable接口 (java.lang.Iterable) 是Java集合的顶级接口之一。Collection接口继承Iterable,所以Collection的所有子类也实现了Iterable接口。
实现这个接口允许对象成为“for-each循环”语句的目标。
Iterator 迭代器(for)
Java Iterator(迭代器)不是一个集合,它是一种用于访问集合的方法,可用于迭代 ArrayLis和 HashSe 等集合。
Iterator 是 Java 迭代器最简单的实现,ListIterator是 Collection API 中的接口, 它扩展了 Iterator 接口
迭代器 it 的两个基本操作是 next 、hasNext 和 remove。
调用 it.next() 会返回迭代器的下一个元素,并且更新迭代器的状态。
调用 it.hasNext() 用于检测集合中是否还有元素。
调用 it.remove() 将迭代器返回的元素删除。
- 集合上的迭代器。在Java集合框架中,迭代器取代了枚举。迭代器与枚举有两个不同之处:迭代器允许调用者在迭代期间使用定义良好的语义从底层集合中删除元素。
- 方法名得到了改进。
Stack
栈(Stack) 是一种 后进先出(LIFO:Last In First Out) 的数据结构
栈是只能在某一端插入和删除的特殊线性表
Stack类表示对象的后进先出(LIFO)堆栈。
它用五个操作扩展了Vector类,允许将Vector作为堆栈处理。
提供了常用的push和pop操作,以及查看堆栈顶部项目的方法,测试堆栈是否为空的方法,以及搜索堆栈中项目并发现它距离顶部有多远的方法。
第一次创建堆栈时,它不包含任何项。
Deque双向队列
Deque (Double Ended Queue)是线性的集合,支持两端的元素插入和移除,也可以称作双向队列。
支持在两端插入和删除元素的线性集合。
deque是“双端队列”的缩写,通常发音为“deck”。
大多数Deque实现对它们可以包含的元素数量没有固定的限制,但是这个接口支持容量受限的Deque以及没有固定大小限制的Deque。
提供了插入、删除和检查元素的方法。这些方法都以两种形式存在:一种在操作失败时抛出异常,另一种返回一个特殊值(空或假,取决于操作)。后一种形式的插入操作是专门为容量受限的Deque实现设计的;在大多数实现中,插入操作不会失败。
扩展了Queue接口。当使用deque作为队列时,会产生先进先出(FIFO, First-In-First-Out)行为。在deque的末尾添加元素,并从开头删除元素
提供了两个删除内部元素的方法:removeFirstOccurrence和removeLastOccurrence。
与List接口不同,该接口不提供对元素的索引访问支持。
虽然Deque实现没有严格要求禁止插入空元素,但强烈鼓励这样做。强烈鼓励任何允许空元素的Deque实现的用户
- 普通队列(一端进另一端出):
Queue queue = new LinkedList()- 或 Deque deque = new LinkedList()
- 双端队列(两端都可进出)
Deque deque = new LinkedList() - 堆栈
Deque deque = new LinkedList()
Queue队列
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WXDNriyy-1664623977686)(%E9%9B%86%E5%90%88%E5%AE%B9%E5%99%A8.assets/image-20220929083229922.png)]
队列(Queue) 是先进先出 (First In First Out, FIFO) 的数据结构,是最先进队列的元素一定最早出队列
队列通常(但不一定)以FIFO(先进先出)方式对元素排序。例外情况包括优先队列,它根据提供的比较器或元素的自然顺序对元素进行排序,以及后进先出(后进先出)对元素进行后进先出排序的后进先出队列(或堆栈)。无论使用何种顺序,队列的头都是调用remove()或poll()来删除的元素。在FIFO队列中,所有的新元素都插入到队列的尾部。其他类型的队列可能使用不同的放置规则。每个Queue实现都必须指定其排序属性。
Queue只能从队尾插入,从对头删除
Collection
public interface Collection<E>
extends Iterable<E>
集合主要分为两大系列:Collection和Map,Collection 表示一组对象,Map表示一组映射关系或键值对
Collection是所有单列集合的父接口
单列集合的顶层接口,表示为一组对象。
其类型为引用数据类型,具体创建对象,通过多态的形式进行
Collections
java.lang.Object
java.util.Collections
public class Collections
extends Object
集合工具类
List有序集合(序列)
这个接口的用户可以精确的控制每个元素在列表中出入的位置。用户可以通过元素的整数索引访问元素,并在列表中搜索元素
与集合不同列表通常允许有重复的元素
ArrayList
ArrayList 类是一个可以动态修改的数组,与普通数组的区别就是它是没有固定大小的限制
-
底层是基于数组实现的,因此具有查找修改快插入删除慢的特点
-
随着元素被添加到数组列表中,容量会自动增长,容量默认大小为16,当超过0.75时扩容1倍,没有指定增长策略的细节
-
如果多个线程并发访问一个ArrayList实例,并且至少有一个线程在结构上修改了该列表,那么它必须在外部同步。
-
通常在自然封装列表的某些对象上同步实现
-
List list = Collections.synchronizedList(new ArrayList(...));
-
-
ConcurrentModificationException 并发修改错误
-
此类完全由在 collection 上进行操作或返回 collection 的静态方法组成
-
根据元素的自然顺序 对指定列表按升序进行排序。
-
列表中的所有元素都必须实现 Comparable 接口
LinkedList
链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的地址。
链表可分为单向链表和双向链表。
一个单向链表包含两个值: 当前节点的值和一个指向下一个节点的链接。
一个双向链表有三个整数值: 数值、向后的节点链接、向前的节点链接。
Java LinkedList(链表) 类似于 ArrayList,是一种常用的数据容器。
-
F头结点引用,Node内部内,L尾节点引用
-
node节点中:item存储元素,next下一个节点,prev上一个节点
-
LinkedList是基于双向链表实现的,不论是增删改查方法还是队列和栈的实现,都可通过操作结点实现
-
LinkedList无需提前指定容量(初始大小),因为基于链表操作,集合的容量随着元素的加入自动增加
-
LinkedList删除元素后集合占用的内存自动缩小,无需像ArrayList一样调用trimToSize()方法
-
LinkedList的所有方法没有进行同步,因此它也不是线程安全的,应该避免在多线程环境下使用
ArrayList与LinkedList的使用
以下情况使用 ArrayList :
- 频繁访问列表中的某一个元素。
- 只需要在列表末尾进行添加和删除元素操作。
以下情况使用 LinkedList :
- 你需要通过循环迭代来访问列表中的某些元素。
- 需要频繁的在列表开头、中间、末尾等位置进行添加和删除元素操作。
Vector
- Vector类实现了一个可增长的对象数组,内部是以动态数组的形式来存储数据的。
- Vector具有数组所具有的特性、通过索引支持随机访问、所以通过随机访问Vector中的元素效率非常高、但是执行插入、删除时效率比较低下。
- 继承了AbstractList,此类提供 List 接口的骨干实现,以最大限度地减少实现”随机访问”数据存储(如数组)支持的该接口所需的工作.对于连续的访问数据(如链表),应优先使用 AbstractSequentialList,而不是此类.
- 实现了List接口,意味着Vector元素是有序的,可以重复的,可以有null元素的集合.
- 实现了RandomAccess接口标识着其支持随机快速访问,实际上,我们查看RandomAccess源码可以看到,其实里面什么都没有定义.因为ArrayList底层是数组,那么随机快速访问是理所当然的,访问速度O(1).
- 实现了Cloneable接口,标识着可以它可以被复制.注意,ArrayList里面的clone()复制其实是浅复制
- 实现了Serializable 标识着集合可被序列化。
实现了RandomAccess接口
- Vector实际上是通过一个数组去保存数据的。当我们构造Vecotr时;若使用默认构造函数,则Vector的默认容量大小是10。
- 当Vector容量不足以容纳全部元素时,Vector的容量会增加。若容量增加系数 >0,则将容量的值增加“容量增加系数”;否则,将容量大小增加一倍。
- Vector的克隆函数,即是将全部元素克隆到一个数组中。
- 遍历Vector,使用索引的随机访问方式最快,使用迭代器最慢
Stack (不常用)
Stack只有入栈和出栈的操作:
在Java中,我们用Deque可以实现Stack的功能:
- 把元素压栈:push(E)/addFirst(E);
- 把栈顶的元素“弹出”:pop()/removeFirst();
- 取栈顶元素但不弹出:peek()/peekFirst()。
为什么Java的集合类没有单独的Stack接口呢?因为有个遗留类名字就叫Stack,出于兼容性考虑,所以没办法创建Stack接口,只能用Deque接口来“模拟”一个Stack了
// 声明
Deque<Integer> stack = new ArrayDeque<Integer>();
Set无序
不包含重复元素的集合
Set的底层使用Map来实现的,其使用key与Object的一个默认对象作为键值对插入到Map中
HashSet
-
HashSet 继承于AbstractSet 该类提供了Set 接口的骨架实现,以最大限度地减少实现此接口所需的工作量。
-
实现Set接口,标志着内部元素是无序的,元素是不可以重复的。
-
实现Cloneable接口,标识着可以它可以被复制。
-
实现Serializable接口,标识着可被序列化。
-
HashSet 基于 HashMap 来实现的,是一个不允许有重复元素的集合。
-
HashSet 允许有 null 值。
-
HashSet 是无序的,即不会记录插入的顺序。
-
HashSet 不是线程安全的, 如果多个线程尝试同时修改 HashSet,则最终结果是不确定的。 您必须在多线程访问时显式同步对 HashSet 的并发访问。
-
HashSet 实现了 Set 接口
TreeSet
作用:实现了 Set 接口,是一个有序的 Set,这样就能从 Set 里面提取一个有序序列
-
TreeSet继承于AbstractSet 该类提供了Set 接口的骨架实现,以最大限度地减少实现此接口所需的工作量。
-
TreeSet实现了NavigableSet接口,意味着它支持一系列的导航方法。比如查找与指定目标最匹配项。
-
实现Cloneable接口,意味着可以它可以被复制。
-
实现Serializable接口,意味着可被序列化
-
TreeSet实际上是TreeMap实现的。当我们构造TreeSet时;若使用不带参数的构造函数,则TreeSet的使用自然比较器;若用户需要使用自定义的比较器,则需要使用带比较器的参数。
-
TreeSet是非线程安全的。
-
TreeSet实现java.io.Serializable的方式。当写入到输出流时,依次写入“比较器、容量、全部元素”;当读出输入流时,再依次读取
LinkHashSet
void foo(Set s) {
Set copy = new LinkedHashSet(s);
...
}
Set s = Collections. synchronizedSet(new LinkedHashSet(...));
哈希表和链表的Set接口实现,可迭代
迭代所需的时间与集的大小成正比,与容量无关
这个实现不是同步的
Map
将键映射到值的对象,映射不能包含重复的键;每个键最多只能映射到一个值
set keyset方法:Iterator
HashMap
-
HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。
-
HashMap 实现了 Map 接口,根据键的 HashCode 值存储数据,具有很快的访问速度,最多允许一条记录的键为 null,不支持线程同步。
-
HashMap 是无序的,即不会记录插入的顺序。
-
HashMap 的 key 与 value 类型可以相同也可以不同,可以是字符串(String)类型的 key 和 value,也可以是整型(Integer)的 key 和字符串(String)类型的 value。
-
HashMap 中的元素实际上是对象,一些常见的基本类型可以使用它的包装类
-
HashMap 继承于AbstractMap,实现了 Map、Cloneable、java.io.Serializable 接口
// HashMap
static class Node<K,V> implements Map.Entry<K,V> {
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
HashMap里面实现一个静态内部类Entry,其重要的属性有 key , value, next,从属性key,value我们就能很明显的看出来Entry就是HashMap键值对实现的一个基础bean,
HashMap的基础就是一个线性数组,这个数组就是Entry[],Map里面的内容都保存在Entry[]里面
resize初始化
- HashMap实行了懒加载, 新建HashMap时不会对table进行赋值, 而是到第一次插入时, 进行resize时构建table;
- 当HashMap.size 大于 threshold时, 会进行resize;
- 当第一次构建时, 如果没有指定HashMap.table的初始长度, 就用默认值16, 否则就是指定的值; 然后不管是第一次构建还是后续扩容, threshold = table.length * loadFactor;
final Node<K,V>[] resize() {
// 根据key值,判断值是否已存在,如果存在就替换值
// 空返回也可以表示之前将null与key关联的映射
public V put(K key,V value){
return putVal(hash(key),key,value,false,true);
}
// hash: 根据key计算hash值
static final int hash(Object key){
int h;
return(key==null)?0:(h=key.hashCode())^(h>>>16);
}
// putVal:显示了与添加相关的方法
// Node<K,V>[] tab;
// Node<K,V> p;
// int n, i; n:数组长度
// n = (tab = resize()).length;
// resize() 初始化或加倍表的大小,如果为空,则按照字段阈值中持有的初始容量目标分配
// 第一次进入:
// 初始化数组默认最大值为16
ewCap=DEFAULT_INITIAL_CAPACITY;
// 扩容条件 : 12
newThr=(int)(DEFAULT_LOAD_FACTOR*DEFAULT_INITIAL_CAPACITY);
// return newTab; 返回了一个长度为16的数组
// & 位运算符号
// 查询下标为 i 的node数据
tab[i=(n-1)&hash])
// newNode() 创建一个常规(非树)节点
// 用于使HashMap集合视图上的迭代器快速失败 ; 此节点上元素数量
modCount
// 在节点插入
afterNodeInsertion
扩容方法 resize()
/**
* Implements Map.put and related methods.
*
* @param hash hash for key
* @param key the key
* @param value the value to put
* @param onlyIfAbsent if true, don't change existing value
* @param evict if false, the table is in creation mode.
* @return previous value, or null if none
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
for(int binCount=0;;++binCount){
// 判断下一个节点
if((e=p.next)==null){
// 新增节点
p.next=newNode(hash,key,value,null);
// 转树或扩容 链长度为8,数组大小为64时 ;转为红黑树
if(binCount>=TREEIFY_THRESHOLD-1) // -1 for 1st
treeifyBin(tab,hash);
break;
}
// 覆盖
if(e.hash==hash&&
((k=e.key)==key||(key!=null&&key.equals(k))))
break;
p=e;
}
// TREEIFY_THRESHOLD = 8
// treeifyBin方法中:MIN_TREEIFY_CAPACITY = 64
// 创建树创建根
// 当inCount >= TREEIFY_THRESHOLD创建
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
// 找到根然后替换节点
TreeNode<K,V> p = replacementTreeNode(e, null);
// Forms tree of the nodes linked from this node : 形成从此节点链接的节点树。
treeify(tab);
// 形成从此节点链接的节点树
final void treeify(Node<K,V>[] tab);
// 替换索引bin中所有链接的节点,除非表太小,在这种情况下,而是调整大小
// treeifyBin调用了resize
}
删除:树,链,node
/**
*如果存在,则从此映射中移除指定键的映射。
返回:先前的值与key相关联,如果key没有映射则为空。(空返回也可以表示之前将null与key关联的映射。)
*/
public V remove(Object key) {
Node<K,V> e;
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}
// 具体实现方法
/**
* matchValue – if true only remove if value is equal 如果为true,只移除value相等的值
* movable – if false do not move other nodes while removing 如果为false,则删除时不要移动其他节点
*/
final Node<K,V> removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable);
// 判断下个节点是否为空后,将当前节点提取为node
if (p instanceof TreeNode)
node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
// 普通删除
直接执行removeNode方法;
// 执行移除节点操作 变回链
// if (node != null && (!matchValue || (v = node.value) == value ||
// (value != null && value.equals(v))))
if (node instanceof TreeNode)
((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
// 删除节点后
afterNodeRemoval(node);
// 因为树长度不够返回 node
return node;
HashTable
“散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。
Hashtable 也是一个散列表,它存储的内容是键值对(key-value)映射。
Hashtable 继承于Dictionary,实现了Map、Cloneable、java.io.Serializable接口。
-
Hashtable 的函数都是同步的
- 线程安全但是效率低。
-
key、value都不可以为null。
-
Hashtable中的映射不是有序的。
-
默认初始容量是11
-
填充/加载因子为0.75
- Dictionary类是任何可将键映射到相应值的类的抽象父类,每个键和值都是对象
- Dictionary源码注释指出 Dictionary 这个类过时了,新的实现类应该实现Map接口
Hashtable | HashMap |
---|---|
方法是同步的 | 方法是非同步的 |
基于Dictionary类 | 基于AbstractMap,而AbstractMap基于Map接口的实现 |
key和value都不允许为null,遇到null,直接返回 NullPointerException | key和value都允许为null,遇到key为null的时候,调用putForNullKey方法进行处理,而对value没有处理 |
hash数组默认大小是11,扩充方式是old*2+1 | hash数组的默认大小是16,而且一定是2的指数 |
TreeMap(有序
- TreeMap 是一个有序的key-value集合,它是通过红黑树实现的。
- TreeMap 继承于AbstractMap,所以它是一个Map,即一个key-value集合。
- TreeMap 实现了NavigableMap接口,意味着它支持一系列的导航方法。比如返回有序的key集合。
- TreeMap 实现了Cloneable接口,意味着它能被克隆。
- TreeMap 实现了java.io.Serializable接口,意味着它支持序列化。
- TreeMap基于红黑树(Red-Black tree)实现。该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的
- Comparator 进行排序,具体取决于使用的构造方法。
- reeMap是非同步的。 它的iterator 方法返回的迭代器是fail-fastl的。
为什么TreeNode转Node不需要强转
都实现了一个共同父类接口
Node<K,V> e;
Node<K,V> p;
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
// Node
static class Node<K,V> implements Map.Entry<K,V> {
// TreeNode
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
// HashMap
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
// LinkedHashMap
public class LinkedHashMap<K,V>
extends HashMap<K,V>
implements Map<K,V>
{
不好的地方或者是可以改进的地方还请大家多多指正