Java集合类

ps:本文是网上找资料总结的。自己也是博客小白一枚。如有侵权,请
私信我删除

  1. 集合类
    总体框架:
    在这里插入图片描述
    大致说明:
    在这里插入图片描述
    1.Iterator(集合迭代器)

通过继承了Iterable接口的类的iterator方法返回一个迭代器。
例如在AarryList中:
List integers=new ArrayList<>();//创建list
Iterator iterator=integers.iterator();//得到迭代器对象
此方法将返回一个iterator的实现,在源码中实现类叫Itr(该类继承了Iterator),通过其它源码可知,此类是AarryList的内部类,即ArryList的Iterator实现在ArrayList内部;
所以迭代器不是一个新的对象,其操作(remove)是会影响原容器的.
Iterator的线程安全性主要取决你的集合
其实我们可以看作维护了一个指向元素的光标。

next()获得序列中的下一个元素。
hasNext()检查序列中是否还有元素(通常作为判断条件)
remove()将迭代器新近返回的元素删除.

当我们使用Iterator对集合进行遍历时,集合就相当于没有权利对自己操作,所有权利移交给Iterator,如果这时在对集合进行 add remove set这样的操作,就会产生并发修改问题 。
2.ListIterator(只能用于list及其子类型)
相对于Iterator 多添加了几种方法,可以添加元素 可以双向操作。可以定位索引。可以修改对象。
ListIterator是iterator的子接口。他是实现双向操作的。不常用。
使用方法:
List list= new ArrayList() ;
ListIterator iter = list.listIterator() ;
2. collection(集合)
在这里插入图片描述
继承于Iterable接口(可迭代的,遍历的,允许对象成为增强型 for语句的目标)
主要方法:
boolean add(Object o)添加对象到集合
boolean remove(Object o)删除指定的对象
int size()返回当前集合中元素的数量
boolean contains(Object o)查找集合中是否有指定的对象
boolean isEmpty()判断集合是否为空
Iterator iterator()返回一个迭代器
boolean containsAll(Collection c)查找集合中是否有集合c中的元素
boolean addAll(Collection c)将集合c中所有的元素添加给该集合
void clear()删除集合中所有元素
void removeAll(Collection c)从集合中删除c集合中也有的元素
void retainAll(Collection c)从集合中删除集合c中不包含的元素

从结构图中可以看出:java抽象出了AbstractCollection抽象类,它实现了Collection中的绝大部分函数;这样,在Collection的实现类中,我们就可以通过继承AbstractCollection省去重复编码。AbstractList和AbstractSet都继承于AbstractCollection,具体的List实现类直接继承于AbstractList,而Set的实现类则直接继承于AbstractSet。

  1. List接口(列表)
    允许元素重复,记录添加顺序,List中的每一个元素都有一个索引;第一个元素的索引值是0,往后的元素的索引值依次+1。允许一个和多个null值。
    总结:
    ArrayList和LinkedList对比
    1对于需要任意位置快速插入,删除元素,应该使用LinkedList 。底层链表
    2对于需要快速随机访问元素,应该使用ArrayList。底层动态数组。
    Vector和ArrayList
  2. Vector线程安全。ArrayList线程不安全。可以使用 Collections.synchronizedList(); 得到一个线程安全的 ArrayList。
  3. ArrayList支持序列化,而Vector不支持;
  4. Vector除了包括和ArrayList类似的3个构造函数之外,另外的一个构造函数可以指定容量增加系数。
  5. 容量增加方式不同:ArrayList容量不足,增加为原来的1.5倍。Vector的容量增长与“增长系数有关”,若指定了“增长系数”,且“增长系数有效(即,大于0)”;那么,每次容量不足时,“新的容量”=“原始容量+增长系数”。若增长系数无效(即,小于/等于0),则“新的容量”=“原始容量 x 2”。
  6. Vector支持通过Enumeration去遍历,而List不支持
    ArrayList类
    线程不安全。ArrayList底层是动态数组。查询快,在末尾增删快。其他地方增删慢。
    public class ArrayList extends AbstractList
    implements List, RandomAccess, Cloneable, java.io.Serializable
    他继承于AbstractList,实现了List, RandomAccess, Cloneable, java.io.Serializable接口
    ArrayList 实现了RandmoAccess接口,即提供了随机访问功能。RandmoAccess是java中用来被List实现,为List提供快速访问功能的。在ArrayList中,我们即可以通过元素的序号快速获取元素对象;这就是快速随机访问。稍后,我们会比较List的“快速随机访问”大于“通过Iterator迭代器访问”的效率。

