JAVA集合
集合结构图
集合分为两大类,分别是 Collection、Map
Collection类集合的结构图如下:
Map类集合结构图如下:
接下来就是分别介绍两种集合的各个实现类。
重点是HashMap等源码分析
1.Collection接口和常用方法
1.1 Collecton接口实现类特点
collection实现子类可以存放多个元素,元素的类型是Object
collection下的List接口的实现类,可以存放相同元素 并且 元素有序
collection下的Set接口的实现类,不能存放相同元素 并且 元素无需
1.2 Collection接口常用方法
这里举例使用ArrayList来演示常用方法,因为对于这些实现类都是通过 List或Set 接口间接实现了Collection接口,所以Collection接口中定义的方法都是通用的。
package com.llh.learn1;
import java.util.ArrayList;
import java.util.List;
public class CollectionMethod {
public static void main(String[] args) {
// Collection接口常用的方法。这里主要以ArrayList来举例(这些方法Collection接口的实现类通用)
ArrayList list = new ArrayList();
// 添加元素 add(Object)
list.add("a");
// 在指定下标位置插入元素 (因为Set集合是无序的,即没有元素下标索引,所以这里在指定位置插入的方法对于Set集合是无法使用的。)
list.add(0,"d");
// 批量添加元素(将一个集合追加到集合后)
list.addAll(List.of("a1","a2","a3"));
System.out.println(list);
// 删除元素
list.remove("a2");
list.remove(0);
// 查找元素是否存在
System.out.println(list.contains("a2"));
System.out.println(list.containsAll(List.of("a1","a3")));
// 获得元素的个数
System.out.println(list.size());
// 判断集合是否为空
System.out.println(list.isEmpty());
// 清空集合内的元素
list.clear();
// 如下代码可以看出来 remove(Object) 方法 一次只删除一个匹配的元素
list.addAll(List.of("1","1","1","1","1"));
System.out.println(list);
list.remove("1");
System.out.println(list);
}
}
1.3 集合的遍历方法
遍历集合可以通过 Iterator迭代器 或者 增强for循环 两种方式来遍历,前提是这个集合类要继承或实现 Iteratorable 接口。
1.3.1 使用Iterator迭代器
Iterator称为迭代器,主要用来遍历Collection集合中的元素 ,也可以用来遍历数组。
从Collection接口继承自 Iteratorable 接口就可以看出来 Iterator会与Collection有关系。Iteratorable 表示可迭代的意思,而Colleciton又是Iteratorable接口的子接口,所有就可以使用 Iterator。
iterator迭代器工作原理
使用Iterator的方式进行遍历
package com.llh.learn1;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class CollectionIterator {
public static void main(String[] args) {
ArrayList list = new ArrayList();
for (int x = 1;x<=10;x++){
list.add("元素:" + x);
}
System.out.println(list);
System.out.println("\n使用迭代器");
useIterator(list);
}
// 定义使用迭代器来实现遍历的方法
public static void useIterator(Collection c){
// 获取关于该集合的iterator迭代器对象
Iterator iterator = c.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
1.3.2 使用增强for循环
增强for循环是Iterator迭代器的简化,也是用来遍历 Collection集合、数组 使用的。
增强for循环
package com.llh.learn1;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class CollectionIterator {
public static void main(String[] args) {
ArrayList list = new ArrayList();
for (int x = 1;x<=10;x++){
list.add("元素:" + x);
}
System.out.println(list);
System.out.println("\n使用增强for循环");
useEnhanceFor(list);
System.out.println("===================");
}
// 使用增强for循环来遍历Collection集合
public static void useEnhanceFor(Collection c){
for (Object o : c){
System.out.println(o);
}
}
}
当使用增强 for 循环遍历实现了Iterable接口的对象(如集合类List、Set等)时,编译器会自动将其转换为使用迭代器的代码。
本质上,下面的增强 for 循环代码:
for (元素类型 变量名 : 可迭代对象) {
// 循环体
}
会被编译器转换为类似这样的迭代器代码:
Iterator<元素类型> iterator = 可迭代对象.iterator();while (iterator.hasNext()) {
元素类型 变量名 = iterator.next();
// 循环体
}
所以,当你使用 增强for循环来遍历集合的时候,底层其实还是会创建出一个关于该集合的Iterator对象,这个是需要注意的。
1.4 List集合的源码实现
1.4.1 ArrayList底层结构和源码分析
ArrayList底层机制:
ArrayList底层实际上基于一个Object类型的数组 elementData。 transient Object[] elementdata; // 这个transient表示短暂的、瞬间,表示该修饰符修饰的属性不会被序列化
当创建ArrayList的时候,如果使用的是空参构造器,则初始化elementData的容量为0,添加第一个元素时elementData容量会扩容到10。如果需要再次扩容,那么就扩容当当前容量的1.5倍
如果使用指定大小的构造器,则初始化elementData的容量为构造器中指定的大小,如果需要再次扩容,同样扩容到当前容量的1.5倍
ArrayList的源码分析:
public class ArrayListSource {
public static void main(String[] args) {
// 使用空参构造器
/** private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; // 把一个空数组赋值给elementData
}
*/
ArrayList list = new ArrayList();
// 开始添加操作
/** public boolean add(E e) {
modCount++; // 修改更改记录数
add(e, elementData, size); // 添加操作
return true;
}
// add方法的代码
private void add(E e, Object[] elementData, int s) { // 这个s是该类的一个变量,默认值为0 (作用是用来指定元素要在elementData数组中的下标)
if (s == elementData.length) // elementData数组的长度为0,结果为true
elementData = grow(); // 进行扩容,grow()方法的具体代码如下
elementData[s] = e; // elementData[0] = e; 把刚才要添加的元素赋值给了elementData[0],完成添加操作
size = s + 1; // 把size进行加1,用于指定下一次要添加元素到数组的下标
}
// grow() 方法
private Object[] grow() {
return grow(size + 1); // grow() 调用 grow(int i)方法,这个i用来表示当前数组所需要的最小容量
}
// grow(int i) 方法
private Object[] grow(int minCapacity) {
int oldCapacity = elementData.length; // 把elementData数组当前的容量赋值给 oldCapacity
// 判断,如果elementData的容量>=1 的情况(也就是说elementData不为空,有值的情况)
if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// 这段是用来求出 elementData容量当前的 1.5 并赋值给 newCapacity
int newCapacity = ArraysSupport.newLength(oldCapacity,
minCapacity - oldCapacity, // minimum growth
oldCapacity >> 1 // preferred growth );
// 使用数组的赋值方法,把当前的elementData赋值给一个新的扩容为自己1.5倍容量的新数组
return elementData = Arrays.copyOf(elementData, newCapacity);
} else {
// 如果原来elementData为空的话就返回一个容量为10的空数组并赋值给elementData,这里DEFAULT_CAPACITY是常量,值为10
return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
}
}
*
*/
for (int i = 0; i < 10; i++) {
list.add(i);
}
// 此时list的容量size已经为10,所以根据源码执行下边的添加操作就会进行扩容了,扩容为当前数组容量的1.5倍
list.add(1);
// 虽然这里输入当前集合的容量为11,这是因为这个方法实际上是返回的size变量,这个变量就是用来记录elementData中真是添加元素的个数,而不是
// elementData的真实容量,这个地方elementData数组的真实容量就是15,但是因为我们值添加11个元素,所以size变量就是11,所以这里就返回11。
// 这个地方可以通过debug来进行查看。
System.out.println(list.size());
}
}
1.4.2 Vector分析
Vector和ArrayList基本相同,只是有一点细微差别。Vector底层也是基于Object类型的数组进行实现的。只不过是Vector是线程安全的。同时在数组的初始化和扩容相较于ArrayList有点不同。
Vector示例代码:
// vector分析
public class VectorSource {
public static void main(String[] args) {
/** public Vector() {
this(10); // 可以看到Vector在初始化的时候直接把一个容量为10的空数组赋值给elementData,这点与ArrayList不同
}
*/
Vector vector = new Vector();
/** public synchronized boolean add(E e) { // 可以看到Vector类的方法都使用了 synchronized 来实现线程同步
modCount++;
add(e, elementData, elementCount);
return true;
}
*/
vector.add("1");
}
}
1.4.3 LinkedList底层结构和源码分析
LinkedList底层实现了 双向链表 和 双端队列 的特点。
LinkedList的底层操作机制:
上边的Node中的item属性就是用来存储真正添加的元素的。
① 简单模拟LinkedList
package com.llh.learn1;
/**
* @Description: TODO
* @Author lilinghao
* @Date 2025/3/4
**/
public class LinkedListMimic {
public static void main(String[] args) {
Node item1 = new Node("item1");
Node item2 = new Node("item2");
Node item3 = new Node("item3");
// first节点指向第一个节点
Node first = item1;
// last节点执行最后一个节点 first、last是用来满足双端队列的特点
Node last = item3;
// 构建各个node之间的链接关系
item1.setNext(item2);
item2.setNext(item3);
item3.setPrev(item2);
item2.setPrev(item1);
// 实现正向遍历
System.out.println("==========正向遍历=============");
while (first != null) {
System.out.println(first.getItem());
first = first.getNext();
}
first = item1; // 遍历过后将头指针复位
// 反向遍历
System.out.println("==========反向遍历=============");
while (last != null){
System.out.println(last.getItem());
last = last.getPrev();
}
// 遍历过后将尾指针复位
// 模拟插入 (插入到 2-3 之间)
System.out.println("==========插入============");
Node item5 = new Node("插入");
item2.setNext(item5);
item3.setPrev(item5);
item5.setPrev(item2);
item5.setNext(item3);
while (first != null) {
System.out.println(first.getItem());
first = first.getNext();
}
first = item1;
// 模拟删除 (删除item5)
System.out.println("========删除===========");
item2.setNext(item3);
item3.setPrev(item2);
while (first != null) {
System.out.println(first.getItem());
first = first.getNext();
}
first = item1;
}
}
class Node {
private Node next;
private Node prev;
private Object item;
public Node() {}
public Node(Object item) {
this.item = item;
}
public Node getNext() {
return next;
}
public void setNext(Node next) {
this.next = next;
}
public Node getPrev() {
return prev;
}
public void setPrev(Node prev) {
this.prev = prev;
}
public Object getItem() {
return item;
}
public void setItem(Object item) {
this.item = item;
}
@Override
public String toString() {
return "Node{" +
"next=" + next +
", prev=" + prev +
", item=" + item +
'}';
}
}
② LinkedList增删改查底层机制
这里主要分析一个add()方法执行流程,至于其它的 remove()、set()……方法的思想和add()方法的思想都差不多
// LinkedList源码分析
public class LinkedListCRUD {
public static void main(String[] args) {
// 使用空参构造器初始化LinkedList对象。此时list的属性 --> size=0、first=null、last=null、modCount=0
LinkedList list = new LinkedList();
/**
public boolean add(E e) {
linkLast(e); // 调用linkLast(e) 方法
return true;
}
// linkLast(e) 方法
void linkLast(E e) {
final Node<E> l = last; // 使用final修饰的Node类型的局部变量来存储 last 的内存地址
final Node<E> newNode = new Node<>(l, e, null); // 正式将元素e放入一个新创建的Node节点
last = newNode; // 把last执行刚才新添加的node节点
// 如果原来的双向列表中的last属性为null,那么就说明原来的链表中没有元素(即为空链表),那么就把first也执行刚才添加的node节点上
if (l == null)
first = newNode;
// 如果原来的链表不为空的话,那么使用 l.next=newNode 来把原来链表的最后一个node的next属性赋值为新增node节点的地址。
// 因为这里 l 指向的是未添加新元素之前时链表的最后一个元素。并且这个l变量是被final进行修饰,所以l的值不会随着last变量的变化而变化
else
l.next = newNode;
// 通过上边的步骤,已经完成了新增节点的插入,以及 双向链表的链接以及first、last的指向问题。最后把list的size属性、modCount属性进行+1
size++;
modCount++;
}
// new Node(l,e,null) 方法
Node(Node<E> prev, E element, Node<E> next) {
this.item = element; // 赋值item
// 赋值新增元素的next,这里传入为null,因为新增元素后边肯定没元素了,所以就把next赋值为null
this.next = next;
// 帮当前双向链表的最后一个node节点赋值给该新增node节点的prev属性。
this.prev = prev;
}
*/
list.add("item1");
list.add("item2");
// 在双向链表的第一个位置插入元素(node)
list.addFirst("itemFirst");
// 在双向链表的最后一个位置插入元素(node)
list.addLast("itemLast");
// 根据元素内容移除元素
list.remove("itemFirst");
list.remove("item2");
System.out.println(list);
// 移除第一个元素
list.removeFirst(); // .remove()等效于 removeFirst(),因为remove方法就是调用的removeFirst()
// 移除最后一个元素
list.removeLast();
// 修改指定下标的元素
list.set(0,"item1-set");
System.out.println(list);
// 获取指定下标的元素
System.out.println(list.get(0));
// 两种遍历方法
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
for (Object o : list) {
System.out.println(o);
}
}
}
③ ArrayList 和 LinkedList的比较
1.5 Set集合
1.5.1 Set集合介绍
①Set集合特点
无序(添加和取出的顺序不一致),并且没有下标索引
不能存储重复的值(null 也只能存储一个)
因为Set、List都是Collection的子接口,因此set和list接口的常用方法基本一致,这里对于Set集合的常用方法不做过多阐述。
并且因为Set集合是无序的,所以在不能进行下标之类的操作,例如通过下标来进行CRUD以
接下来就来介绍关于Set集合接口的实现类
1.5.2 HashSet底层结构和源码分析
① HashSet说明
② HashSet源码解析
public class HashSetSource {
public static void main(String[] args) {
// 调用空参构造器
/**
public HashSet() {
map = new HashMap<>(); // 可以看到HashSet底层在调用HashMap的空参构造器,把hashMap对象赋值给自己的map属性
}
// HashMap的空参构造器
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // 把DEFAULT_LOAD_FACTOR 这个常量是加载因子赋值给loadFactor,默认值为0.75
}
*/
HashSet set = new HashSet();
/** 调用添加方法
public boolean add(E e) {
// 向map属性(即hashmap对象)添加元素 (因为set是单列,而map是双列:k-v,所以set这里把map的value填充为同一个常量PRESENT)
return map.put(e, PRESENT)==null;
}
// map.put(e,PRESENT)方法
public V put(K key, V value) {
// 又调用该方法。这里hash(key)对key进行hash计算,false表示的是onlyIfAbsent属性的值,true表示的是evict属性的值
return putVal(hash(key), key, value, false, true);
}
// hash(key)方法
static final int hash(Object key) {
int h;
// 如果key为null就直接返回值=0,如果不为null就根据key的hashCode方法并且进行再运算求出计算值
// 所以这里就决定了 null 会返回同一个值,就是0
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
// putVal(hash(key),key,value,false,true) 方法 =================== 重点=============================
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
// 定义临时局部变量。tab用来存储hashMap中的table索引数组。n用来存储hashMap中table数组的总长度
Node<K,V>[] tab; Node<K,V> p; int n, i;
// 如果hashMap中的table为空或者table数组的长度为0,那么就使用resize()方法进行扩容,并把扩容后的数组总长度赋值给n
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// 判断key进行hash后在hashMap的Node类型的table数组的对应下标位置是否为空
if ((p = tab[i = (n - 1) & hash]) == null)
// 为空就直接把新添加的node赋值在hashmap的table对应索引的位置
tab[i] = newNode(hash, key, value, null);
else {
// 如果hashmap的node类型的数组中下标等于hash操作后的key的位置不为空
Node<K,V> e; K k;
// 如果table[hash(key)]中的元素跟新增元素的key相同,那么就把该位置的值赋值给e,用于配合下边的对相同key的value替换问题
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
// 如果table[hash(key)] 的值是树类型的话,那么就调用树相关的方法,目的也是来完成插入操作
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
// 如果上边那种情况都不是,那就判断该索引的链表中是否有于该元素相同key的node
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
// 通过循环判断条件,可以走到该条件分支就表示该链表中没有与新增元素相同的key
// 那么就创建新的node,并让原来链表中最后一个node的next指向新node,新node的next置为空
p.next = newNode(hash, key, value, null);
// 如果此时一个链表的数组超过了8,那么就考虑树化
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
// 这里进行树化的时候会判断hashmap的table数组总长度是否达到64,如果没达到就暂时不树化,而是对table表进行2倍扩容
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
// 到这里就说明链表中有与新增元素相同key的node,那么就直接break循环。同时因为不断的e=p.next,此时 跳出循环后e只好指向的是链表中相同key的node
// 这个也是用来配合下边的判断 用来对相同key的value替换问题
break;
p = e;
}
}
if (e != null) {
// 用来进行在新增元素的时候 对于相同key的value替换(用新增的value来替换原来的value)
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
// 如果onlyIfAbsent为false(就是不使用putIfAbsent 来添加元素) 或者 原来node的value为null,那么就进行替换操作
e.value = value;
afterNodeAccess(e);
// 返回node原来旧的value
return oldValue;
}
}
++modCount; // 增加一次修改次数
if (++size > threshold) // 如果node的数量超过临界值,那就就对hashmap的table数组进行扩容
resize();
afterNodeInsertion(evict);
return null;
}
*/
set.add("java");
set.add("linux");
set.add("java");
System.out.println(set);
}
}
HashMap的扩容机制
从上边的源码可以看出来hashmap的扩容机制。
关于添加HashSet重复元素的判断
通过源代码中 (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))
可以看出来,判断key是否相同是 同时根据 hashCode(hash方法里面实际上调用hashCode,然后对hashCode进行再运算) 和 对应元素的 equals 方法。
那么根据这个我们可以自己定义对于HashSet来说什么情况才算是 元素相同。
下面是一个关于HashSet重复值的相关练习题
package com.llh.my_collection;
import java.util.HashSet;
import java.util.Objects;
/**
* @Description: TODO
* @Author lilinghao
* @Date 2025/3/2
**/
public class HashsetTest {
public static void main(String[] args) {
/** 练习要求: 要求创建一个hashset对象,创建三个Employee对象存放入hashset,要求如果employee的name和age相同,那么不能添加到hashset中
* 分析: hashset(hashmap) 的去重机制是根据元素的 hashCode 和 equals 来进行实现的。所以这里要对Employee对象name和age同时相同是进行去重,那么就需要
* 1. 重写Employee的hashCode()方法,要求其在 name、age相同时输出相同的hashCode
* 2. 重写Employee的equals方法,比较其name和age相同时就认为两个对象相同
*/
HashSet hashSet = new HashSet();
Employee e1 = new Employee("llh", 22);
Employee e2 = new Employee("lz", 22);
Employee e3 = new Employee("llh", 22);
hashSet.add(e1);
hashSet.add(e2);
hashSet.add(e3);
System.out.println(e1.hashCode());
System.out.println(e3.hashCode());
System.out.println(hashSet);
}
}
class Employee{
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Employee(String name, Integer age) {
this.name = name;
this.age = age;
}
public Employee() {
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
try {
Employee e2 = (Employee) o;
return this.getName().equals(e2.getName()) && this.getAge()==e2.getAge();
}catch (Exception e){
return false;
}
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
1.5.3 LinkedHashSet
①LinkedHashSet说明
LinkedHashSet通过双向链表来确保了插入和遍历的顺序一致。
public LinkedHashSet() {
super(16, .75f, true); //LinkedHashSet 初始化就直接初始化出容量为16的Node类型数组
}
1.6 Collections工具类
Collections是一个操作 List、Set 集合的工具类,提供了一系列的静态方法,用来实现对List、Set集合的排序、查询、修改等操作。
方法概述如下:
具体的演示代码如下:
package com.llh.learn1;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Random;
/**
* @Description: TODO
* @Author lilinghao
* @Date 2025/3/4
**/
public class MyCollectionsUtil {
public static void main(String[] args) {
ArrayList list = new ArrayList();
Random random = new Random();
for (int i = 0; i < 10; i++) {
list.add(random.nextInt(100));
}
System.out.println("======== 原List集合 ================");
System.out.println(list);
// 反转 Collections.reverse(List list) Collections.reverse(List list,Comparator comparator)
System.out.println("=======反转===============");
Collections.reverse(list);
System.out.println(list);
// 随机排序(洗牌) Collections.shuffle(List list)
System.out.println("==============洗牌==================");
Collections.shuffle(list);
System.out.println(list);
// 按顺序排序 Collections.sort()
System.out.println("============按默认顺序排序(从小到大)============");
Collections.sort(list);
System.out.println(list);
// 使用指定比较器进行排序 Collections.sort(List,Comparator)
System.out.println("========= 指定规则排序 ==========");
Comparator<Integer> comparator = (o1, o2) -> o1 - o2;
Collections.sort(list, comparator);
System.out.println(list);
// 交换对应下标的位置的元素 Collections.swap(List list,index1,index2)
System.out.println("====== 交换元素 ==========");
Collections.swap(list, 0, 1);
System.out.println(list);
// 当前集合最大元素 Collections.swap(Collection collection)
System.out.println("========当前集合最大元素=======");
System.out.println(Collections.max(list));
//Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
//比如,我们要返回长度最大的元素
// min() ,min(Comparator)
// 复制集合 copy(List desc,List src)
ArrayList list1 = new ArrayList();
// 因为ArrayList的初始长度为0,所以我们要先添加点元素让 ArrayList 扩容
for (int i = 0; i < 10; i++) {list1.add(i);}
Collections.copy(list1, list);
System.out.println("===========复制list集合=============\n"+list1);
// replaceAll(List,oldValue,newValue) 使用新值替换 List 对象的所有旧值
Collections.replaceAll(list, 47, 50);
// frequency(Collection,value) 统计元素在集合中出现的次数
System.out.println("50出现的次数"+Collections.frequency(list, 50));
}
}
2. Map接口和常用方法
2.1 Map接口实现类特点
key为null只能有一个 这个是因为通过hash和equals 对元素的key进行去重
前边的Set、List底层运用Map的时候都是对Map进行阉割使用,Set、List只需要使用Map中的key。所以可以理解Set、List是Map的阉割版。
Map的常用方法:
package com.llh.learn1;
import java.util.HashMap;
/**
* @Description: TODO
* @Author lilinghao
* @Date 2025/3/4
**/
public class HashMapTest {
public static void main(String[] args) {
HashMap map = new HashMap();
// 添加元素
map.put("1", "item1");
map.put("2", "item2");
map.put("3", null);
map.put(null, "null-hello");
map.put(null, "null-world"); // 会发生替换,最多只能有一个key为null。因为key去重同样包括null
System.out.println(map);
System.out.println("===================");
map.putIfAbsent("1", "hello"); // 如果不存在相同key就添加。(条件:原来元素的value不为null,如果为null则替换value)
map.putIfAbsent("3", "world"); // 因为3对应的值为value,所以这里就发生替换,替换后为 3 - world
System.out.println(map.put("2", "item2-替换")); // 如果发生替换就会返回原来的旧value
System.out.println("=====================");
System.out.println(map);
System.out.println(map.get("1"));
System.out.println(map.containsKey("1")); // 判断是否存在某个key
System.out.println(map.containsValue("world")); // 判断是否存在某个value
Collection values = map.values();
for (Object o : values){
System.out.println(o);
}
HashMap map1 = new HashMap();
for (int x = 1;x<=5;x++){
map.put(""+x,"元素:" + x);
}
map.putAll(map1);
System.out.println(map);
map.clear(); // 清楚map内的元素
System.out.println(map.isEmpty());
}
}
1.6 Collections工具类
Collections类是一个用来操作 Collection接口(就是List、Set接口实现类)的工具类。其中提供了一系列静态方法,对集合中的元素进行排序、cha
2.2 Map实现类的遍历
遍历原理: 遍历主要是通过Map内的EntrySet对象,这个对象是对Map内table内的链表内的node数据进行整理,用来方便我们遍历Map。
所以遍历map就使用提供的内部属性 KeySet、ValueSet、EntrySet来完成。
public static void useEntrySet(Map map){
for (Object o : map.entrySet()){
Map.Entry entry = (Map.Entry) o;
System.out.println("key:"+entry.getKey()+" ---> value:"+entry.getValue());
}
}
public static void useKeySet(Map map){
for(Object o : map.keySet()){
System.out.println("key:"+o+" ---> value:"+map.get(o));
}
}
public static void useIterator(Map map){
Iterator iterator = map.keySet().iterator();
while (iterator.hasNext()){
Object key = iterator.next();
System.out.println("key:"+key+" ---> value:"+map.get(key));
}
}
2.3 HashMap
对于HashMap的底层源码在分析 HashSet的时候就已经分析过了,因为HashSet底层就是HashMap。所以这里就不再进行源码分析了,直接上结论。
HashMap的结构是 Node类型的数组 + 单向链表 + 红黑树
HashMap源码底层结论
2.4 LinkedHashMap
linkedHashMap就是在HashMap的基础上把链表改为了双向链表,并且LinkedHashMap添加了 head 、tail属性,分别用来指向双向链表的头和尾节点。
关于图解: LinkedHashSet底层就是用的LinkedHashMap,所以那个图解就是LinkedHashMap的图解。如下
2.5 HashTable
HashTable 和 HashMap对比
其它方面Hashtable和HashMap基本相同。
关于Properties,该类是Hashtable的子类。所以Map集合的通用方法它也通用
package com.llh.learn1;
import java.util.Properties;
public class MyProperties {
public static void main(String[] args) {
Properties properties = new Properties();
properties.put("john", 100); //k-v
properties.put("lucy", 100);
properties.put("lic", 100);
properties.put("lic", 88); //如果有相同的 key , value 被替换
System.out.println(properties);
System.out.println(properties.get("john"));
properties.remove("lic");
properties.setProperty("john", "hello");
properties.setProperty("ming","world");
System.out.println(properties);
}
}