【第二阶段:java基础】第13章:集合(P493-P552):Collection(List、Set)、Map(HashMap、HashTable)、Collections

本文深入讲解Java集合框架体系,包括Collection和Map接口的不同实现类及其特点。重点介绍List、Set、Map接口的具体实现如ArrayList、HashSet、HashMap等,并探讨它们的底层实现机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本系列博客是韩顺平老师java基础课的课程笔记,B站:课程链接,吐血推荐的一套全网最细java教程,获益匪浅!

1. 集合框架体系

  • 集合

    • 可以动态地保存多个对象,使用比较方便
    • 提供了一系列方便的操作对象的方法:add、remove、set、get等
    • 使用添加删除元素比较简单。
  • 集合类有很多,主要分为两大类:单列集合和双列集合

  • collection接口实现类的特点

    • 可以存放多个元素,每个元素可以是object
    • 有些collection的实现类,可以存放重复的元素,有些不可以
    • 有些collection的实现类,是有序的(list),有些是无序的(set)
    • collection接口没有直接的实现子类,是通过它的接口Set和List来实现的
  • collection接口实现类的遍历,以ArrayList为例:

    • 使用iterator()迭代器进行遍历
      • 所有实现collection接口的集合类都有一个iterator()方法,用以返回iterator接口的对象
      • iterator仅用于遍历集合,本身并不存放对象
      • 循环退出时,iterator指向最后一个元素。如果需要重新遍历,要重置迭代器
    • 使用增强for循环进行遍历
      • 增强for循环底层也是用到了迭代器,可以将其理解为简化版本的迭代器
      • 数组也可以用增强for循环进行遍历
	// 方法1
    ArrayList arrayList = new ArrayList();
    Iterator iterator=arrayList.iterator();while (iterator.hasNext())
    {
                 //返回类型是object    
                 Object obj=iterator.next();
    }
    Iterator iterator=arrayList.iterator()
    // 方法2
    		for(Object book : arrayList){
              System.out.println(book);
              }
              

2. Collection(单列集合)

2.1 List

  • List接口的方法
    • 在这里插入图片描述
  • List接口的特点
    • List中元素是有顺序的(添加的顺序和取出的顺序一致),且有重复的元素
    • List中每个元素都有其对应的顺序索引,取元素:list.get(3)

以下是🍓List接口的三个常用实现类

1️⃣ArrayList

  • 可以加入null,并且可以加入多个
  • ArrayList基本等同于Vector,除了ArrayList是线程不安全的(但是执行效率高),在多线程的情况下,不建议使用ArrayList
  • 🍓ArrayList的扩容机制
    • ArrayList中维护了一个Object类型的数组elementData
    • 当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData容量为0,第一次添加元素后扩容为10,如果还需要扩容则扩容elementData为原来的1.5倍(0-10-15-22-33…)
    • 如果使用的是指定大小的构造器,则初始elementData为指定大小,如果需要扩容则直接扩容elementData为原来的1.5倍

2️⃣Vector

  • vector的底层也是一个对象数组
  • vector是线程同步的,即线程安全,vector类的操作方法带有synchronized
  • 在开发中,如果需要线程同步安全时,考虑使用vector
  • vector的扩容机制与ArrayList的扩容机制对比
    • 在这里插入图片描述

3️⃣ LinkedList

  • LinkedList实现了双向链表和双端队列的特点

  • 可以条件任意元素(包括null),且元素可重复

  • 线程不安全,没有实现同步

  • 底层操作机制

    • 底层维护一个双向链表
    • 维护了两个属性first 和 last 分别指向 首结点和尾结点
    • 每个结点维护了prev next item 三个属性,其中通过prev指向前一个,next指向后一个及诶单那,最终实现双向链表
    • 在这里插入图片描述
    • 所以LinkedList的元素添加和删除,不是通过数组完成的,相对来说效率更高
  • ArrayList和LinkedList的比较

    • 如果改查操作较多,选择ArrayList
    • 如果增删的操作较多,选择LinkedList
    • 一般来说,在程序中80%-90%的操作都是查询,因此大部分请鲁昂下会选择ArrayList
    • 在一个项目中,根据业务灵活原则,一个模块使用ArrayList,另一个模块使用LinkedList,也就是说,要根据业务来选择
    • 在这里插入图片描述

2.2 Set

  • Set接口的基本介绍
    • 无序(添加和去除的顺序不一致),没有索引
    • 不允许重复元素,所以最多包含一个null
  • Set接口的常用方法
    • 和List一样,Set接口也是Collection的子接口,因此常用方法和Collection接口一样
  • Set接口的遍历方式
    • 可以使用迭代器和增强for循环,不能使用索引的方式来获取