ArrayList包含了两个重要的对象:elementData(动态数组) 和 size(实际大小)。
源码分析:
private static final int DEFAULT_CAPACITY = 10;
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

  1. ArrayList 实际上是通过动态数组去保存数据的。当我们构造ArrayList时;若使用默认构
    造函数,则ArrayList的默认容量大小是10。其实调用默认的构造函数并没有初始化为10。创建了一个空数组。在第一次调用add方法才初始化为10.
    后面调用方法才开始初始化为10.
    对于ArrayList的插入操作中真正耗时的操作是 System.arraycopy(elementData, index, elementData, index + 1, size - index); 是个JNI函数,它是在JVM中实现的。System.arraycopy(elementData, index, elementData, index + 1, size - index); 会移动index之后所有元素即可。这就意味着,ArrayList的add(int index, E element)函数,会引起index之后所有元素的改变!
    int newCapacity = oldCapacity + (oldCapacity >> 1);
  2. grow() 方法进行扩容,新容量的大小为 oldCapacity + (oldCapacity >> 1),也就是旧容量的 1.5 倍。扩容操作需要调用 Arrays.copyOf() 把原数组整个复制到新数组中,这个操作代价很高,因此最好在创建 ArrayList 对象时就指定大概的容量大小,减少扩容操作的次数。
    3.序列化:ArrayList 基于数组实现,并且具有动态扩容特性,因此保存元素的数组不一定都会被使用,那么就没必要全部进行序列化。保存元素的数组 elementData 使用 transient 修饰,该关键字声明数组默认不会被序列化。
    ArrayList 实现了 writeObject() 和 readObject() 来控制只序列化数组中有元素填充那部分内容。当写入到输出流时,先写入“容量”,再依次写入“每一个元素”;当读出输入流时,先读取“容量”,再依次读取“每一个元素”。
  3. ArrayList的克隆函数,即是将全部元素克隆到一个数组中。
    遍历方式最好使用:随机访问

LinkedList类
线程不安全,底层基于双向链表实现,使用Node存储节点信息。在任意位置增删快,查询慢,顺序访问高效,随机访问效率低。
public class LinkedList
extends AbstractSequentialList
implements List, Deque, Cloneable, java.io.Serializable
继承于AbstractSequentialList的双向链表。它也可以被当作堆栈、队列或双端队列进行操作。
LinkedList的每个Node(内部类)包含一个数据(element)和previous,next两个指针。
实现 List 接口,能对它进行队列操作。
实现 Deque 接口,即能将LinkedList当作双端队列使用。

