今日语录:山有峰顶,海有彼岸。漫漫长途,终有回转。余味苦涩,终有回甘。
一、集合的基本概念和理解
-
分析数组的不足
1、长度必须一开始指定,而且一旦指定,不能更改
2、保存的必须为同一类型的元素
3、使用数组进行增加/删除元素的示意代码,比较麻烦
-
集合的好处
1、可以动态的保存任意多个元素,使用比较方便
2、提供了一系列方便的操作对象的方法:add remove set get 等
3、使用集合添加,删除新元素的适宜代码
二、集合框架体系
-
单列集合

-
双类集合(存放的键值对K-V)

三、Collection
-
Collection接口实现类的特点
public interface Collection<E> extends Iterable<E>
1、collection实现子类可以存放多个元素,每个元素可以是Object
2、有些Collection的实现类,可以存放重复的元素,有些不可以
3、有些collection的实现类,有些是有序的(List),有些是无序的(Set)
4、Collection接口没有直接的实现子类,是通过他的子接口Set和List来实现的
-
Collection常用方法
1、add:添加元素
2、remove:删除指定元素
3、contains:查找元素是否存在
4、size:获取元素个数
5、isEmpty:判断是否是空
6、clear:清空
7、addAll:添加多个元素
8、containsAll:查找多个元素是否都存在
9、removeAll:删除多个元素
-
-
Collection接口遍历对象
方式一:Iterator(迭代器)
-
简介
Collection接口遍历元素方式
1、Iterator对象称为迭代器,主要用来遍历Collection集合中的元素
2、所有实现了Collection接口的集合类都有一个Iterator()方法,用以返回一个实现了Iterator接口的对象,即可以返回一个迭代器
3、Iterator的结构
4、Iterator仅用于遍历集合,Iterator本身并不存放对象
5、当退出while循环后,这时iterator迭代器,指向最后的元素
iterator.next();
6、如果希望再次遍历,需要重置我们的迭代器。
-
迭代器的执行原理
Iterator iterator = coll.iterator();
hasNext();//判断是否还有下一个元素
while(iterator.hasNext()){
// next(); 1 指针下移 2 将下移以后集合位置上的元素返回
System.out.println(iterator.next());
}PS:在调用iterator.next()方法之前必须要调用iterator.hasNext()进行检测。若不调用,且下一条记录无效,直接调用会抛出NoSuchElementException异常
-
方式二-for循环增强
增强for循环,可以代替iterator迭代器,特点:增强for循环就是简化版的iterator,本质是一样的。只能用于遍历集合或数组
-
基本语法
for(元素类型 元素名 :集合名或数组名){
访问元素
}
1、List
-
List简介
List接口是Collection接口的子接口
1、List集合类中元素有序(即添加顺序和取出顺序一致)、且可重复
2、List集合中的每个元素都有其对应的顺序索引,即支持索引
3、List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素
4、JDK API中List接口的实现类有:
ArrayList LinkedList Stack Vector
-
List常用方法
1、void add(int index,Object ele);在index位置插入ele元素
2、bolean addAll(int index,Collection eles):从index位置开始将eles中的所有元素添加进来
3、Object get(int index):获取指定index位置的元素
4、int indexOf(Object obj); 返回obj在集合中首次出现的位置
5、int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置
6、Object remove(int index):移除指定index位置的元素,并返回此元素
7、Object set(int index,Object ele):设置指定index位置的元素为ele,相当于是替换
8、List subList(int fromIndex,int toIndex):返回从fromIndex到toIndex位置的子集合
-
List的三种遍历方式【ArrayList,LinkedList,Vector】
方式一:使用iterator
Iterator iterator = col.iterator();
while(iterator.hasNext()){
Object obj = iterator.next();
}
方式二:使用增强for
for(Object o : col){
}
方式三:使用普通for循环
for(int i=0,i<col.size();i++){
Object obj = list.get(i);
System.out.println(obj);
}
ArrayList
-
ArrayList注意事项
1、permits all elements,include null.ArrayList可以加入null,并且可以有多个
2、ArrayList是由数组来实现数据存储的
3、ArrayList基本等同于Vector,除了ArrayList是线程不安全的(执行效率高),看源码,方法没加synchronized关键字
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}在多线程模式下,不建议使用ArrayList
-
ArrayList底层操作机制源码分析
1、ArrayList中维护了一个Object类型的数组elementData
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
* transient:表示瞬间,短暂的,表示该属性不会被序列化
*/
transient:Object[] elementData; // non-private to simplify nested class access2、当创建ArrayList对象时,如果使用的是无参构造器,则初始化elementData容量为0,第一次添加,则扩容elementData为10,如果需要再次扩容,则扩容elementData为1.5倍
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}3、如果使用的是指定大小的构造器,则初始化elementData容量为指定大小,如果需要扩容,则直接扩容elementData为1.5倍
/**
* Constructs an empty list with the specified initial capacity.
*
* @param initialCapacity the initial capacity of the list
* @throws IllegalArgumentException if the specified initial capacity
* is negative
*/
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}4、扩容机制算法
image-20211207222026386 -
无参构造方法-动态扩容机制演示
// 添加元素,动态扩容机制演示
ArrayList list = new ArrayList();
for(int i = 0;i<10;i++){
list.add(i);
}
/**
* 执行list.add()
* (1)先确定是否要扩容
* (2)然后在执行赋值
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
/**
* 该方法确定minCapacity
* (1)第一次扩容为 10
*/
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
/**
* (1) modCount++ :记录这个集合被修改的次数(防止多线程重复操作)
* (2) 如果elementData的大小不够,就调用grow(minCapacity)进行扩容
*/
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
/**
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
* 扩容机制
* @param minCapacity the desired minimum capacity
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
// 进行1.5倍扩容
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 如果新的容量 < 最小的容量(10),那么就把最小容量赋值给新容量
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
// 使用Arrays.copyOf(),可以保留集合中原来的数据
elementData = Arrays.copyOf(elementData, newCapacity);
}
LinkedList
-
说明
1、LinkedList底层实现了双向链表和双端队列特点
2、可以添加任意元素(元素可以重复),包括null
3、线程不安全,没有实现同步
-
-
LinkedList的底层操作机制
1、LinkedList底层维护了一个双向链表
transient Node<E> first;
transient Node<E> last;2、LinkedList中维护了两个属性first和last分别指向首节点和尾节点
3、每个节点(Node对象),里面维护了prev、next、item三个属性,其中通过prev指向前一个,通过next指向后一个节点。最终实现双向链表
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;
}
}4、所以LinkedList的元素的添加和删除,不是通过数组完成的,相对来说效率最高
-
LinkedList源码分析
add(E e)
1、首先,创建LinkedList对象
LinkedList linkedList = new LinkedList();
// 此时底层源码如下
/**
* Constructs an empty list.
*/
public LinkedList() {
}2、此时linkedList的属性 first=null last=null
3、向linkedList添加元素,执行以下代码
/**
* Appends the specified element to the end of this list.
*
* <p>This method is equivalent to {@link #addLast}.
*
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#add})
*/
public boolean add(E e) {
linkLast(e);
return true;
}4、将新的节点,加入到双向链表的最后
/**
* Links e as last element.
*/
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}remove()
1、无参,默认删除的是第一个节点,并返回第一个节点的元素
/**
* Retrieves and removes the head (first element) of this list.
*
* @return the head of this list
* @throws NoSuchElementException if this list is empty
* @since 1.5
*/
public E remove() {
return removeFirst();
}
/**
* Removes and returns the first element from this list.
*
* @return the first element from this list
* @throws NoSuchElementException if this list is empty
*/
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}2、执行删除操作
/**
* Unlinks non-null first node f.
*/
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
// 获得当前要删除节点的元素
final E element = f.item;
// 获得当前要删除节点(首个节点)的下一个节点信息
final Node<E> next = f.next;
// 把当前要删除节点的item置空
f.item = null;
// 把第一个节点指向的下一个节点置空
f.next = null; // help GC
// 把LinkedList的第一个节点重设为->下一个节点
first = next;
if (next == null)
last = null;
else
//把下一个节点的指向之前节点的连接置空
next.prev = null;
// 将集合长度减1
size--;
// 记录修改次数:+1
modCount++;
// 返回被删除节点的元素
return element;
} -
ArrayList和LinkedList的比较
底层结构 增删的效率 改查的效率 安全性 ArrayList 可变数组 较低,数组扩容 较高 线程不安全 LinkedList 双向链表 较高,通过链表追加 较低 线程不安全 -
如何选择ArrayList和LinkedList:
1、如果我们改查的操作多,选择ArrayList
2、如果我们增删的操作多,选择LinkedList
3、一般来说,在程序中,80%-90%都是查询,因此大部分情况下会选择ArrayList
4、在一个项目中,根据业务灵活选择,也可能这样,一个模块使用的是ArrayList,另一个模块使用LinkedList
Vactor
-
类定义
public class Vector<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable -
类图
-
简介
1、Vector底层也是一个对象数组,protected Object[] elementData;
2、Vector是线程同步的,即线程安全。Vector类的操作方法有synchronized
/**
* Returns the element at the specified position in this Vector.
*
* @param index index of the element to return
* @return object at the specified index
* @throws ArrayIndexOutOfBoundsException if the index is out of range
* ({@code index < 0 || index >= size()})
* @since 1.2
*/
public synchronized E get(int index) {
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
return elementData(index);
}3、在开发中,需要线程同步安全时,考虑使用Vector
-
Vector源码解读
1、创建Vector类时,无参默认容量是10
/**
* Constructs an empty vector so that its internal data array
* has size {@code 10} and its standard capacity increment is
* zero.
*/
public Vector() {
this(10);
}2、有参执行扩容机制
/**
* Appends the specified element to the end of this Vector.
*
* @param e element to be appended to this Vector
* @return {@code true} (as specified by {@link Collection#add})
* @since 1.2
*/
public synchronized boolean add(E e) {
modCount++; //记录修改次数
ensureCapacityHelper(elementCount + 1); //确认集合容量是否够用
elementData[elementCount++] = e;
return true;
}
/**
* 确认集合容量是否够用
* This implements the unsynchronized semantics of ensureCapacity.
* Synchronized methods in this class can internally call this
* method for ensuring capacity without incurring the cost of an
* extra synchronization.
*
* @see #ensureCapacity(int)
*/
private void ensureCapacityHelper(int minCapacity) {
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
/**
* 集合容量不够用执行扩容(2倍扩容)
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
// 如果capacityIncrement不大于0,那么执行2倍扩容
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
} -
Vector和ArrayList比较
底层结构 版本 线程安全(同步)效率 扩容倍数 ArrayList 可变数组 jdk1.2 不安全,效率高 如果有参构造,按1.5倍扩容,如果是无参,第一次按10扩展容量,从第二次开始按1.5倍扩容 Vector 可变数组Object[] jdk1.0 安全,效率不高 如果是无参,默认10,满后,就按2倍扩容,如果指定大小,则每次直接按2倍扩容
2、Set
-
简介
1、无序( 添加和取出的顺序不一致 ),没有索引
2、不允许重复元素,所以最多包含一个null
3、JDK API 中Set接口的实现类有
image-20230216232806582 -
Set接口的常用方法
和List接口一样,Set接口也是Collection的子接口,因此,常用方法和Collection接口一样
-
Set接口的遍历方式
同Collection的遍历方式一致,因为Set接口也是Collection接口的子接口
1、可以使用迭代器
2、增强for循环
3、不能使用索引的方式来获取
HashSet
-
说明
1、HashSet实现了Set接口
2、HashSet实际上是HashMap,如下源码
/**
* Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
* default initial capacity (16) and load factor (0.75).
*/
public HashSet() {
map = new HashMap<>();
}3、可以存放null值,但是只能有一个null
4、HashSet不保证元素是有序的,取决于hash后,再确定索引的结果(即,不保证存放顺序和取出顺序一致)
5、不能有重复元素/对象
-
HashSet底层机制说明
1、分析HashSet底层是HashMap,HashMap底层是(数组+链表+红黑树)
2、示例代码
package com.hspedu.set_;
/**
* @author 韩顺平
* @version 1.0
*/
@SuppressWarnings({"all"})
public class HashSetStructure {
public static void main(String[] args) {
//模拟一个HashSet的底层 (HashMap 的底层结构)
//1. 创建一个数组,数组的类型是 Node[]
//2. 有些人,直接把 Node[] 数组称为 表
Node[] table = new Node[16];
//3. 创建结点
Node john = new Node("john", null);
table[2] = john;
Node jack = new Node("jack", null);
john.next = jack;// 将jack 结点挂载到john
Node rose = new Node("Rose", null);
jack.next = rose;// 将rose 结点挂载到jack
Node lucy = new Node("lucy", null);
table[3] = lucy; // 把lucy 放到 table表的索引为3的位置.
System.out.println("table=" + table);
}
}
class Node { //结点, 存储数据, 可以指向下一个结点,从而形成链表
Object item; //存放数据
Node next; // 指向下一个结点
public Node(Object item, Node next) {
this.item = item;
this.next = next;
}
}
HashSet扩容机制
1、HashSet底层就是HashMap
2、添加一个元素时,先得到hash值–会转成–>索引值
3、找到存储数据表table,看到这个索引位置是否已经存放元素
4、如果没有,直接放入元素
5、如果有,调用equals进行比较,如果相同,就放弃添加(因为相同的已存在),如果不相同,则添加到最后
6、在Java8中,如果一条链表的元素个数到达TREEIFY_THRESHOLD(默认是8),并且table的大小 >= MIN_TREEIFY_CAPACITY(默认64),就会进行树化(转为红黑树)
HashSet树化机制(转成红黑树)
1、HashSet底层是HashMap,第一次添加时,table数组扩容到16,临界值(threshold)是16*加载因子
(loadRactor)是0.75 = 12
2、如果table数组使用到了临界值12,就会扩容到16 * 2 =32,新的临界值就是32 * 0.75 = 24,依此类推
8 --> 16(临界值16*0.75=12) --> 32(临界值32*0.75=24) --> 64(临界值64*0.75=48) --> 128(临界值128*0.75=96)
3、在java8中,如果一条链表的元素个数到达TREEIFY_THRESHOLD(默认64),就会进行树化(红黑树),否则仍然采用数组扩容机制
HashSet源码分析
add()
1、执行HashSet(),底层其实是一个HashMap
public HashSet() {
map = new HashMap<>();
}
2、执行add()
public boolean add(E e) {
// 例如:e = "java"
// PRESENT =static final Object PRESENT = new Object
return map.put(e, PRESENT)==null;
}
3、执行put(),会执行hash(key),得到key对应的hash值
算法为:( h = key.hashCode() ) ^ (h >>> 16)
public V put(K key, V value) {
// key = "java"
// value = PRESENT 共享
return putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
4、执行putVal()
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
// table就是transient Node<K,V>[] table,一个HashMap的数组,类型是Node[]
// if 语句表示如果当前table是null,或者 大小=0,就是第一次扩容,到16个空间
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//(1)根据key,得到hash去计算该Key应该存放到table表的哪一个索引位置,并把这个索引位置,赋给p变量
//(2)判断p 是否为null
//(2.1)如果p为null,表示还没有存放元素,就创建一个Node(key="java",value=PRESENT)
//(2.2)就放在该位置tab[i] = newNode(hash, key, value, null)
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
//(3)如果p不是null,并且当前索引位置对应的链表的第一个元素和准备添加的元素的hash值一样,
//(3.1)并且满足 准备加入的key 和 p 指向的Node 节点的key是同一个对象
//(3.2) p 指向的Node 节点的key 的equals()和准备加入的key比较后相同,就不能加入
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
// 再判读p是不是一棵红黑树
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
// 如果table对应索引位置已经是一个链表,则使用for循环进行比较
//(1)依次和该链表的每一个元素比较后,都不相同,则加入到链表的最后
// (1.1)注意在把元素添加到链表后,立即判断 该链表是否已经达到8个节点,就调用treeifyBin()对当前这个链表进行树化(转成红黑树),注意,在转为红黑树时,要进行判断,判断条件->
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY(64))
resize();
如果上面条件成立,先进行table扩容。
只有上面条件不成立时,才进行树化(转成红黑树)
//(2)依次和该链表的每一个元素比较过程中,如果有相同情况,就直接break
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
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;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
LinkedHashSet
-
说明
1、LinkedHashSet是HashSet的子类
2、LinkedHashSet底层是一个LinkedHashMap,底层维护了一个 数组 + 双向链表
3、LinkedHashSet根据元素的hashCode 值来决定元素的存储位置,同时使用链表维护元素的次序,这使得元素看起来是以插入顺序保存的
4、LinkedHashSet不允许添加重复元素
-
类图
image-20211212182046044 -
LinkedHashSet源码分析
1、LinkedHashSet加入顺序和取出元素/数据的顺序一致
2、LinkedHashSet底层维护的是一个LinkedHashMap(是HashMap的子类)
3、LinkedHashSet 底层结构(数组table+双向链表)
4、添加第一次时,直接将数组table扩容到 16 ,存放的节点类型是 LinkedHashMap$Entry
5、数组是 HashMapEntry
四、Map
-
类图(双类集合)
image-20211215230528206 -
Map类特点(JDK1.8)
1、Map与Collection并列存在,用于保存具有映射关系的数据:Key-Value
2、Map中的 key 和 value 可以是任何引用类型的数据,会封装到HashMap$Node对象中
3、Map中的 key 不允许重复
4、Map中 value 可以重复
5、Map中的 key 可以为Null,value也可以为null,注意key为null,只能有一个,value为null,可以有多个
6、常用String类作为Map的key
7、key 和 value 之间存在单向一对一关系,即通过指定的key 总能找到对应的value
8、Map存放数据的key-value示意图,一对 k-v 是放在一个 Node中的,有因为Node实现了Entry接口,有些书上也说,一对k-v就是一个Entry
1、k-v 最后是 HashMap$Node node = newNode(hash, key, value, null);
2、k-v 为了方便程序员的遍历,还会创建EntrySet 集合,该集合存放的元素的类型 Entry,而一个Entry 对象就有k-v EntrySet<Entry<K,V>>,即:
transient Set<Map.Entry<K,V>> entrySet;
3、entrySet 中,定义的类型是Map.Entry,但是实际上存放的还是HashMapNode 实现了Map.Entry<K,V>
static class Node<K,V> implements Map.Entry<K,V>{...}
4、当把HashMap$Node 对象 存放到 entrySet 就方便我们的遍历,因为Map.Entry 提供了重要方法:K getKey(); V getValue();
image-20211215224354205 -
Map接口和常用方法
1、Map接口常用方法
1、containsKey:查找键是否存在
2、keySet:获取所有的键
3、entrySet:获取所有关系
4、values:获取所有的值
2、Map接口遍历方式
Map map = new HashMap();
map.put("1",1);
map.put("2",2);
map.put("3",3);第一组:先取出所有的 Key ,通过取出 key 取出对应的 value
1、增强for循环
Set keySet = map.keySet();
for(Object key : keySet){
System.out.println(key + "-" + map.get(key));
}2、迭代器
Iterator iterator = keySet.iterator();
while(iterator.hasNext()){
Object key = iterator.next();
System.out.println(key + "-" + ,ap.get(key));
}第二组:先取出所有的values
Collecion values = map.values();
1、增强for循环
for(Object value : values){
System.out.println(value);
}2、迭代器
Iterator iterator = values.iterator();
while(iterator.hasNext()){
Object value = iterator.next;
System.out.println(value);
}第三组:使用EntrySet 来获取 k – v
Set entrySet = map.entrySet();
1、增强for循环
for(Object entry : entrySet){
Map.Entry m = (Map.Entry)entry;
System.out.println(m.getKey() + "-" + m.getValue());
}2、使用Iterator
Iterator iterator = entrySet.iterator();
while(iterator.hasNext()){
Object next = iterator.next();
// System.out.println(next.getClass());//HashMap$Node ->实现Map.Entry(getKey,getValue)
// 向下转型 Map.Entry
Map.Entry m = (Map.Entry)entry;
System.out.println(m.getKey() + "-" + m.getValue());
}
HashMap
-
小结
1、Map接口的常用实现类:HashMap、Hashtable 和 Properties
2、HashMap 是 Map 接口使用频率最高的实现类
3、HashMap 是以 key-value 对的方式来存储数据
4、key 不能重复,但是是值可以重复,允许使用null键和null值
5、如果添加相同的key,则会覆盖原来的key-value,等同于修改(key不会替换,value会替换)
6、与HashSet一样,不保证映射顺序,因为底层是以hash表的方式来存储的(jdk1.8的hashMap底层是 ->数组+链表+红黑树)
7、HashMap没有实现同步,因此是线程不安全的,方法没有做同步互斥的操作,没有synchronized
-
HashMap底层机制及源码剖析
image-20220104213332538 1>(k,v)是一个Node实现了Map.Entry<K,V>,查看HashMap的源码可以看到
2> jdk7.0的hashmap底层实现[数组+链表],jdk8.0底层[数组+链表+红黑树]
-
HashMap扩容机制
1、HashMap底层维护了Node类型的数组table,默认为null
2、当创建对象时,将加载因子(loadfactor)初始化为0.75
3、当添加key-value时,通过key 的哈希值得到在table的索引,然后判断该索引处是否有元素,如果没有元素则直接添加;如果该索引处有元素,继续判断该元素的key是否和准备加入的key相同,如果相等,则直接替换val;如果不相等需要判断是树结构还是链表结构,做出相应处理。如果添加时发现容量不够,则需要扩容
4、第1次添加,则需要扩容table容量为16,临界值(threshold)为16*0.75=12
5、以后再扩容,则需要扩容table容量为原来的2倍,临界值为原来的两倍,即32*0.75=24,依此类推
6、在java8中,如果一条链表的元素个数超过TREEIFY_THRESHOLD(默认是8),并且table的大小>=MIN_TREEIFY_CAPACITY(默认64),就会进行树化(转为红黑树)
-
HashMap源码解读
1、执行构造器new HashMap(),初始化加载因子loadfactor = 0.75,HashMap$Node[] table = null
image-20220104215208840 2、执行put
public V put(K key, V value) { // key = "1" value=1
return putVal(hash(key), key, value, false, true);
}3、计算key的hash值
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}4、执行putVal()
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
// 如果底层的table数组为null,或者length=0,就扩容到16(resize()方法)
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// 取得hash值对应的table索引位置的Node,如果为null,就直接把加入的K-V,创建成一个Node,直接加入到该位置
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
// 如果key的hash值存在,则具体判断值
else {
// 辅助变量
Node<K,V> e; K k;
// 如果table的索引位置的key的hash相同和新的key的hash值相同,并满足(存在的节点的key和准备加入的key是同一个对象,或者 equals相等)
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
// 如果当前的table的已有的Node是红黑树,就按照红黑树处理
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
// 如果找到的node,后面是链表,则循环比较
else {
for (int binCount = 0; ; ++binCount) {
// 如果整个链表没有一个对象和想要加入的对象相同,就加入到链表的最后
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
// 加入后,判断当前链表的个数,是否已经达到8个,到8个,就进行树化
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
// 树化(转成红黑树)
treeifyBin(tab, hash);
break;
}
// 如果在循环过程中,发现有key相同(equals或hash相等),则break,然后只替换value
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
// 替换,key对应的value
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
// 如果size>临界值(threshold)12-24-48....,则执行扩容
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}5、树化(转成红黑树)
/**
* Replaces all linked nodes in bin at index for given hash unless
* table is too small, in which case resizes instead.
*/
// 如果table为null,或者大小还没达到64,暂时不树化,而是进行扩容,否则才会真正的树化-->剪枝
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
else if ((e = tab[index = (n - 1) & hash]) != null) {
TreeNode<K,V> hd = null, tl = null;
do {
TreeNode<K,V> p = replacementTreeNode(e, null);
if (tl == null)
hd = p;
else {
p.prev = tl;
tl.next = p;
}
tl = p;
} while ((e = e.next) != null);
if ((tab[index] = hd) != null)
hd.treeify(tab);
}
}6、扩容算法
/* 初始化或加倍表大小。如果为空,则分配
* 与初始容量目标一致,保持在现场阈值。
* 否则,因为我们用的是2的幂展开
* 每个bin中的元素必须保持相同的索引,或者移动
**/
在新表中,偏移量的幂为2。
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
Hashtable
-
类图