以下是🍓Set接口的常用实现类

1️⃣HashSet

  • 基本介绍

    • HashSet实现了Set接口
    • HashSet的底层实际上是HashMap,底层维护的是一个数组table+单向链表
    • 可以存放null值,但是只能有一个null
    • HashSet不保证元素是有序的,取决于hash后,再确定索引的结果。也就是说,不保证存放元素的顺序和取出的顺序一致,有可能一样,有可能不一样
    • 不能有重复的元素
    • 面试题:理解HashSet不能加入重复元素的真正含义需要看源码
    • 在这里插入图片描述
  • HashSet添加元素的机制

    • HashSet的底层实际上是HashMap
    • 添加一个元素,先得到hash值,然后转成索引值
    • 找到存储数据表table,看这个索引位置是已经存放了元素
    • 如果有,则调用equals进行比较(注意具体比较什么是由程序员定的,不一定是比较值的大小),如果相同则放弃添加,如果不同的,则添加到最后
  • HashSet的扩容机制

    • 第一次添加时,table扩容到16,临界值(threshold)是16*加载因子(loadFactor)0.75=12

    • 也就是说,不是到达16才进行第二次扩容,而是在12时就开始扩容

    • 如果table数组到临界值12,就会扩容到162=32,新的临界值是320.75=24,以此类推

    • 注意:新加入的结点无论是连接在链表之后,还是直接放入在table中,都是要占用一个size的

    • 在java8中,如果一条链表的个数到达TREEIFY_THRESHOLD(默认为8),并且table的大小>=MIN_TREEIFY_CAPACITY(默认为64),就会进行树化(红黑树),否则仍采用数组扩容机制(比如说table大小为16,但是某条链表上的元素已经有8个,该链表再加入新元素时,table会扩容为32,该链表长度变为9

    • 在没有树化之前,其实是数据结构中拉链法的hashmap

    • 在这里插入图片描述

    • 树化之后table没有什么变化,只要是长度>=8的单链表会变成一棵红黑树(Node结点变成TreeNode结点)

  • 练习题

    • 两个元素的hashcode相同只能说明两个元素存入的位置是一样的,接下来比较equal,equals返回true不再添加进去,equals返回false的话就会挂在在单链表后面
    • 在这里插入图片描述
    • hashcode和equals都需要重写,直快捷键alt+insert就可以快捷地重写
package com.leetcode;
import java.util.HashSet;
import java.util.Objects;
public class test {
public static void main(String[] args) {
        HashSet hashSet=new HashSet();        
        hashSet.add(new Employee("a",18));        
        hashSet.add(new Employee("b",17));        
        hashSet.add(new Employee("a",18));        	
        System.out.println(hashSet);    }
}

class Employee{
private String name;    
private int age;    
public Employee(String name, int age) {
this.name = name;        
this.age = age;    }

@Override    public String toString() {
return "Employee{"+
"name:"+name+
",age:"+age+"}";    }

@Override    
public boolean equals(Object o) {
if (this == o) return true;        
if (o == null || getClass() != o.getClass()) return false;        
Employee employee = (Employee) o;        
return age == employee.age && Objects.equals(name, employee.name);    }

@Override    
public int hashCode() {
return Objects.hash(name, age);    }
}
  • 思考题
    • 在这里插入图片描述
package com.leetcode;
import java.util.HashSet;
import java.util.Objects;public class test {
public static void main(String[] args) {
        HashSet hashSet=new HashSet();        
        hashSet.add(new Employee("linda",1,new MyDate(1,2,3)));        
        hashSet.add(new Employee("linda",4,new MyDate(1,2,3)));//前两个属于相同的员工        
        hashSet.add(new Employee("linda",1,new MyDate(1,3,3)));        
        //碰撞时不能添加进入HashSet必须满足两个条件:1. hashcode相同(代表存入到table中的相同位置)2.equals相同(代表存入的内容一样,不准存)        
        System.out.println(hashSet);    }
}

class Employee{
private String name;    
private int sal;    
private MyDate birthday;    
public Employee(String name, int sal, MyDate birthday) {
this.name = name;        
this.sal = sal;        
this.birthday = birthday;    }

@Override
public boolean equals(Object o) {
if (this == o) return true;        
if (o == null || getClass() != o.getClass()) return false;        
Employee employee = (Employee) o;        
return Objects.equals(name, employee.name) && Objects.equals(birthday, employee.birthday);    }

@Override    
public int hashCode() {
return Objects.hash(name, birthday);    }

@Override    
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", sal=" + sal +
", birthday=" + birthday +
'}';    }
}