LinkedList包含两个重要的成员:header(双向链表的表头,它是双向链表节点所对应的类Node的实例。Node中包含成员变量: previous, next, element。其中,previous是该节点的上一个节点,next是该节点的下一个节点,是该节点所包含的值。)和 size(节点个数/实际大小)。
源码分析:
if (index < (size >> 1)) {
Node x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node x = last;
for (int i = size - 1; i > index; i–)
x = x.prev;
return x;
}
通过add(int index, E element)向LinkedList插入元素时。先是在双向链表中找到要插入节点的位置index;找到之后,再插入一个新节点。
双向链表查找index位置的节点时,有一个加速动作:若index < 双向链表长度的1/2,则从前向后查找; 否则,从后向前查找。
我们在通过get(int index)方法根据索引访问元素是:其实是通过先会比较“index”和“双向链表长度的1/2”;若前者大,则从链表头开始往后查找,直到index位置;否则,从链表末尾开始先前查找,直到index位置。
LinkedList的克隆函数,即是将全部元素克隆到一个新的LinkedList对象中。
LinkedList实现java.io.Serializable。当写入到输出流时,先写入“容量”,再依次写入“每一个节点保护的值”;当读出输入流时,先读取“容量”,再依次读取“每一个元素”。
随机访问:随机访问一般是通过index下标访问,行为类似数组的访问。
顺序访问:类似于链表的访问,一步一步访问。
遍历方式最好使用:顺序遍历(增强for) 千万不要用随机遍历
Vector类
线程安全,底层也是动态数组。基本类似于ArrayList
public class Vector
extends AbstractList
implements List, RandomAccess, Cloneable, java.io.Serializable
Vector的数据结构和ArrayList差不多,它包含了3个成员变量:elementData , elementCount, capacityIncrement。
(01) elementData 是"Object[]类型的数组",它保存了添加到Vector中的元素。elementData是个动态数组,如果初始化Vector时,没指定动态数组的>大小,则使用默认大小10。
(02) elementCount 是动态数组的实际大小。
(03) capacityIncrement (arraylist没有)是动态数组的增长系数。如果在创建Vector时,指定了capacityIncrement的大小;则,每次当Vector中动态数组容量增加时>,增加的大小都是capacityIncrement。
源码分析:

  1. 底层动态数组,使用默认构造函数 默认容量10.
  2. 扩容时:若容量增加系数 >0,则将容量的值增加“容量增加系数”;否则,将容量大小增加一倍。
  3. Vector的克隆函数,即是将全部元素克隆到一个数组中。

Stack类
public
class Stack extends Vector
栈顶在数组的最后一个,
因为继承于vector 也是线程安全的。栈(先进后出)底层是动态数组,基于Vector的,性质都一样。
执行push时(即,将元素推入栈中),是通过将元素追加的数组的末尾中。
执行peek时(即,取出栈顶元素,不执行删除),是返回数组末尾的元素。
执行pop时(即,取出栈顶元素,并将该元素从栈中删除),是取出数组末尾的元素,然后将该元素从数组中删除。
2. Set接口(集合)
没有重复元素的集合。可以允许存在一个null值。
HashSet
public class HashSet
extends AbstractSet
implements Set, Cloneable, java.io.Serializable

按照hash算法来储存集合中的元素,存取(少量)和查找性能好。
特点:不能保证元素的排列顺序, 不是同步的,元素值可以为null.
HashSet内部就是使用HashMap(我们通过new创建的都是以HashMap作为实现基础,在jdk中java.util包内的源代码才可能创建以LinkedHashMap作为实现的。)只不过HashSet里面的HashMap所有的value都是同一个Object(private static final Object PRESENT = new Object();)而已。其Map的默认大小为Hashmap的默认大小,容量为16,负载因子0.75.
HashSet集合判断两个元素相等的标准是两个对象通过equals方法比较相等,并且两个对象的hashCode()方法返回值相等
存入hash表中,必须要有hashcode和equals方法。
基本数据类型java提供,自定义的我们自己提供(但是有默认的)。
在这里插入图片描述
TreeSet
public class TreeSet extends AbstractSet
implements NavigableSet, Cloneable, java.io.Serializable
实现了NavigableSet接口,意味它支持一系列的导航方法。比如查找与指定目标最匹配项。
TreeSet实现java.io.Serializable的方式。当写入到输出流时,依次写入“比较器、容量、全部元素”;当读出输入流时,再依次读取。
TreeSet不支持快速随机遍历,只能通过迭代器进行遍历!
提供有序,不重复的集合,基于TreeMap实现的。非同步的。
TreeSet可以确保集合元素处于排序状态。TreeSet支持两种排序方式,自然排序 和定制排序,其中自然排序为默认的排序方式。向TreeSet中加入的应该是同一个类的对象。
两种排序:
自然排序:使用要排序元素的CompareTo(Object obj)方法来比较元素之间大小关系,然后将元素按照升序排列。
定制排序:应该使用Comparator接口,实现 int compare(T o1,T o2)方法。自己重写方法。

遍历的时候通过排序的结果遍历出来
TreeSet判断两个对象不相等的方式是两个对象通过equals方法返回false,或者通过CompareTo方法比较没有返回0.