-
Hashtable基本介绍
1、存放的元素是键值对:即K-V
2、hashTable的键和值都不能为null
3、hashTable使用方法基本上和HashMap一样
4、hashTable是线程安全的(synchronized),hashMap是线程不安全的
-
Hashtable底层介绍
1、底层有数组 Hashtable$Entry[],初始化大小为11
2、临界值/阈值 threshold 8 = 11*0.75
3、扩容机制
4、执行方法 addEntry(hash, key, value, index);添加K-V封装到Entry
5、当if(count >= threshold)满足时,执行扩容
6、按照 int newCapacity = (oldCapacity << 1) + 1;的大小进行扩容(2倍+1)
-
HashMap和Hashtable对比
版本 线程安全(同步) 效率 允许Null键Null值 HashMap 1.2 不安全 高 允许 Hashtable 1.0 安全 较低 不允许
LinkedHashMap
LinkedHashMap通过在HashMap的基础上增加一条双向链表
TreeMap
底层源码同HashMap
Properties
-
基本介绍
1、Properties类继承Hashtable类并且实现了Map接口,也是使用一种键值对的形式来保存数据
2、他的使用特点和Hashtable类似
3、Properties还可以用于从XXX.properties文件中,加载数据到Properties类对象,并进行读取和修改
4、说明:工作后,xxx.properties文件通常作为配置文件,这个知识点在IO流举例
五、Collections
-
Collections工具类介绍
1、Collections是一个操作Set、List和Map等集合的工具类
2、Collections中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作
排序操作(均为static方法)
1、reverse(List):反转List中元素的顺序
2、shuffle(List):对List集合元素进行随机排序
3、sort(List):根据元素的自然顺序对指定List集合元素按升序排序
4、sort(List,Compartor):根据指定的Comparator产生的顺序对List集合元素进行排序
5、swap(List,int,int):将指定list集合中i处元素和j处元素进行交换
查找、替换
1、Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
2、Object max(Collection,Comparator):根据Comparator指定的顺序,返回给定集合中的最大元素
3、Object min(Collection):根据元素的自然顺序,返回给定集合中的最小元素
4、Object(Collection,Comparator):根据Comparator指定的顺序,返回给定集合中的最小元素
5、int frequency(Collection,Object):返回指定集合中指定元素的出现次数
6、void copy(List dest,List src):将src中的内容复制到dest中
7、boolean replaceAll(List list,Object oldVal, Object newVal):使用新值替换List对象的所有旧值
六、小结
-
在实际开发中,选择什么集合实现类,主要取决于业务操作特点,然后根据集合实现类特性进行选择,分析如下:
1、先判断存储的类型(一组对象[单列]或一组键值对[双列])
2、一组对象:Collection接口
允许重复:List
增删多:LinkedList [底层维护了一个双向链表]
改查多:ArrayList [底层维护了Object类型的可变数组]
不允许重复:Set
无序:HashSet [底层是HashMap,维护了一个哈希表 即(数组+链表+红黑树)]
排序:TreeSet
插入和取出顺序一致:LinkedHashSet [维护数组+双向链表]
3、一组键值对:Map
键无序:HashMap [底层是:哈希表 jdk7:数组+链表,jdk8:数组+链表+红黑树]
键排序:TreeMap
键插入和取出顺序一致:LinkedHashMap
读取文件:Properties
原文始发于微信公众号(Whisper Coding):Java入门-集合
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/255487.html