class MyDate{
private int year;    
private int month;    
private int day;    
public MyDate(int year, int month, int day) {
this.year = year;        
this.month = month;        
this.day = day;    }

@Override    
public boolean equals(Object o) {
if (this == o) return true;        
if (o == null || getClass() != o.getClass()) return false;        
MyDate myDate = (MyDate) o;        
return year == myDate.year && month == myDate.month && day == myDate.day;    }

@Override    
public int hashCode() {
return Objects.hash(year, month, day);    }

@Override    
public String toString() {
return "MyDate{" +
"year=" + year +
", month=" + month +
", day=" + day +
'}';    }
}

2️⃣LinkedHashSet

  • 基本介绍

    • LinkedHashSet是HashSet的子类
    • LinkedHashSet的底层是LinkedHashMap,底层维护了一个table+双向链表
    • LinkedHashSet根据元素的hashcode值来决定元素的存储位置,同时使用链表维护元素的次序,这使得元素看起来是以插入的顺序保存的
    • LinkedHashSet不允添加重复的元素
  • 底层机制

    • 双向链表使得遍历LinkedHashSet时,也能确保插入顺序和遍历顺序一致(和HashSet不一样)

    • 在这里插入图片描述

    • 每一个结点都有before和after属性,这样形成一个双向链表

    • 添加元素时,首先求hash值,再求索引,确定其在table中的位置,然后将添加的元素加入到双向链表中(如果已经存在则不再添加)

  • 扩容机制

    • 第一次添加时,table扩容到16,存放的结点类型是LinkedHashMap$Entry
    • 数组的类型是HashMap N o d e [ ] ,存放的元素类型是 L i n k e d H a s h M a p Node[] ,存放的元素类型是LinkedHashMap Node[],存放的元素类型是LinkedHashMapEntry
    • 其他和HashSet相同

3️⃣TreeSet

  • 最大的特点是可以排序
  • 当使用无参构造器时,默认是无序的,但是可以使用有参构造器传入Comparetor接口
  • 不允许加入Comparetor比较结果相同的元素!(比如说compator按照字符串长度进行排序,如果两个字符串长度相同,那么后面进来的字符串无法插入)
  • TreeSet的底层还是TreeMap

3. Map(双列集合)

  • map接口的特点
    • 特点一

      • map与collection并列存在,用于保存具有映射关系的数据(key-value)
      • map中的key和value可以是任何引用类型的数据,会封装到HashMap$Node对象中
      • Map中的key不允许重复,原因和HashSet一样(当有相同的key时,会将原来的结点替换掉)
      • map中的value可以重复
      • map中的key和value都可以为null,但是key为null只能有一个,value为null可以有多个
      • 常用String作为map中的key,key是object类型的,因此只要是对象就可以放进去
      • key和value之间存在单向一对一关系,即通过key总能找到对应的value
      • HashSet中存的就是key,所有结点的value都填充了一个叫做present的对象
    • 特点二

      • map存放数据的key-value示意图,一对k-v是放在HashMap的Node结点中的(node结点是HashMap的一个内部类),又因为Node实现了Entry接口,有些书上也说一对k-v就是一个Entry
        1. k-v 最后是 HashMap$Node node=newNode(hash,key,value,null)
      • 2.k-v 为了方便程序员的遍历,还会创建EntrySet集合,该集合存放的类型Entry,每一个Entry有对象kv,也就是EntrySet<Entry<K,V>> 即 transient Set<Map.Entry<K,V>> entrySet
        1. EntrySet中定义的类型是Map.Entry,但是实际上存放的还是HashMap N o d e ,这是因为 H a s h M a p Node ,这是因为HashMap Node,这是因为HashMapNode实现了接口Map.Entry
        1. 当把HashMap$Node对象存到entrySet中就可以方便遍历,因为Map.Entry提供了两个重要的方法: getKey() getValue()
      • 在这里插入图片描述
      • 在这里插入图片描述

《这一节着实没听懂》

  • map接口的常用方法

    • 在这里插入图片描述
    • 在这里插入图片描述
  • map遍历的六种方式

    • 相关方法
      • containKey:查找键是否存在
      • keySet:获取所有的键值
      • entrySet:获取所有关系k-v
      • values:获取所有的值
    • 第一组:通过keySet先取出所有的key,再通过key取出对应的Value
    • 第二组:通过collection把所有的value取出
    • 第三组:通过EntrySet来获取k-v