比较器的两种实现:
1, 继承Comparable接口 并实现compareTo()方法;
2, 定义一个单独的对象比较器,继承自Comparator接口,实现compare()方法。

  1. Queue接口(队列)
    优先级队列(加一个比较器(compator)的队列。)
    队列:先进先出(FIFO)尾部添加、头部删除。Queue实现通常不允许插入null元素,尽管一些实现(如LinkedList )不禁止插入null 。 即使在允许它的实现中, null也不应该插入到Queue ,因为null也被用作poll方法的特殊返回值来指示该队列不包含任何元素。
    提供了:
    boolean add(E e) 将指定的元素插入到此队列中,如果可以立即执行此操作而不违反容量限制, true成功返回 true ,如果当前没有可用的空间,则抛出 IllegalStateException 。
    E element() 检索,但不删除,这个队列的头。
    boolean offer(E e) 如果在不违反容量限制的情况下立即执行,则将指定的元素插入到此队列中。
    E peek() 检索但不删除此队列的头,如果此队列为空,则返回 null 。
    E poll() 检索并删除此队列的头部,如果此队列为空,则返回 null 。
    E remove() 检索并删除此队列的头。
  2. Deque接口(双端队列)
    继承于Queue队列。先进后出、后进先出都能实现,linkedList和arrayDeque是他的实现类。
    arrayDeque:
  3. 无限的扩展,自动扩展队列大小的。(当然在不会内存溢出的情况下。)
  4. 非线程安全的,不支持并发访问和修改。
  5. 支持fast-fail.
  6. 作为栈使用的话比比栈要快.
  7. 当队列使用比linklist要快。
  8. null元素被禁止使用。

7.提供双端操作的方法,
public class ArrayDeque extends AbstractCollection
implements Deque, Cloneable, Serializable
默认初始化:默认容量16.
public ArrayDeque() {
elements = new Object[16];
}
两个重要的索引:head和tail
// 第一个元素的索引
private transient int head;
// 下个要添加元素的位置,为末尾元素的索引 + 1
private transient int tail;
扩容时:扩容原来的两倍。
源码详解:
// 扩容为原来的2倍。
private void doubleCapacity() {
assert head == tail;
int p = head;
int n = elements.length;
int r = n - p; // number of elements to the right of p
int newCapacity = n << 1;
if (newCapacity < 0)
throw new IllegalStateException(“Sorry, deque too big”);
Object[] a = new Object[newCapacity];
// 既然是head和tail已经重合了,说明tail是在head的左边。
System.arraycopy(elements, p, a, 0, r); // 拷贝原数组从head位置到结束的数据
System.arraycopy(elements, 0, a, r, p); // 拷贝原数组从开始到head的数据
elements = (E[])a;
head = 0; // 重置head和tail为数据的开始和结束索引
tail = n;
}

// 拷贝该数组的所有元素到目标数组
private T[] copyElements(T[] a) {
if (head < tail) { // 开始索引大于结束索引,一次拷贝
System.arraycopy(elements, head, a, 0, size());
} else if (head > tail) { // 开始索引在结束索引的右边,分两段拷贝
int headPortionLen = elements.length - head;
System.arraycopy(elements, head, a, 0, headPortionLen);
System.arraycopy(elements, 0, a, headPortionLen, tail);
}
return a;
}
3.Map接口(映射)
Map 是映射接口,Map中存储的内容是键值对(key-value)。Map映射中不能包含重复的键;每个键最多只能映射到一个值。允许以键集(keySet())、值集(values())或键-值映射(entrySet())关系集的形式查看某个映射的内容。Map.Entry是Map中内部的一个接口,Map.Entry是键值对,
SortedMap 是继承于Map的接口。SortedMap中的内容是排序的键值对,排序(自然和定制)的方法是通过比较器(Comparator)。
NavigableMap 是继承于SortedMap的接口。相比于SortedMap,NavigableMap有一系列的导航方法;如"获取大于/等于某对象的键值对"、“获取小于/等于某对象的键值对”等等。
HashMap类
特点:线程不安全,无序的,它存储的内容是键值对(key-value)映射。Key和 value都可以为null.通过拉链法(数组+链表)实现哈希表(哈希数组)。
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
HashMap 的实例有两个参数影响其性能:“初始容量” 和 “加载因子”。容量 是哈希表中桶的数量,初始容量 只是哈希表在创建时的容量。加载因子 是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行 rehash 操作(即重建内部数据结构),从而哈希表将具有大约两倍的桶数。
默认加载因子是 0.75, 这是在时间和空间成本上寻求一种折衷。加载因子过高虽然减少了空间开销,但同时也增加了查询成本(在大多数 HashMap 类的操作中,包括 get 和 put 操作,都反映了这一点)。在设置初始容量时应该考虑到映射中所需的条目数及其加载因子,以便最大限度地减少 rehash 操作次数。如果初始容量大于最大条目数除以加载因子,则不会发生 rehash 操作。
源码分析:
// 默认构造函数。只设置加载因子为0.75
HashMap()
// 指定“容量大小”的构造函数。先判断指定大小是不是超过了最大容量(1<<30)没有设置大小和加载因子。
HashMap(int capacity)
// 指定“容量大小”和“加载因子”的构造函数。判断容量(小于1<<30)和加载因子(>0)的合理性,然后设置
HashMap(int capacity, float loadFactor)
// 包含“子Map”的构造函数.一个个put进去。
HashMap(Map<? extends K, ? extends V> map).
几个重要的成员变量:table, size, threshold, loadFactor, modCount。
  table是一个Entry[]数组类型,而Entry实际上就是一个单向链表。哈希表的"key-value键值对"都是存储在Entry数组中的。 这也是为什么我们说HashMap是通过拉链法解决哈希冲突的。
  size是HashMap的大小,它是HashMap保存的键值对的数量。
  threshold是HashMap的阈值,用于判断是否需要调整HashMap的容量。threshold的值=“容量*加载因子”,当HashMap中存储数据的数量达到threshold时,就需要将HashMap的容量加倍。
  loadFactor就是加载因子。
  modCount是用来实现fail-fast机制的。
HashMap将“key为null”的元素都放在table的位置0处,即table[0]中;“key不为null”的放在table的其余位置!
clone()方法的作用很简单,就是克隆一个HashMap对象并返回。
HashMap实现java.io.Serializable,分别实现了串行读取、写入功能。
串行写入函数是writeObject(),它的作用是将HashMap的“总的容量,实际容量,所有的Entry”都写入到输出流中。
而串行读取函数是readObject(),它的作用是将HashMap的“总的容量,实际容量,所有的Entry”依次读出。
存储结构:
通过拉链法(数组[紫色部分]+链表[绿色部分])
数组中存放的就是Hash值。而链表就是解决Hash冲突(多个元素的Hash值相同)的,使用链表串起来。在jdk1.8中,采用了数组+链表+红黑树。当我们的当同一hash值的结点数小于8时,则采用链表,否则,采用红黑树。提高查询速度。

在这里插入图片描述
–1.8之后变成 -------------

在这里插入图片描述
Jdk1.8源码put方法的分析:
public put(K key, V value) { return putVal(hash(key), key, value, false, true);}
调用putVal方法
首先如果table为空,或者还没有元素时,则扩容。
通过传进来的Hash值来计算在table表中的index值。Index= (容量 - 1) & hash 实际上就是取模。这样比取模的效率高。这也是Hashmap要求数组的size为2的幂乘的原因。
精妙:首先,Hashmap要求数组的size为2的幂乘,比如16,32,64,仔细看,当数组大小为16的时候,tab.length-1=15,在内存中的表示是00001111,将hash值与00001111做&(位与)操作后,会将hash值除了后四位全部抹为0,只保留了后四位,这样的方式完成了跟index = (hash & 0x7FFFFFFF) % tab.length一样的效果,就是取模。但是位运算的效率比取模操作高得多,也就是说HashMap的index计算方式要比Hashtable快得多。(Hashmap的初始size是16,每次扩容按照newsize = oldsize*2)
然后在table中查找index值。如果没有直接插入。
如果有,就是碰撞了。然后判断首节点的类型是红黑树还是链表。分别处理。
后面判断两个key是否相等是使用equals方法。不等插入。相等就覆盖
在后面如果链表的长度>=8,还需要转成红黑树。
HashMap中自己写了一个Hash算法。并没有和HashTable一样直接使用HashCode值
Hash算法:首先取key的hashCode,然后对16进行异或运算和右移运算。目的就是尽量打乱hashcode的低16位。
Object的Hashcode算法是native(c++写的)应该是一个和地址有关的32位的值。
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