Set keySet = map.keySet();
//第一组
for(Object key:keySet){
    System.out.println(key+"-"+map.get(key));}
Iterator iterator = keySet.iterator();while (iterator.hasNext()){
    Object key=iterator.next();    System.out.println(key+"-"+map.get(key));}

//第二组:
Collection values = map.values();
for(Object value :values){
    System.out.println(value);}
Iterator iterator1 = values.iterator();while (iterator1.hasNext()){
    Object o=iterator1.next();    System.out.println(o);}
//第三组
Set set = map.entrySet(); 
// entrySet<Map.Entry<k,v>>
for(Object entry : set){
    Map.Entry node=(Map.Entry) entry;    System.out.println(node.getKey()+"-"+node.getValue());}
Iterator iterator2 = set.iterator();while (iterator2.hasNext()){
    Map.Entry node=(Map.Entry) iterator2.next();    System.out.println(node.getKey()+"-"+node.getValue());}
  • map小结
    • HashMap是以k-v的方式来存储数据的,是Map接口使用频率最高的实现类
    • key不能重复,但是value是可以重复的,如果添加相同的key,会覆盖原来的k-v,等同于修改
    • 与HashSet一样,不保证映射的顺序,因为底层是以hash表的方式来存储的
    • HashMap没有实现同步,因此是线程不安全的

🍓以下是Map接口的常用实现类

1️⃣HashMap

  • (k,v)是一个Node类,它实现了接口Map.Entry<K,V>
  • 在这里插入图片描述
  • jdk7.0的HashMap的底层实现是【数组+链表】,jdk8底层的实现是【数组+链表+红黑树】
  • HashMap的扩容机制和HashSet完全一致(因为HashSet的底层就是用HashMap实现的)
  • 链表长度到达8,并且table长度到达64时才会树化。假如树化后,不停地删除这棵树上的结点,就会进行剪枝操作:将这棵树转为链表

2️⃣Hashtable

  • 基本介绍

    • 存放键值对
    • 键值都不允许为null
    • 使用方法和HashMap一样
    • HashTable是线程安全的,HashMap是线程不安全的
  • 底层机制

    • 底层有数组HashTable$Entry[] 初始化大小为11
    • 临界值 threshold 8 = 11 * 0.75 (即大小大于8时开始扩容)
    • 扩容机制: 211+1=23 即 newCapacity=oldCapacity2+1
  • hashMap与hashtable的对比

    • 在这里插入图片描述

2️⃣Properties

  • Properties继承自HashTable类并实现了Map接口,也是使用一种键值对的形式来保存数据的
  • 使用特点和hashtable类似
  • Properties还可以用于从xxx.properties文件中,加载数据到Properties类对象,并进行读取和修改
  • 说明:工作后xxx.properties文件通常作为配置文件,这个知识点在IO流举例

3️⃣TreeMap

  • 使用默认的构造器创建TreeMap是无序的(如果是字符串是默认按照字符串的大小进行排序的)
  • 如果两个k-v的compator结果相同,会将原来的v进行替换,k不变(比如compare按照k的字符串长度进行排序,有了【abc,123】后再加入h【sp,567】结果会变成【abc,567 】)

4. 总结

  • 开发中应该如何选择集合实现类?
    • 判断存储的类型:是一组对象还是一组键值对?
      • 一组对象:Collection接口

        • 允许重复:List

          • 增删多:LinkedList(底层维护了一个双向链表)
          • 改查多:ArrayList(底层维护Object类型的可变数组)
        • 不允许重复:Set

          • 无序:HashSet(底层是HashMap,维护一个哈希表,即数组+链表+红黑树)
          • 排序:TreeSet
          • 插入和取出顺序一致:LinkedHashSet,维护一个数组+双向链表
      • 一组键值对:Map接口

        • 键无序:HashMap(底层是jdk7:数组+链表,jdk8:数组+链表+红黑树)
        • 键排序:TreeMap
        • 键插入和取出顺序一致:LinkedHashMap
        • 读取文件:Properties

5. Collections工具类

  • 基本介绍

    • Collections是一个操作Set、List、Map等集合的工具类
    • Collections中提供了一系列静态的方法对集合元素进行排序,查询和修改等操作
  • 顺序操作

    • Collections.shuffle(list)
    • 在这里插入图片描述
  • 查找替换

    • 在这里插入图片描述

内容好多呀~ 不过不要害怕,继续往下面学!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

vector<>

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值