遍历操作:
1.遍历HashMap的键值对:
第一步:根据entrySet()获取HashMap的“键值对”的Set集合。
第二步:通过Iterator迭代器遍历“第一步”得到的集合。
2.遍历HashMap的键
第一步:根据keySet()获取HashMap的“键”的Set集合。
第二步:通过Iterator迭代器遍历“第一步”得到的集合。
3.遍历HashMap的值
第一步:根据value()获取HashMap的“值”的集合。
第二步:通过Iterator迭代器遍历“第一步”得到的集合。
备注:
哈希桶就是盛放不同key链表的容器(即是哈希表)一个桶就是一个链表。
就是因为上面说的Index= (容量 - 1) & hash 实际上就是取模。这就是HashMap容量为2次幂的原因。
其他的解决Hash冲突的算法:

  1. 开放定址法:当冲突发生时,使用某种探测技术(线性探测再散列,平方探测再散列,随机探测再散列)在散列表中形成一个探测序列。沿此序列逐个单元地查找,直到找到给定的关键字,或者 碰到一个开放的地址(即该地址单元为空)为止(若要插入,在探查到开放的地址,则可将待插入的新结点存人该地址单元)。由于每次都是线性递增,容易导致堆聚,
  2. 再哈希法: 再哈希法又叫双哈希法,有多个不同的Hash函数,当发生冲突时,使用第二个,第三个,….,等哈希函数。计算地址,直到无冲突。虽然不易发生聚集,但是增加了计算时间。
    3. 建立公共溢出区: 将哈希表分为基本表和溢出表两部分,凡是和基本表发生冲突的元素,一律填入溢出表。
    为什么HashMap的负载因子是0.75 ?
    哈希冲突”和“空间利用率”矛盾的一个折中。在注释中有说明就是在理想情况下,使用随机哈希码,节点出现的频率在hash桶中遵循泊松分布,同时给出了桶中元素个数和概率的对照表。

从上面的表中可以看到当桶中元素到达8个的时候,概率已经变得非常小,也就是说用0.75作为加载因子,每个碰撞位置的链表长度超过8个是几乎不可能的。

HashTable类
线程安全。它的key、value都不可以为null。映射是无序的,
public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable
extends Dictionary<K,V>。比较原始的,声明了操作"键值对"函数接口的抽象类。
基本的很多都是和HashMap一样的,
与Hashmap的区别:

  1. HashTable是线程安全的,几乎所有的方法都是使用synchronized修饰的。保证同步。
    HashMap对应的线程安全的有concurrentHashMap,但如果不用concurrentHashMap的话,也可以只用Collections.synchronizedMap(Map)进行转换。
  2. HashMap允许key值为null,并会把key值为null放在Entry数组中的第一个桶中;HashTable不允许存放key为null的存放,如果为null会抛出异常。
  3. HashMap中对于key值的定位有内部封装的hash算法,而HashTable中是直接使用.hashcode获取hash值;在定位index时:hashtable使用的是int index = (hash & 0x7FFFFFFF) % tab.length; 取模运算效率低于hashmap的位运算,
  4. 因为保证了同步性,所以效率比Hashmap低。当然影响效率还有其他的问题。
  5. HashTable默认的初始大小为11,之后每次扩充为原来的2n+1。HashMap默认的初始化大小为16,之后每次扩充为原来的2倍。如果在创建时给定了初始化大小,那么HashTable会直接使用你给定的大小,而HashMap会将其扩充为2的幂次方大小。
  6. HashTable的底层数组没有使用红黑树,只用了链表。

为什么HashTable会尽量使用素数、奇数作为容量。当哈希表的大小为素数时,简单的取模哈希的结果会更加均匀。

LinkedHashMap类:
public class LinkedHashMap<K,V>
extends HashMap<K,V>
implements Map<K,V>
继承自HashMap<K,V>,很多地方还是一样的
特点:线程不安全,能够保证插入元素的顺序。允许key为null,value为null。他的entry中添加了两个属性,一个before,一个after。
在这里插入图片描述
为了记录前一个插入的元素和记录后一个插入的元素。其实hash表的存储结构还是一样的。
在这里插入图片描述
相当于另外维护了一张双向链表来保护他的顺序。以决定迭代时输出的顺序。
构造函数多了一个:
LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) 构造一个空的 LinkedHashMap实例,具有指定的初始容量,负载因子和排序模式(默认是false)。 /true:指定迭代的顺序是按照访问顺序(近期访问最少到近期访问最多的元素)来迭代的。 false:指定迭代的顺序是按照插入顺序迭代,也就是通过插入元素的顺序来迭代所有元素。
其中还有一个entry对象Head指向双向链表的第一个. Tail指向最后一个。
TreeMap类
特点:线程不安全,有序的key-value集合,它是通过红黑树实现的。该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。TreeMap的基本操作 containsKey、get、put 和 remove 的时间复杂度是 log(n) 。

public class TreeMap<K,V>
extends AbstractMap<K,V>
implements NavigableMap<K,V>, Cloneable, java.io.Serializable

TreeMap 实现了NavigableMap接口,意味着它支持一系列的导航方法。比如返回有序的key集合。
TreeMap的本质是红黑树,它包含几个重要的成员变量: root, size, comparator。
  root 是红黑数的根节点。它是Entry类型,Entry是红黑数的节点,它包含了红黑数的6个基本组成成分:key(键)、value(值)、left(左孩子)、right(右孩子)、parent(父节点)、color(颜色)。Entry节点根据key进行排序,Entry节点包含的内容为value。
  红黑数排序时,根据Entry中的key进行排序;Entry中的key比较大小是根据比较器comparator来进行判断的。
  size是红黑数中节点的个数。
维护红黑树策略:
1.改变某个节点的颜色使之符合规则
2.改变树的结构关系 进行左旋或者右旋。

WeakHashMap类:
public class WeakHashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>
和HashMap一样,WeakHashMap 也是一个散列表,它存储的内容也是键值对(key-value)映射,而且键和值都可以是null。
不过WeakHashMap的键是“弱键”。在 WeakHashMap 中,当某个键不再正常使用时,会被从WeakHashMap中被自动移除。更精确地说,对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的丢弃,这就使该键成为可终止的,被终止,然后被回收。某个键被终止时,它对应的键值对也就从映射中有效地移除了。
这个“弱键”的原理呢?大致上就是,通过WeakReference和ReferenceQueue实现的。 WeakHashMap的key是“弱键”,即是WeakReference类型的;ReferenceQueue是一个队列,它会保存被GC回收的“弱键”。实现步骤是:
(01) 新建WeakHashMap,将“键值对”添加到WeakHashMap中。
实际上,WeakHashMap是通过数组table保存Entry(键值对);每一个Entry实际上是一个单向链表,即Entry是键值对链表。
(02) 当某“弱键”不再被其它对象引用,并被GC回收时。在GC回收该“弱键”时,这个“弱键”也同时会被添加到ReferenceQueue(queue)队列中。
(03) 当下一次我们需要操作WeakHashMap时,会先同步table和queue。table中保存了全部的键值对,而queue中保存被GC回收的键值对;同步它们,就是删除table中被GC回收的键值对。
这就是“弱键”如何被自动从WeakHashMap中删除的步骤了。
和HashMap一样,WeakHashMap是不同步的。可以使用 Collections.synchronizedMap 方法来构造同步的 WeakHashMap。
其中多维护了一个queue保存的是“已被GC清除”的“弱引用的键”。 在Java中,WeakReference和ReferenceQueue 是联合使用的。在WeakHashMap中亦是如此。

ps:本文是网上找资料总结的。自己也是博客小八义美

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值