集合
集合
集合的理解和好处
数组不足:
- 长度开始时必须指定,,而且一旦指定不能被更改
- 保存的必须为同一类型元素
- 使用数组进行元素增加代码较为麻烦
数组扩容示例
Person[] pers = new Person(1);
per[0] = new Person();
增加新的person对象
Person[] pers2 = new Person[pers.length+1]; // 新创建数组
for(){} // for循环拷贝新的数组元素到pers2
pers2[pers2.length-1] = new Person(); // 添加新的对象
集合好处:
- 可以动态保存任意多个数组,使用方便
- 提供了一系列方便操作对象的方法:add,remove,set,get等
- 使用集合添加,删除新的元素简洁明了
集合框架体系(背诵记忆)
框架:

单列集合

双列集合

解读:
1.集合主要分为两组(单列集合,双列集合)
2.Collection 接口有两个重要子接口:List 和 Set ,他们的实现子类都是单列集合
3.Map 接口的实现子类是双列(K键,V值)集合,存放键值对
Collection接口
Collection接口实现特点
- Collection实现子类可以存放多个元素,每个元素可以是Object
- 有些Collection的实现类可以存放重复元素,有些不可以
- 有些Collection的实现类,有些是有序的(List),有些不是有序的(Set)
- Collection接口没有直接的实现子类,是通过他的子接口Set和List来实现的
Collection常用方法(实现子类ArrayList演示)
- add :添加单个元素
- remove :删除指定元素
- contains :查找元素是否存在
- size :获取元素个数
- isEmpty :判断是否为空
- clear :清空
- addAll :添加多个元素
- containsAll :查找多个元素是否都存在
- removeAll :删除多个元素
ps:
![]()
删除有这两种输入形式,第一种指定删除内容,返回值为boolean;第二种为删除指定元素下标,返回值为对象
方法详解:
package javaSEStudy.collection_;
import java.util.ArrayList;
import java.util.List;
// Collection 接口方法
public class CollectionMethod {
// 忽略所有警告信息
@SuppressWarnings("all")
public static void main(String[] args) {
List list = new ArrayList();
// add 添加单个元素
list.add("Hello");
list.add(10); // 相当于list.add(Integer(10))
list.add(true);
list.add("World");
list.add("Java");
System.out.println("添加: "+list);
// remove 删除指定元素
list.remove("Hello");// 删除Hello
list.remove(0); // 删除第1个元素
System.out.println("删除: "+list);
// contains 查找元素是否存在
System.out.println(list.contains("Java"));
// size 获取元素个数
System.out.println(list.size());
// isEmpty 判断是否为空
System.out.println(list.isEmpty());
// clear 清空,集合里面所有元素都没了
list.clear();
System.out.println("清空之后的集合: "+list);
// addAll 添加多个元素
ArrayList list1 = new ArrayList();
list1.add("红楼梦");
list1.add("三国演义");
list1.add("西游记");
list1.add("水浒传");
list.addAll(list1);
System.out.println("添加了一个新集合的集合: "+list);
// containsAll 查找多个元素是否都存在
System.out.println(list.containsAll(list1));
// 删除多个元素
list.add("聊斋");
list.removeAll(list1);
System.out.println("将list1集合内容删除后的集合: "+list);
}
}
Collection接口遍历元素
使用Iterator(迭代器)
- Iterator 对象称为迭代器,主要用于遍历Collection集合中的元素
- 所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象,即可以返回一个迭代器
- Iterator结构图
![]()
- Iterator仅用于遍历集合,Iterator本身并不存放对象
Iterator执行原理

package javaSEStudy.collection_;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
// Iterator 迭代器
public class CollectionIterator {
@SuppressWarnings({"all"})
public static void main(String[] args) {
Collection col = new ArrayList();
col.add(new Book("三国演义","罗贯中",10.1));
col.add(new Book("小李飞刀","古龙",5.1));
col.add(new Book("红楼梦","曹雪芹",62.3));
// System.out.println(col);
// 希望遍历col集合
/**
* 1.先得到col对应的迭代器
* 2.使用while循环遍历
* 3.当退出while循环后,Iterator指向最后的元素
* 4.如果希望再此遍历,需要重置迭代器 iterator = col.iterator();
* 快捷键输出while (iterator.hasNext()) {} -----> itit
* Ctrl+j 输出当前可以快捷生成的所有模版
*/
Iterator iterator = col.iterator();
while (iterator.hasNext()) {
// 判断是否还有数据
// 返回下一个元素类型是Object
Object obj = iterator.next();
System.out.println(obj);
}
// 重置迭代器
iterator = col.iterator();
System.out.println("======第二次迭代======");
while (iterator.hasNext()) {
// 判断是否还有数据
// 返回下一个元素类型是Object
Object obj = iterator.next();
System.out.println(obj);
}
}
}
class Book{
private String name;
private String author;
private double price;
public Book(String name, String author, double price) {
this.name = name;
this.author = author;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
@Override
public String toString() {
return "name='" + name + '\'' +
", author='" + author + '\'' +
", price=" + price ;
}
}
Iterator接口方法

使用for循环增强
增强for循环可以替代iterator迭代器,特点:增强for循环就是简化版的itreator,本质一样,只能用于遍历集合或数组
// 基本语法
for(元素类型 元素名:集合名或数组名){
访问元素
}
让我们来举个栗子
package javaSEStudy.collection_;
import java.util.ArrayList;
import java.util.Collection;
// 增强for循环详解
public class CollectionFor {
@SuppressWarnings("all")
public static void main(String[] args) {
Collection col = new ArrayList();
col.add(new Book("三国演义", "罗贯中", 10.1));
col.add(new Book("小李飞刀", "古龙", 5.1));
col.add(new Book("红楼梦", "曹雪芹", 62.3));
/**
* 详解
* 1.使用增强for,可以在Collection上
* 2.增强for,底层仍然是Iterator
* 3.增强for可以理解为简化版 迭代器遍历
* 4.快捷键 I
*/
// 使用增强for循环
for (Object book : col) {
System.out.println(book);
}
// 也可以直接在数组上使用
int[] num = {15, 15, 589, 43, 59, 31, 48, 942, 65};
for (int i : num) {
System.out.println(i);
}
}
}
课堂练习

package javaSEStudy.collection_.class_Test;
import java.util.ArrayList;
import java.util.Iterator;
// 创建3个对象,分别使用功能增强for和Iterator遍历输出
public class Test01 {
@SuppressWarnings({"all"})
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add(new Dog("Jack",1));
list.add(new Dog("Tom",2));
list.add(new Dog("Victoria",5));
// 使用迭代器Iterator
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
Object D = iterator.next();
System.out.println(D);
}
// 使用增强for循环
for (Object D : list) {
System.out.println(D);
}
}
}
class Dog{
private String name;
private int age;
public Dog(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
List接口
基本介绍
List接口是Collection接口的子接口
- List集合类中元素有序(即添加顺序和取出顺序一致),且可重复
- List集合中的每个元素都有其对应的顺序索引,即支持索引
- List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可根据序号存取容器中的元素
- JDK API中List接口的实现类有
![]()
常用方法
list集合里添加了一些根据索引来操作集合元素的办法
- void add(int index,Object ele) :在index位置插入ele元素
- boolean addAll(int index,Collection eles) :从index位置开始将eles中所有元素添加进来
- Object get(int index) :获取指定index位置的元素
- int indexOf(Object obj) :返回obj在当前集合中末次出现的位置
- Object remove(int index) :移除指定index位置的元素,并返回此元素
- Object set(int index,Object ele) :设置指定index位置的元素为ele,相当于替换
- List subList(int formIndex,int toIndex) :返回从formIndex到toIndex位置的子集合
package javaSEStudy.collection_.list;
import java.util.ArrayList;
import java.util.List;
// List接口的方法
public class ListMethod {
@SuppressWarnings({"all"})
public static void main(String[] args) {
List list1 = new ArrayList();
list1.add("Tom");
list1.add("Jack");
list1.add("Marry");
list1.add("蔡徐坤");
list1.add("小黑");
/**
* void add(int index,Object ele) :在index位置插入ele元素
*/
list1.add(1,"白展堂");
System.out.println("被插入数据后的集合: "+ list1);
/**
* boolean addAll(int index,Collection eles) :从index位置开始将eles中所有元素添加进来
*/
List list2 = new ArrayList();
list2.add("小黑");
list2.add("小白");
list2.add("小黄");
list2.add("小红");
list1.addAll(3,list2);
System.out.println("加入一个集合后的集合: "+ list1);
/**
* Object get(int index) :获取指定index位置的元素
*/
System.out.println("集合中第6个元素: "+list1.get(5));
/**
* int indexOf(Object obj) :返回obj在当前集合中首次出现的位置
* int lastIndexOf(Object obj) :返回obj在当前集合中末次出现的位置
*/
System.out.println("小黑在集合中首次出现的位置: "+list1.indexOf("小黑"));
System.out.println("小黑在集合中最后一次出现的位置: "+list1.lastIndexOf("小黑"));
/**
* Object remove(int index) :移除指定index位置的元素,并返回此元素
*/
System.out.println("集合被删除的元素: "+list1.remove(0));
System.out.println("删除元素后的集合: "+list1);
/**
* Object set(int index,Object ele) :设置指定index位置的元素为ele,相当于替换
*/
list1.set(0,"Victoria");
System.out.println("将第一个元素替换后的集合: "+list1);
/**
* List subList(int formIndex,int toIndex) :返回从formIndex到toIndex位置的子集合
* [0,3) 左闭右开
*/
List list = list1.subList(0, 3);
System.out.println("该位置的子集合: "+list);
}
}
课堂练习

package javaSEStudy.collection_.list.test;
import java.util.ArrayList;
import java.util.Iterator;
// 对集合进行操作
public class Test01 {
@SuppressWarnings({"all"})
public static void main(String[] args) {
ArrayList list = new ArrayList();
// list.add("Jack");
// list.add("Marry");
// list.add("Tom");
// list.add("Java");
// list.add("Python");
// list.add("小红");
// list.add("小黑");
// list.add("小黄");
// list.add("小白");
// list.add("小蓝");
for (int i = 0; i < 10; i++) {
list.add("小青"+i);
}
System.out.println("初始集合: "+list);
// 插入
list.add(1,"蔡徐坤");
// 获取第5个元素
System.out.println("第五个元素: "+list.get(4));
// 删除第六个元素
list.remove(5);
// 修改第七个元素
list.set(6,"叼毛");
// Iterator迭代器遍历
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
Object list1 = iterator.next();
System.out.print(list1+" ");
}
}
}
List的遍历方式
ps:ArrayList,LinkedList,Vector的遍历方式也和List一样
方式一:使用Iterator
方式二:使用增强for循环
方式三:使用普通循环

package javaSEStudy.collection_.list;
import java.util.*;
// List接口的遍历方式
public class ListFor {
@SuppressWarnings({"all"})
public static void main(String[] args) {
/**
* 下面这三种运行类型都不报错,证明使用遍历方法都是一样的
*/
List list = new ArrayList();
// List list = new Vector();
// List list = new LinkedList();
list.add("Jack");
list.add("Marry");
list.add("Tom");
list.add("Java");
list.add("Python");
// Iterator
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
Object obj = iterator.next();
System.out.println(obj);
}
System.out.println("=========================");
// 增强for
for (Object list1 : list) {
System.out.println(list1);
}
System.out.println("=========================");
// 普通for
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
}
课堂练习

package javaSEStudy.collection_.list.test;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Vector;
// 实现类的课堂练习
public class Test02 {
@SuppressWarnings({"all"})
public static void main(String[] args) {
/**
* 使用ArrayList
*/
ArrayList list1 = new ArrayList();
list1.add(new Book("红楼梦", "曹雪芹", 100.1));
list1.add(new Book("西游记", "吴承恩", 10.1));
list1.add(new Book("水浒传", "施耐庵", 9));
list1.add(new Book("三国演义", "罗贯中", 80));
list1.add(new Book("西游记", "吴承恩", 10.1));
bubbleSort(list1);
System.out.println(list1);
/**
* LinkedList
*/
LinkedList list2 = new LinkedList();
list2.add(new Book("红楼梦", "曹雪芹", 100.1));
list2.add(new Book("西游记", "吴承恩", 10.1));
list2.add(new Book("水浒传", "施耐庵", 9));
list2.add(new Book("三国演义", "罗贯中", 80));
list2.add(new Book("西游记", "吴承恩", 10.1));
bubbleSort(list2);
System.out.println(list2);
/**
* Vector
*/
Vector list3 = new Vector();
list3.add(new Book("红楼梦", "曹雪芹", 100.1));
list3.add(new Book("西游记", "吴承恩", 10.1));
list3.add(new Book("水浒传", "施耐庵", 9));
list3.add(new Book("三国演义", "罗贯中", 80));
list3.add(new Book("西游记", "吴承恩", 10.1));
bubbleSort(list3);
System.out.println(list3);
}
// 冒泡排序
public static void bubbleSort(List<Book> list) {
for (int i = 0; i < list.size() - 1; i++) {
for (int j = 0; j < list.size() - i - 1; j++) {
if (list.get(j).getPrice() > list.get(j + 1).getPrice()) {
Book temp = list.get(j);
list.set(j, list.get(j + 1));
list.set(j + 1, temp);
}
}
}
}
}
@Data
@AllArgsConstructor
class Book {
private String name;
private String author;
private double price;
}
ps:因为使用可lambok,就没有更改重写格式,输出就不是如图效果,但是原理是一样的
ArrayList
注意事项
- permits all elements,includding null ,ArrayList 可以加入多个null
- ArrayList 是由数组来实现数据存储的
- Arrays基本等同于Vector,除了ArrayList是线程不安全(执行效率高),在多线程情况下不建议使用ArrayList
底层源码分析
- ArrayList中维护了一个Object类型的数组elementData
transient Object[] elementData; // transient 表示短暂的,瞬间, 表示该属性不会被序列化 - 当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData容量为0,第一次添加时,则扩容为10,如需再次扩容,则扩容elementData为1.5倍,也就是elementData*1.5,单数舍去小数部分
- 如果使用的是指定大小的构造器,则初始elementData容量为指定大小,如果需要扩容,则直接扩容elementData为1.5倍
package javaSEStudy.collection_;
import java.util.ArrayList;
// ArrayList注意事项及源码分析
@SuppressWarnings({"all"})
public class ArrayListDetail {
public static void main(String[] args) {
ArrayList list = new ArrayList();
/**
* ArrayList 可以存放多个null
* 他的所有方法都没有加 ynchronized ()
*/
// list.add(null);
// list.add(null);
// list.add("玛丽");
// System.out.println(list);
/**
* 使用有参和无参构造器创建对象
* 默认初始化是无参构造
*/
// ArrayList list = new ArrayList(8);
// 使用for循环添加1~10个数据
for (int i = 0; i < 10; i++) {
list.add(i);
}
// 使用for循环添加11~15个数据
for (int i = 11; i <= 15; i++) {
list.add(i);
}
list.add(100);
list.add(200);
list.add(null);
}
}
无参构造器源码
首先执行方法,创建一个空的elementData数组

然后执行添加方法,但是会先确认ensureCapacityInternal() 确认是否扩容,在执行赋值

再确认minCapacity值,最开始minCapacity=0,DEFAULT_CAPACITY=10,取大值赋值

注意,此时不会先对其赋值,先去判断大小够不够

这里是执行扩容机制的源码

然后最后开始逐步返回值,将其扩容
有参构造器源码
首先执行的是有参构造

中间程序都和无参构造一样
而最后扩容机制
- 第一次扩容就按照elementData*1.5来扩容
- 整个执行流程都差不多
Vevtor
介绍
- Vector类的定义说明
![]()
- Vector的底层也是一个对象数组 protected Object[] elementData;
- Vector 是线程同步的,即线程安全,Vector类的操作方法带有synchronized
- 在开发中,需要线程同步安全时,考虑使用Vector
Vector和ArrayList的比较
| 底层结构 | 版本 | 线程安全,同步效率 | 扩容倍数 | |
|---|---|---|---|---|
| ArrayList | 可变数组 | JDK1.2 | 不安全,效率高 | 如果为有参构造,则是1.5倍;如果是无参构造,第一次为10,第二次为1.5倍 |
| Vector | 可变数组 | JDk1.0 | 安全,效率不高 | 如果是无参构造,默认为10,满后就按照2倍扩容;如果指定大小,则每次直接按2倍扩容 |
package javaSEStudy.collection_;
import java.util.Vector;
// Vector详解
@SuppressWarnings({"all"})
public class VectorDetail {
public static void main(String[] args) {
/**
* JDK17版本
* new Vector() 底层就是10
* 初始化方法(无参构造):
* public Vector() {
* this(10);
* }
* 初始化方法(有参构造):
* public Vector(int initialCapacity) {
* this(initialCapacity, 0);
* }
*/
// Vector v = new Vector(8);
Vector v = new Vector();
for (int i = 0; i < 10; i++) {
v.add(i);
}
v.add(100);
/**
* 扩容机制
* 1.首先自动装箱
* public static Integer valueOf(int i) {
* if (i >= IntegerCache.low && i <= IntegerCache.high)
* return IntegerCache.cache[i + (-IntegerCache.low)];
* return new Integer(i);
* }
* 2.添加数据到Vector集合
* public synchronized boolean add(E e) {
* modCount++;
* add(e, elementData, elementCount);
* return true;
* }
* 3.确定是否需要扩容
* private void add(E e, Object[] elementData, int s) {
* if (s == elementData.length)
* elementData = grow();
* elementData[s] = e;
* elementCount = s + 1;
* }
* 4.执行扩容
* 第一步
* private Object[] grow(int minCapacity) {
* int oldCapacity = elementData.length;
* int newCapacity = ArraysSupport.newLength(oldCapacity,
* minCapacity - oldCapacity, /* minimum growth
* capacityIncrement > 0 ? capacityIncrement : oldCapacity /* preferred growth );
* return elementData = Arrays.copyOf(elementData, newCapacity);
*}
* 第二步
* int prefLength = oldLength + Math.max(minGrowth, prefGrowth); // might overflow
* if (0 < prefLength && prefLength <= SOFT_MAX_ARRAY_LENGTH) {
* return prefLength;
* } else {
* // put code cold in a separate method
* return hugeLength(oldLength, minGrowth);
* }
* }
* 第三步
* public static int max(int a, int b) {
* return (a >= b) ? a : b;
* }
* 将扩容后的集合返回
* public static <T> T[] copyOf(T[] original, int newLength) {
* return (T[]) copyOf(original, newLength, original.getClass());
* }
*/
}
}
LinkedList
说明
- LinkedList底层实现了双向链表和双端队列特点
- 可以添加任意元素(元素可以重复),包括null
- 线程不安全,没有实现同步
底层操作机制
- LinkedList底层维护了一个双向链表
- LinkedList中维护了两个属性first和last分别指向首节点和尾节点
- 每个节点(Node对象),里面又维护了prev,next,item三个属性,其中通过prev指向前一个,通过next指向后一个节点,最终实现双向链表
- 所以LinkedList的元素的添加和删除,不是通过数组完成的,相对来说效率较高
- 模拟一个简单的双向链表
![]()
我们对双向链表简单理解
package javaSEStudy.collection_.LinkedLIst;
// 双向链表简单理解
@SuppressWarnings({"all"})
public class Doubly_LinkedList {
public static void main(String[] args) {
// 实现一个简单的双向链表
Node jack = new Node("jack");
Node queen = new Node("queen");
Node lady = new Node("lady");
// 连接三个结点,形成双向链表
// jack --> queen --> lady
jack.next = queen;
queen.next = lady;
// lady --> queen --> jack
lady.prev = queen;
queen.prev = jack;
// 让first引用指向jack,就是双向链表的头结点
Node first = jack;
// 让last引用指向 lady,就是双向链表的尾结点
Node last = lady;
/**
* 演示 从头到尾遍历(重要)
*/
System.out.println("------从头到尾-------");
while (true){
if (first == null){
break;
}
System.out.println(first);
first = first.next;
}
/**
* 演示 从尾到头遍历(重要)
*/
System.out.println("------从尾到头-------");
while (true){
if (last == null){
break;
}
System.out.println(last);
last = last.prev;
}
/**
* 演示双向链表的添加删除对象
* 在queen和lady之间添加一个对象
*/
Node victoria = new Node("victoria");
queen.next = victoria;
victoria.next = lady;
lady.prev = victoria;
victoria.prev = queen;
// 注意需要对first重置,否则无法输出
first = jack;
System.out.println("------从头到尾(二次遍历)-------");
while (true){
if (first == null){
break;
}
System.out.println(first);
first = first.next;
}
last = lady;
System.out.println("------从尾到头(二次遍历)-------");
while (true){
if (last == null){
break;
}
System.out.println(last);
last = last.prev;
}
}
}
// 定义一个Node类,Node 对象 表示双向链表的一个结点
@SuppressWarnings({"all"})
class Node{
public Object item; // 真正存放数据的地方,所以是Object
public Node next; // 指向下一个结点
public Node prev; // 指向上一个结点
public Node(Object name) {
this.item = name;
}
@Override
public String toString() {
return "Node name : " + item;
}
}
LinkedList修改
话不多说,我们直接上代码
package javaSEStudy.collection_.LinkedLIst;
import java.util.Iterator;
import java.util.LinkedList;
// LinkedList的增删改查
@SuppressWarnings({"all"})
public class LinkedListCRUD {
public static void main(String[] args) {
LinkedList list = new LinkedList();
for (int i = 1; i <= 10; i++) {
list.add(i);
}
System.out.println("添加: " + list);
/**
* 添加方法底层源码
* 1.new LinkedLIst 创建了一个空数组
* public LinkedList() {
* }
* 此时它的first和last都等于null
* 2.执行add方法
* public boolean add(E e) {
* linkLast(e);
* return true;
* }
* 3.将新的结点加到双向链表的最后
* 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++;
* }
*/
// 演示一个删除结点
list.remove(); // 如果没有参数默认删除第一个元素
// 此时在对新链表删除第三个元素
list.remove(2);
System.out.println("删除: " + list);
/**
* list.remove();
* 执行方法:
* public E remove() {
* return removeFirst();
* }
* 再次执行:
* public E removeFirst() {
* final Node<E> f = first;
* if (f == null)
* throw new NoSuchElementException();
* return unlinkFirst(f);
* }
* 删除底层源码unlinkFirst
* private E unlinkFirst(Node<E> f) {
* // assert f == first && f != null;
* final E element = f.item;
* final Node<E> next = f.next;
* f.item = null;
* f.next = null; // help GC
* first = next;
* if (next == null)
* last = null;
* else
* next.prev = null;
* size--;
* modCount++;
* return element;
* }
*/
// 修改某个结点对象
list.set(0, 999);
System.out.println("修改: " + list);
// 得到某个结点对象
// get(5)是得到双向链表的第六个对象
System.out.println(list.get(5));
// 遍历
// 因为LinkedList实现了List接口,遍历方式可用迭代器,增强for,普通for
System.out.println("--------遍历----------");
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
System.out.println("--------遍历----------");
for (Object o : list) {
System.out.println(o);
}
System.out.println("--------遍历----------");
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
}
比较
ArrayList和LinkedList比较
| 底层结构 | 增删的效率 | 改查的效率 | |
|---|---|---|---|
| ArrayList | 可变数组 | 较低;数组扩容 | 较高 |
| Vector | 双向链表 | 较高,通过链表增加 | 较低 |
如何选择?
- 如果我们改查操作较多,选择ArrayList
- 如果我们增删的操作较多,选择LInkedList
- 一般来说,在程序中80%~90%都是查询,因此大部分情况下都会选择ArrayList
- 在一个项目中,根据业务灵活选择,也可能一个模块使用ArrayList,一个模块使用LinkedList
Set
基本介绍
- 无序(添加和取出的顺序不一致),没有索引
- 不允许重复元素,所以最多包含一个null
- JDK API中的Set接口的实现类有(最主要的是(HashSet TreeSet))
![]()
Set接口
常用方法
和List接口一样,Set也是Collection的子接口,因此常用方法和Collection接口一样

遍历方式
同Collection的遍历方式一样,因为Set接口是Collection接口的子接口
1.可以使用迭代器
2.增强for
3.不能使用索引的方式来获取(也没有get方法),因此不能使用普通for
让我们来举个栗子
package javaSEStudy.collection_.set_;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
// Set方法详解
@SuppressWarnings({"all"})
public class SetMethod {
public static void main(String[] args) {
// 以Set的实现类HashSet展示
/**
* 1.set接口的实现类的对象(set接口对象),不能存放重复数据
* 2.可添加一个null
* 3.set接口对象存放数据是无序的(添加和取出顺序不一致)
* 4.但是取出顺序虽然不是添加的顺序,但是固定的
*/
Set set = new HashSet();
set.add("jack");
set.add("tom");
set.add("james");
set.add("james");
set.add(null);
set.add(null);
// 每次取出的值都是固定的
for (int i = 0; i < 10; i++) {
System.out.println(set);
}
/**
* 遍历 迭代器,增强for
* 即使自己遍历,输出顺序也是固定的
* Set没有get方法,也不能通过索引获取,所以不能使用普通for
*/
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
System.out.println("-------------------------");
for (Object o : set) {
System.out.println(o);
}
}
}
HashSet
说明
- HashSet实现了Set接口
- HashSet实际上是HashMap
- 可以存放null,但是也只能有一个null
- HashSet不保证元素使有序的,取决于Hash后,在确定索引值
- 不能有重读元素/对象,Set接口特性
package javaSEStudy.collection_.set_;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.HashSet;
// HashSet详解
@SuppressWarnings({"all"})
public class HashSet_ {
public static void main(String[] args) {
HashSet hashSet = new HashSet();
/**
* 1.HashSet本质上是HashMap
* 调用HashSet底层源码(构造器)
* public HashSet() {
* map = new HashMap<>();
* }
*
* 2.能存放null,只能存放一个
* 3.不保证存放元素顺序和取出顺序一致
*/
hashSet.add(1);
hashSet.add(null);
hashSet.add(null);
// 执行add方法后,会返回一个boolean值,添加成功 返回一个true,失败则返回false
System.out.println(hashSet.add(5));
System.out.println(hashSet.add(6));
System.out.println(hashSet.add(5));
// 可以删除对象,添加的对象加了引号,则删除时也得加,否则不用
hashSet.remove(6);
System.out.println(hashSet);
System.out.println("-----------------------------");
// 将hashSet重置,此时他为空
hashSet = new HashSet();
/**
* HashSet不能存放相同的元素/对象?
* 注意对象! 这里是new的,Dog类默认调用的是Object的equals和hashCode,基于内存比较
* 此时他们内存地址不同,认为这里是两个不同对象,所有都能存进去
*/
/**
* 假如在Dog类添加注解@Data @AllArgsConstructor
* Lombok生成的equals()和hashCode()基于name字段判断相等性。
* 所以此时字段相同,认为是同一个对象,去重保留一个
*/
hashSet.add("jack");
hashSet.add("jack");
hashSet.add(new Dog("Tom")); // 可以加入
hashSet.add(new Dog("Tom")); // 可以加入
System.out.println(hashSet);
// 经典面试题 只加入了一个蔡徐坤
// 因为存放的是String,将equals方法重写,按内容进行比较
hashSet.add(new String("蔡徐坤")); // T
hashSet.add(new String("蔡徐坤")); // F
System.out.println(hashSet);
}
}
//@Data
//@AllArgsConstructor
class Dog{
private String name;
public Dog(String name) {
this.name = name;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
'}';
}
}
TreeSet
底层其实是TreeMap
本质上是调用String类的compare比较器进行排序
package javaSEStudy.collection_Map.collection_.set_.treeSet;
import java.util.Comparator;
import java.util.TreeSet;
// TreeSet详解
@SuppressWarnings({"all"})
public class TreeSet_ {
public static void main(String[] args) {
// TreeSet ts = new TreeSet();
/**
* 底层源码
* 1.在没有做任何处理的时候,它是无序的(使用无参构造器)
* 2.使用TreeSet提供的一个构造器,可以传入一个比较器(匿名内部类),并指定排序规则
* 首先构造器把传入的构造器对象赋给了TreeSet 的底层 TreeMap
* 然后在调用 treeSet.add("world"),在底层会执行到
*
* // 先将比较器取出
* Comparator<? super K> cpr = comparator;
* if (cpr != null) {
* do {
* parent = t;
*
* // 这里的cpr就是我们的匿名内部类(对象)
* cmp = cpr.compare(key, t.key);
* if (cmp < 0)
* t = t.left;
* else if (cmp > 0)
* t = t.right;
* else {
* // 如果相等则返回0,那么这个数据则加入不了
* V oldValue = t.value;
* if (replaceOld || oldValue == null) {
* t.value = value;
* }
* return oldValue;
* }
* } while (t != null);
*
*/
TreeSet ts = new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
// 调用String的compareTo方法进行字符串的大小比较
// 按unicode编码进行排序,也就是首字母顺序
// return ((String) o1).compareTo((String) o2);
// 按加入元素的长度大小进行排序(从大到小)
// 这里相同长度的就无法加入,因为底层将其 认为长度相等就是同一个元素
return ((String) o2).length() - ((String) o1).length();
}
});
ts.add("hello");
ts.add("world");
ts.add("what");
ts.add("are");
ts.add("you");
ts.add("弄啥嘞");
System.out.println(ts);
}
}
源码初解
HashMap底层是(数组+链表+红黑树)
存放方式:
- 先获取元素的哈希值(HashCode方法)
- 对哈希值进行运算`,得出一个索引值,即为要存放在哈希表的位置号
- 如果该位置上没有别的元素,则直接存放
- 如果有元素,先进行equals判断,相等则不存放,不相同则以链表形式添加
ps:注意,有无对equals方法重写,未重写则比较的是地址
package javaSEStudy.collection_.set_;
// HashSet 模拟底层链表存储
@SuppressWarnings({"all"})
public class HashSetStructure {
public static void main(String[] args) {
// 模拟HashSet的底层 (HashMap的底层结构)
/**
* 1.创建一个数组,数组,数组类型是 Node[]
* Node[] 也可称为表
*/
Node[] table = new Node[16];
// 创建一个结点
Node john = new Node("john", null);
// 将该结点存放在索引为2的位置上
table[2] = john;
Node jane = new Node("jane", null);
// 将jane结点挂载到john后面
john.next = jane;
// 在链表后再添加一个数据
Node rose = new Node("rose", null);
jane.next = rose;
Node victoria = new Node("victoria", null);
// 在索引为3的位置存入结点victoria
table[3] = victoria;
System.out.println(table);
}
}
// 创建一个结点 存储数据,可以指向下一个结点,从而形成链表
class Node{
// 存放数据
Object item;
// 指向下一个结点
Node next;
public Node(Object item, Node next) {
this.item = item;
this.next = next;
}
}
形成链表结构

再添加元素

底层机制
添加元素
一个开发小技巧:在哪个位置需要局部变量(辅助变量),就在哪个位置创建
- 1.HashSet底层是HashMap
- 2.添加一个元素时,先得到hash值-->会转换成索引值
- 3.找到存储数据表table,看这个索引位置是否已经存放有元素
- 4.如果没有则直接加入
- 5.如果有调用equals(该equals方法由程序员自己确定)比较,如果相同,就放弃添加,如果不同则添加到最后
- 6.在Java8中,如果一条链表的元素个数到达TREEIFY_THRESHOLD(默认是8)最大值,并且table表的大小 >= MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树)
package javaSEStudy.collection_.set_;
import java.util.HashSet;
// HashSet底层也就是HashMap详解
@SuppressWarnings({"all"})
public class HashSetSource {
public static void main(String[] args) {
HashSet hashSet = new HashSet();
hashSet.add("php");
hashSet.add("java");
hashSet.add("python");
hashSet.add("java");
System.out.println(hashSet);
/**
* HashSet源码解读
* 1.执行构造器
* public HashSet() {
* map = new HashMap<>();
* }
* 2.执行一个add方法,E是泛型,将字符串常量赋值给e
* public boolean add(E e) {
* // 这里验证返回值是否为空,如果返回一个查询对象,则证明加入失败,为null才加入成功
* return map.put(e, PRESENT)==null;
* // 在这里 PERSENT是: private static final Object PRESENT = new Object();
* // 用于占位,让HashSet使用HashMap
* }
* 3.执行put方法,该方法会执行hash(key)方法,得到key对应的hash值
* public V put(K key, V value) { // k-->"php",value-->PRESENT(共享)
* return putVal(hash(key), key, value, false, true);
* }
*
* 哈希值是不是HashCode? 不是,因为这里(h = key.hashCode()) ^ (h >>> 16)执行了处理
* 哈希值计算方法
* static final int hash(Object key) {
* int h;
* return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
* }
* 4.核心代码
* 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 就是HashMap的一个数组,类型是Node[] 用于存放结点
* // 如果这个table的大小为0或者null,执行扩容,扩容到16个空间
* // resize()方法中计算了一个数组大小临界值(数组扩容大小*0.75),到达这个值(这里是12)就开始准备扩容
* if ((tab = table) == null || (n = tab.length) == 0)
* n = (tab = resize()).length;
*
* // (1)根据key,得到hash 去计算该key应该存放到table表中的哪个索引位置
* 并把这个位置的对象赋值给p
* // (2)再判断p是否为空
* 如果p为null,表示还没有存放过数据,就创建一个Node(key,value) 在这里key="php",value=PRESENT
* 就将该值放在这个位置 tab[i] = newNode(hash, key, value, null);
* // 第二次存入数据则直接进入这个判断,判断存入哪个位置
* if ((p = tab[i = (n - 1) & hash]) == null)
* // 在这里还存入了 hash,用于之后添加数据比较
* tab[i] = newNode(hash, key, value, null);
* else {
* // 开发技巧:在哪儿需要辅助变量,就在哪个位置定义
* Node<K,V> e; K k; // 定义辅助变量
* // 如果当前索引位对应链表的第一个元素和准备添加的key的hash值一样
* // 并且满足下面两个条件之一:
* 准备加入的key 和 p指向的Node结点的 key 是同一个对象
* p指向的Node结点的 key的equals()和准备加入的key 比较后相同
* 就不能加入
* if (p.hash == hash &&
* ((k = p.key) == key || (key != null && key.equals(k))))
* e = p;
*
* // 再判断p是不是一颗红黑树
* // 如果是红黑树,就调用putTreeVal该方法增加
* else if (p instanceof TreeNode)
* e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
*
* // 如果table对应的索引位置是一个链表,就依次比较
* else {
* // 循环比较,将拿到的值和链表中的值一个一个比较
* 1.如果依次比较后,都不相同,则放在最后
* ps:如果将该元素添加到链表后,立马判断该链表是否 >=8 个结点;
* 就调用treeifyBin(tab, hash)进行树化(红黑树);
* 在进行树化前,进行判断,判断条件(表为空或长度小于64):
* if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY(64))
* // 对table执行扩容
* resize();
* 只有当上述条件不成立,才执行树化
*
* 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;
* // 如果该数组大于了临界值(12),就执行扩容
* if (++size > threshold)
* resize();
* // 该方法对于HashMap是个空方法,留给子类去使用,例如生成一个有序链表
* afterNodeInsertion(evict);
* // 返回空代表挂载成功,否则就是返回原来的旧值
* return null;
* }
*/
}
}
HashSet扩容和转成红黑树机制
- HashSet的底层是HashMap,第一次添加时,table表数组扩容到16,临界值(threshold)是 16*加载因子(loadFactor)是0.75 = 12
ps:链表上的元素也被认为是添加了一个元素,所以即使table只有两个结点有值,但是值仍然到达了12,也会执行扩容 - 如果table数组使用到了临界值12,就会扩容到162 = 32,新的临界值就是320.75 = 24,以此类推
- 在java8中,如果一条链表的元素个数到达TREEIFY_THRESHOLD(默认为8),并且table的大小>=MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树),否则仍然采用数组扩容机制
ps:此处转红黑树机制:必须只有一条链表长度到达了8,且表长度为64才转红黑树,如果没有链表达到8,就无法树化,会一直按照扩容机制扩容
package javaSEStudy.collection_.set_;
import java.util.HashSet;
// 链表转红黑树
@SuppressWarnings({"all"})
public class HashSetIncrement {
public static void main(String[] args) {
HashSet hashSet = new HashSet();
/**
* 执行数组扩容机制
* 当链表达到最大长度8,执行数组扩容机制时
* 如果hash值相同,仍然会存储在该处,但是数组会扩容一次
* 直到表到达64,进行树化
*/
for (int i = 0; i < 12; i++) {
hashSet.add(new A(i));
}
}
}
// 重写HashCode方法,让返回的值都为100,形成链表
class A {
private int n;
public A(int n) {
this.n = n;
}
@Override
public int hashCode() {
return 100;
}
}
课堂练习

ps:当重写equals方法时,HashCode也得重写,否则比较无意义
package javaSEStudy.collection_.set_.test;
import java.util.HashSet;
import java.util.Objects;
// name和age被认为是相同员工不能存储
@SuppressWarnings({"all"})
public class Test01 {
public static void main(String[] args) {
HashSet hashSet = new HashSet();
hashSet.add(new Employee("John", 10));
hashSet.add(new Employee("Marry", 56));
hashSet.add(new Employee("John", 10));
System.out.println(hashSet);
}
}
class Employee {
private String name;
private int age;
public Employee(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public boolean equals(Object o) {
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);
}
@Override
public String toString() {
return "name='" + name + '\'' +
", age=" + age;
}
}

package javaSEStudy.collection_.set_.test;
import java.util.HashSet;
import java.util.Objects;
// name和age被认为是相同员工不能存储
@SuppressWarnings({"all"})
public class Test01 {
public static void main(String[] args) {
HashSet hashSet = new HashSet();
hashSet.add(new Employee("John", 22,new MyDate(2005,5,4)));
hashSet.add(new Employee("Marry", 22,new MyDate(2005,5,4)));
hashSet.add(new Employee("John", 22,new MyDate(2005,5,4)));
System.out.println(hashSet);
}
}
class Employee {
private String name;
private int age;
private MyDate birthday;
public Employee(String name, int age, MyDate birthday) {
this.name = name;
this.age = age;
this.birthday = birthday;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public MyDate getBirthday() {
return birthday;
}
public void setBirthday(MyDate birthday) {
this.birthday = birthday;
}
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
return Objects.equals(getName(), employee.getName()) && Objects.equals(getBirthday(), employee.getBirthday());
}
@Override
public int hashCode() {
return Objects.hash(getName(), getBirthday());
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", age=" + age +
", 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;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
public int getDay() {
return day;
}
public void setDay(int day) {
this.day = day;
}
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
MyDate myDate = (MyDate) o;
return getYear() == myDate.getYear() && getMonth() == myDate.getMonth() && getDay() == myDate.getDay();
}
@Override
public int hashCode() {
return Objects.hash(getYear(), getMonth(), getDay());
}
@Override
public String toString() {
return year +"-"+ month +"-"+ day;
}
}
LinkedHashSet
说明
- LinkedHashSet是HashSet的子类
- LinkedHashSet 底层是 LinkedHashMap,底层维护了一个 数组+双向链表
- LinkedHashSet 根据元素 HashCode 值来决定元素的存储位置,同时使用链表来维护元素的次序,这使得元素看起来是以插入顺序保存的
- LinkedHashSet 不允许添加重复元素
底层源码
- 1.在LinkedHashSet中维护了一个Hash表和双向链表(LinkedHashSet 有head和tail)
- 2.每一个节点有before和after属性,这样可以形成双向链表
- 3.在添加一个元素时,先求Hash值,再求索引,确定该元素在table表中的位置,然后将添加的元素加入到双向链表,如果已存在,则不添加(原则上和hashSet一样)
- 4.这样的话,我们遍历LinkedHashSet也能确保插入顺序和遍历顺序一致(有序地)
- 5.第一次添加时,直接将数组table扩容到15,存放的节点类型是LinkedHashMap$Entry
- 6.数组是 HashMap$Node[] 存放的元素是 LinkedHashMap$Entry类型 就说明Node是Entry的超类
继承关系在内部类完成
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
Map接口

ps:这里讲解的是JDK8的Map接口特点(很实用)
- 1.Map和Collection并列存在,用于保存具有映射关系的数据:Key-Value
- 2.Map中的 key 和 value 可以是任何引用类型的数据,会封装到HashMap$Node对象中
- 3.Map中的 key 不允许重复,原因和HashSet一样
- 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 是放在一个HashMap$Node中的,有因为Node实现了 Entry接口,有些书上也说一对k-v就是一个Entry
![]()
对8进行详细说明
左边是一个table表,使用HashMap$Node 存放数据,然后将这些数据集中存放到一个集合当中,这个集合叫做entrySet,在将entrySet这个集合存放到右边的Entry中(方便控制,加强管理),Entry是实现了Set接口的,使用KeySet方法可单独控制输入Key值,使用Value可单独控制传入Value值,注意这里并不是存储,而是指向,实际数据还是存放在table中
key使用的是Set接口,而value使用的是Collection接口
话不多说,我们直接看代码
package javaSEStudy.collection_.map_;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
// Map接口实现类特点
@SuppressWarnings({"all"})
public class Map_ {
public static void main(String[] args) {
/**
* 1.保存具有映射关系数据 Key-Value
* 这里虽然看起来是有序的,但是其实还是无序的
* 底层是按照哈希算法进行排序存放
* 2.map中的 key 和 value 可以是任何引用类型的数据,会封装到HashMap$Node对象中
*/
Map map = new HashMap();
map.put("NO.1","Jack"); // 第一个为 Key , 第二个为 Value
map.put("NO.2","Marry");
/**
* 3.map中的 key 不允许重复,和HashSet一样
*/
// 注意,这里当key值相同时,这里是采用替换机制去做
// 所以key值为NO.2的value被替换为 蔡徐坤
map.put("NO.2","蔡徐坤"); // 当有相同的key值,等价于替换
/**
* 4.map中的value可以重复
*/
map.put("NO.3","Jack");
/**
* 5.Map中的 key 可以为null,value也可以为 null,注意:key为null只能有一个,value为null,可以有多个
*/
map.put(null,null);
map.put(null,"基尼太美");
map.put("NO.4",null);
map.put("NO.5",null);
/**
* 6.常用String类作为Map的key
*/
map.put(1,"张三丰");
map.put("String类","无极");
map.put(new Object(),"天下无贼"); // 也满足 key-value
/**
* 7.key 和 value 之间存在单向一对一关系,即通过指定的 key 值总能找到对应的value
* 通过get方法传入一个key值,会返回对应的value
*/
System.out.println(map.get(1));
System.out.println(map.get(null));
System.out.println(map.get("NO.5"));
System.out.println(map);
/**
* 8.k-v为了方便程序员遍历,还会创建EntrySet集合,该集合存放的元素类型 Entry
* 而一个Entry对象就有 k , v --> EntrySet<Entry<K,V>>
* entrySet 中,定义的类型为 map.Entry,但是实际上还是存放的是 HashMap$Node
* 这是因为 static class Node<K,V> implement Map.Entry<K,V>
*
* 这样做的好处:当把 HashMap$Node 对象存放到 EntrySet 就方便我们的遍历,因为 Map.Entry 提供了重要的方法
* getKey() getValue()
*/
Set set = map.entrySet();
// 查看类型
System.out.println(set.getClass()); // HashMap$EntrySet
for (Object obj : set) {
// 查看存储的类型
// System.out.println(obj.getClass()); // HashMap$Node
// 为了从HashMap$Node取出k-v,需要向下转型
Map.Entry entry = (Map.Entry) obj;
System.out.println(entry.getKey()+":"+entry.getValue());
}
}
}
常用方法

- put :添加
- remove :根据键删除对应映射关系
- get :根据键获取值
- size :获取元素个数
- isEmpty :判断个数是否为0
- clear :清除
- containsKey :查找键是否存在
话不多说,我们直接上代码
package javaSEStudy.collection_.map_;
import org.w3c.dom.ls.LSOutput;
import java.util.HashMap;
import java.util.Map;
// map接口常用方法
@SuppressWarnings({"all"})
public class MapMethod {
public static void main(String[] args) {
/**
* put 添加
*/
Map map = new HashMap();
map.put("邓超",new Book("",10)); // 第一次正常存入
map.put("邓超","孙俪"); // 第二次被替换
map.put("王宝强","马蓉"); // 正常存入
map.put("宋喆","马蓉"); // 正常放入,男主不同
map.put("大碗宽面",null); // 进去了,key值唯一,正常存入
map.put(null,"刘亦菲"); // 正常存入
map.put("蔡徐坤","基尼太美"); // 正常存入
System.out.println(map);
/**
* remove 根据键值删除映射关系
* 这里有两种方法,一种只填入key值,另一种将key和value一起输入
*/
map.remove(null);
map.remove("蔡徐坤","基尼太美");
System.out.println(map);
/**
* get 根据键获取值
*/
System.out.println(map.get("邓超"));
/**
* size 获取元素个数,k-v算一个
*/
System.out.println(map.size());
/**
* isEmpty 判断个数是否为0 输出为Boolean
*/
System.out.println(map.isEmpty());
/**
* containsKey 查找键是否存在
* containsValue 查找值是否存在
*/
System.out.println(map.containsKey("王宝强"));
System.out.println(map.containsValue("马蓉"));
/**
* clear 清除k-v
*/
map.clear();
System.out.println("map: "+map);
}
}
class Book {
private String name;
private int num;
public Book(String name, int num) {
this.name = name;
this.num = num;
}
}
Map六大遍历方式
遍历需要用到方法
containsKey :查找键是否存在
keySet :获取所有的键
entrySet :获取所有的关系
values :获取所有的值
package javaSEStudy.collection_.map_;
import java.util.*;
// Map的六大遍历方式
@SuppressWarnings({"all"})
public class MapTraversal {
public static void main(String[] args) {
Map map = new HashMap();
map.put("邓超",new Book("",10)); // 第一次正常存入
map.put("邓超","孙俪"); // 第二次被替换
map.put("王宝强","马蓉"); // 正常存入
map.put("宋喆","马蓉"); // 正常放入,男主不同
map.put("大碗宽面",null); // 进去了,key值唯一,正常存入
map.put(null,"刘亦菲"); // 正常存入
map.put("蔡徐坤","基尼太美"); // 正常存入
/**
* 第一组:将所有的key值取出,通过key取出对应的values
*/
Set keySet = map.keySet();
// 1.增强for 最简单
System.out.println("======第一种:增强for======");
for (Object key : keySet) {
System.out.println(key + ":" + map.get(key));
}
// 2.迭代器
System.out.println("======第二种:迭代器======");
Iterator iterator = keySet.iterator();
while (iterator.hasNext()) {
Object key = iterator.next();
System.out.println(key + ":" + map.get(key));
}
/**
* 第二组:把所有的values取出
*/
Collection values = map.values();
// 这里是个Collection的集合,有两种方式将其遍历
// 1.增强for
System.out.println("------增强for------");
for (Object value : values) {
// 无法通过values反向取值,只能显示values
System.out.println(value);
}
// 2.迭代器
System.out.println("------迭代器------");
Iterator iterator1 = values.iterator();
while (iterator1.hasNext()) {
Object value = iterator1.next();
System.out.println(value);
}
/**
* 第三组:通过EntrySet 来获取k-v
*/
Set entrySet = map.entrySet();
// 1.增强for
System.out.println("******* 增强for *******");
for (Object entry : entrySet) {
// 将entry转成Map.Entry
Map.Entry m = (Map.Entry) entry;
System.out.println(m.getKey() + ":" + m.getValue());
}
// 2.迭代器
System.out.println("******* 迭代器 *******");
Iterator iterator2 = entrySet.iterator();
while (iterator2.hasNext()) {
// HashMap$Node -(实现)-> Map.Entry(getKey,getValue)
// 在这里个取出的m就是 HashMap$Node
Map.Entry m = (Map.Entry) iterator2.next();
System.out.println(m.getKey() + ":" + m.getValue());
}
}
}
该怎么使用?
- 1.默认使用的是entrySet()
- 2.大数据量时必选entrySet()
- 3.需要删除元素时,使用迭代器
Map课堂练习

package javaSEStudy.collection_.map_.test;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
// 添加员工对象
@SuppressWarnings({"all"})
public class Test01 {
public static void main(String[] args) {
HashMap hashMap = new HashMap();
hashMap.put(1,new Employees("Jack",19000,1));
hashMap.put(2,new Employees("Jane",15000,2));
hashMap.put(3,new Employees("James",21000,3));
hashMap.put(4,new Employees("Marry",12000,4));
hashMap.put(5,new Employees("Tom",1000,5));
// 开始遍历
// 使用增强for(效率低)
Set set = hashMap.keySet();
for (Object key :set) {
// 通过键获取对应的值,并强制转换为Employees
Employees obj =(Employees) hashMap.get(key);
if (obj.getSalary() > 18000){
System.out.println(obj);
}
}
System.out.println("---------------------------------");
// 使用 entrySet 迭代器(效率高)
Set set1 = hashMap.entrySet();
Iterator iterator = set1.iterator();
while (iterator.hasNext()) {
// 获取下一个键值对,并转换为entry
Map.Entry entry = (Map.Entry) iterator.next();
// 从entry中获取值,并强制转换为Employees
// if可以简写 ((Employees) entry.getValue()).getSalary()
Employees value =(Employees) entry.getValue();
if (value.getSalary() > 18000){
System.out.println(value);
}
}
}
}
class Employees {
private String name;
private double salary;
private int Id;
public Employees(String name, double salary, int Id) {
this.name = name;
this.salary = salary;
this.Id = Id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public int getId() {
return Id;
}
public void setId(int id) {
this.Id = id;
}
@Override
public String toString() {
return "name='" + name + '\'' +
", salary=" + salary +
", Id=" + Id;
}
}
HashMap小结
- 1.Map接口的常用的实现类:HashMap,Hashtable和Properties
- 2.HashMap是Map接口使用频率最高的实现类
- 3.HashMap是以key-value 对的方式来存储数据(HashMap$Ndoe类型)
- 4.key不能重复,但是值可以重复,允许使用null键和null值
- 5.如果添加相同的key,则会覆盖原来的key-value,等同于修改(key不会替换,value会)
- 6.与HashSet一样,不保证映射顺序,因为底层是以hash表存储(JDK8的HashMap 底层: 数组+链表+红黑树)
- 7.HashMap没有实现同步(方法没有做同步互斥操作),因此线程不安全
HashMap底层源码
扩容机制(和HashSet相同)
- 1.HashMap底层维护了Node类型的数组table,默认为null
- 2.当创建对象时,将加载因子(loadfactor)初始化为0.75
- 3.当添加k-v时,通过key的哈希值得到table的索引,然后判断该索引处是否有元素,如果没有元素则直接添加,如果该索引处有元素,继续判断key值和准备加入的key值是否相等,如果相等则直接替换value,如果不相等需要判断是树结构还是链表结构,做出相应处理,如果添加时发现容量不够,则需要扩容 rasize()
- 4.第一次添加,则需要扩容table容量为16,临界值(threshould)为12 (16*0.75 )
- 5.以后扩容,则将扩容的table表的容量为原来的2倍,临界值也为原来的两倍,以此类推
- 6.在Java8中,如果一条链表的元素个数超过TREEIFY_THRESHOLD(默认为8),并且table的大小 >= MIN_TREEIFY_CAPACITY(默认为64),就会进行树化(红黑树)
ps:此处转红黑树机制:必须只有一条链表长度到达了8,且表长度为64才转红黑树,如果没有链表达到8,就无法树化,会一直按照扩容机制扩容
我们直接上代码展示
package javaSEStudy.collection_.map_;
import java.util.HashMap;
// HashMap源码详解
@SuppressWarnings({"all"})
public class HashMapSource {
public static void main(String[] args) {
HashMap hashMap = new HashMap();
hashMap.put("java", 10);
hashMap.put("python", 10);
hashMap.put("java", 20);
/**
* 代码解读
* 1.初始化加载因子
* public HashMap() {
* this.loadFactor = DEFAULT_LOAD_FACTOR;
* }
* 此时 HashMap$Node[] table = null
*
* 2.执行put方法(会计算哈希值)
* public V put(K key, V value) {
* return putVal(hash(key), key, value, false, true);
* }
* 3.执行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表为0,执行扩容到16
* if ((tab = table) == null || (n = tab.length) == 0)
* n = (tab = resize()).length;
*
* // 如果计算出的哈希值对应table的索引位置为null,就把加入的k-v创建成一个Node,加入该位置
* if ((p = tab[i = (n - 1) & hash]) == null)
* tab[i] = newNode(hash, key, value, null);
* else {
* Node<K,V> e; K k;
*
* // table表的索引位置的哈希值和新加入的哈希值相同,且(同一个对象 或 equals(内容相同)返回为真)
* // 就认为不能加入
* if (p.hash == hash &&
* ((k = p.key) == key || (key != null && key.equals(k))))
* e = p;
*
* // 是否是一颗红黑树,是则按照红黑树方式处理
* else if (p instanceof TreeNode)
* e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
*
* // 是否是一个链表
* else {
* // 死循环
* for (int binCount = 0; ; ++binCount) {
* // 如果在这个链表中没有相同的值,则放在最后
* if ((e = p.next) == null) {
* p.next = newNode(hash, key, value, null);
* // 判断链表长度是否到达8,从而进入treeifyBin判断是否树化
* if (binCount >= TREEIFY_THRESHOLD - 1)
* treeifyBin(tab, 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)
* // 在这里做了一个替换操作
* e.value = value; // 将key对应的value替换
* afterNodeAccess(e);
* return oldValue;
* }
* }
* ++modCount;
* if (++size > threshold)
* resize();
* afterNodeInsertion(evict);
* return null;
* }
* 4.关于树化(红黑树)
* 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();
* 如果table表为null,或者大小还没到64,暂时不执行树化,而是先扩容
* 否则才会进行真正的树化 --> 剪枝 (红黑树退化为链表)
*/
}
}
LinkedHashMap
LinkedHashMap 详解
LinkedHashMap 是 Java 集合框架中的一个重要类,它继承自 HashMap,同时通过维护一个双向链表来保持元素的插入顺序或访问顺序。
基本特性
- 继承关系:
LinkedHashMap<K,V>extendsHashMap<K,V>implementsMap<K,V> - 有序性:与 HashMap 的无序不同,LinkedHashMap 可以保持元素的插入顺序或访问顺序
- 性能:提供了与 HashMap 相近的时间复杂度(O(1) 的基本操作)
核心实现原理
LinkedHashMap 通过在 HashMap 的节点基础上添加双向链表指针来实现有序性:
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after; // 双向链表指针
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
两种排序模式
-
插入顺序(默认):元素按照插入顺序排列
- 新插入的元素放在链表尾部
- 已存在的键重新插入不会改变顺序
-
访问顺序:元素按照访问顺序排列(最近访问的放在最后)
- 通过构造方法设置:
new LinkedHashMap(16, 0.75f, true) - 访问包括 get、put、replace 等操作
- 适合实现 LRU 缓存
- 通过构造方法设置:
重要方法
-
构造方法:
// 默认插入顺序 LinkedHashMap() // 指定初始容量和加载因子 LinkedHashMap(int initialCapacity, float loadFactor) // 指定初始容量、加载因子和排序模式 LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) -
覆盖的钩子方法:
// 在插入新节点后调用,可用于实现LRU的移除策略 protected boolean removeEldestEntry(Map.Entry<K,V> eldest)
实现LRU缓存示例
// 最大容量为100的LRU缓存
final int MAX_ENTRIES = 100;
Map<String, String> lruCache = new LinkedHashMap<String, String>(MAX_ENTRIES, 0.75f, true) {
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > MAX_ENTRIES;
}
};
与HashMap的比较
| 特性 | HashMap | LinkedHashMap |
|---|---|---|
| 顺序 | 无序 | 插入顺序或访问顺序 |
| 迭代性能 | 较快 | 稍慢(需要遍历链表) |
| 内存占用 | 较少 | 较多(维护链表指针) |
| 适用场景 | 通用键值存储 | 需要保持顺序的场景 |
使用注意事项
- 线程不安全:与 HashMap 一样,LinkedHashMap 不是线程安全的
- 迭代性能:迭代 LinkedHashMap 比迭代 HashMap 稍慢
- 内存开销:每个元素需要额外存储两个引用(before 和 after)
LinkedHashMap 是一个非常实用的集合类,特别适合需要保持元素顺序的场景,如实现缓存机制、记录操作历史等。
Hashtable
基本介绍
- 1.存放的元素是键值对:即k-v
- 2.Hashtable的键和值都不能为null,否则抛出异常:NullPointterException
- 3.Hashtable的使用方法基本上和HashMap一致
- 4.Hashtable是线程安全的,HashMap是线程不安全的
package javaSEStudy.collection_Map.map_.hashtable_;
import java.util.Hashtable;
// Hashtable详解
@SuppressWarnings({"all"})
public class Hashtable_ {
public static void main(String[] args) {
Hashtable table = new Hashtable();
table.put("A", 1);
table.put("B", 2);
table.put("C", 3);
table.put("D", 4);
table.put("D", 88); //执行替换
// 以下三种写法都会报空指针异常
// table.put(null, null);
// table.put("E", null);
// table.put(null, 5);
System.out.println(table);
/**
* 简单说明底层
* 1.底层有个数组 Hashtable$Entry[] 初始化大小为11
* 2.threshold(临界值) 8 --> 11*0.75
* 3.扩容:按照自己的扩容机制,并非2倍
*/
}
}
扩容机制
package javaSEStudy.collection_Map.map_.hashtable_;
import java.util.Hashtable;
// Hashtable 底层源码
@SuppressWarnings({"all"})
public class HashtableSource {
public static void main(String[] args) {
Hashtable table = new Hashtable();
table.put("A", 1);
table.put("B", 2);
table.put("C", 3);
table.put("D", 4);
table.put("D", 88);
table.put("hello1", 1);
table.put("hello2", 1);
table.put("hello3", 1);
table.put("hello4", 1);
table.put("hello5", 1);
System.out.println(table);
/**
* Hashtable 自己的扩容机制
* 真正执行扩容的方法:
* addEntry(hash, key, value, index); 添加k-v 封装到 Entry\
*
* rehash(); 这个里面是扩容具体方法
*
* 扩容方法详解
* protected void rehash() {
* int oldCapacity = table.length;
* Entry<?,?>[] oldMap = table;
*
* // overflow-conscious code
* // 扩容机制: 原来的数组容量 * 2 + 1
* int newCapacity = (oldCapacity << 1) + 1;
* if (newCapacity - MAX_ARRAY_SIZE > 0) {
* if (oldCapacity == MAX_ARRAY_SIZE)
* // Keep running with MAX_ARRAY_SIZE buckets
* return;
* newCapacity = MAX_ARRAY_SIZE;
* }
* Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];
*
* modCount++;
* threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
* table = newMap;
*
* for (int i = oldCapacity ; i-- > 0 ;) {
* for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {
* Entry<K,V> e = old;
* old = old.next;
*
* int index = (e.hash & 0x7FFFFFFF) % newCapacity;
* e.next = (Entry<K,V>)newMap[index];
* newMap[index] = e;
* }
* }
* }
*
*
*/
}
}
比较
HashMap 和 Hashtable
| 版本 | 线程同步(安全) | 效率 | 允许Null键null值 | |
|---|---|---|---|---|
| HashMap | 1.2 | 不安全 | 高 | 可以 |
| Hashtable | 1.0 | 安全 | 较低 | 不可以 |
Properties
基本介绍
- Properties类继承自Hashtable类并实现接口Map接口,也是实现一种键值对的形式保存数据
- 使用特点和Hashtable相似
- Properties还可以用于从xxx.properties 文件中,加载数据到properties类对象,并进行读取和修改
- 说明:xxx.properties文件通常作为配置文件
基本使用
- put 增
- remove 删
- 这里使用相同的key值,会更改值的特性 改
- get()(括号里面是k值) 或 getProperty()(括号里面是k值) 查
package javaSEStudy.collection_Map.map_.hashtable_.properties_;
import lombok.extern.jackson.Jacksonized;
import java.util.Properties;
// Properties 详解
@SuppressWarnings({"all"})
public class Properties_ {
public static void main(String[] args) {
/**
* put 增加
* 因为底层存储结构与HashMap类似,所以不保证无序
* 但是可通过扩展和包装实现有序
*/
Properties pro = new Properties();
pro.put("hello", "world"); // k-v 存储
pro.put("hello1", 100);
pro.put("hello1", 520); // 当键相同时,也会发生值的替换
pro.put("蔡徐坤", "基尼太美");
// 注意 它和它的超类Hashtable一样,不允许空值,否则会报空指针异常
// pro.put(null, 520);
// pro.put("Jack", null);
System.out.println(pro);
/**
* 查:
* 获取值:通过key,获取对应的值
*/
System.out.println(pro.get("hello"));
System.out.println(pro.getProperty("hello"));
/**
* remove 删除
*/
pro.remove("蔡徐坤");
System.out.println(pro);
/**
* put 修改
*/
pro.put("大碗宽面",66);
pro.put("大碗宽面",123456);
}
}
TreeMap
package javaSEStudy.collection_Map.map_.treeMap_;
import java.util.Comparator;
import java.util.TreeMap;
// TreeMap 详解
@SuppressWarnings({"all"})
public class TreeMap_ {
public static void main(String[] args) {
// 使用默认构造器
// 如果什么也不处理,无序输出
// TreeMap treeMap = new TreeMap();
/**
* 使用提供的构造器对字符串大小进行排序
*/
TreeMap treeMap = new TreeMap(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
// 默认比较大小的方式(字符串首字母)
// return ((String) o2).compareTo((String) o1);
// 根据字符串长度
return ((String) o2).length() - ((String) o1).length();
}
});
treeMap.put("ba", "好难听");
treeMap.put("cdu", "卡布基诺");
treeMap.put("wsajid", "萨瓦迪卡");
treeMap.put("ddio", "汴京");
treeMap.put("a", "发生啥了");
System.out.println(treeMap);
/**
* 1.调用构造器,把穿入了实现Compare接口的匿名内部类(对象),传给了TreeMap的compare
* final int compare(Object k1, Object k2) {
* return comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2)
* : comparator.compare((K)k1, (K)k2);
* }
* 2.调用put方法
* 第一次添加 把k-v对象封装到 Entry 对象中,放入 root
* 第一次添加调用了比较器,但是并没有去接受比较的结果,仅检查是不是空值
* private V put(K key, V value, boolean replaceOld) {
* Entry<K,V> t = root;
* if (t == null) {
* addEntryToEmptyMap(key, value);
* return null;
* }
*
* 后续添加
* Comparator<? super K> cpr = comparator;
* if (cpr != null) {
* do {
* // 遍历所有的key,给当前key找存放位置
* parent = t;
*
* // 动态绑定到匿名内部类Compare中
* cmp = cpr.compare(key, t.key);
*
* // 找应放在哪个位置
* if (cmp < 0)
* t = t.left;
* else if (cmp > 0)
* t = t.right;
* else {
* // 如果遍历过程中发现与key相同的key,直接返回,不添加
* V oldValue = t.value;
* if (replaceOld || oldValue == null) {
* t.value = value;
* }
* return oldValue;
* }
* } while (t != null);
*/
}
}
Collections工具类
Collections工具类介绍
- 1.Collections 是一个操作Set,List和Map等集合的工具类
- 2.Collections 中提供了一系列静态的方法对集合元素进行排序,查询和修改等操作
方法
排序操作: (均为static方法)
-
- reverse(List): 翻转List中元素的排序
-
- shuffle(List): 对List集合元素进行随机排序
-
- sort(List): 根据元素的自然排序对指定的List集合元素进行升序排序
-
- sort(List,Comparator): 根据指定的 Comparator 产生的顺序对List集合元素进行排序
-
- swap(List,int,int): 将指定list集合中的i处元素和j处元素进行交换
替换和查找
-
- Object max(Collection): 根据元素的自然顺序,返回给定集合中的最大元素
-
- Object max(Collection,Comparator): 根据Comparator 指定的顺序,返回给定集合中的最大元素
-
- Object min(Collection)
-
- Object min(Collection,Comparator)
-
- int frequency(Collection,Object): 返回指定集合中元素的出现次数
-
- void copy(List dest,List src): 将src中的内容复制到dest中
-
- bollean replaceAll(List list,Object oldVal,object newVal): 使用新的值替换list中所有的旧值
话不多说,我们直接上代码
package javaSEStudy.collection_Map.collections_;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
// Collections 工具类详解
@SuppressWarnings({"all"})
public class Collections_ {
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("e");
list.add("f");
list.add("Tom");
list.add("Tom");
list.add("Tom");
list.add("Tom");
list.add("Jack");
list.add("Luccy");
System.out.println("原始数据: " + list);
/**
* reverse(list) 翻转元素
*/
Collections.reverse(list);
System.out.println("元素进行翻转: " + list);
/**
* shuffle(list) 将元素位置随机打乱
* 使用场景: 抽奖
*/
Collections.shuffle(list);
System.out.println("元素随机打乱位置: " + list);
/**
* sort(list) 根据元素自然顺序对指定list集合元素进行升序排序
*/
Collections.sort(list);
System.out.println("升序: " + list);
/**
* sort(List,Comparator) 根据指定的Comparator产生顺序对list集合元素进行排序
* 例如:希望对字符串按照长度大小进行排序
*/
Collections.sort(list, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
// 可以加入校验
// 从高到低
return ((String) o1).length() - ((String) o2).length();
}
});
System.out.println("按照字符串的大小进行升序" + list);
/**
* swap(list,int,int) 将指定的list集合中的i处元素和j处元素进行交换位置,i和j的位置得是有效的
* 倘若位置不是有效的,会报数组下标越界异常 IndexOutOfBoundsException
*/
Collections.swap(list, 2, 6);
System.out.println("交换位置: " + list);
System.out.println("------------------------------------------------------------");
// 这里sort返回的是列表,不支持链式调用
Collections.sort(list);
System.out.println("当前数组: " + list);
/**
* Object max(Collection): 根据元素的自然顺序,返回给定集合中的最大元素
*/
System.out.println("自然顺序的最大值: " + Collections.max(list));
/**
* Object max(Collection,Comparator): 根据Comparator 指定的顺序,返回给定集合中的最大元素
* 例如:返回字符串长度最大的
*/
Object max = Collections.max(list, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return ((String) o1).length() - ((String) o2).length();
}
});
System.out.println("字符串长度最大值: " + max);
/**
* Object min(Collection) 返回最小值
*/
System.out.println("自然排序最小值: " + Collections.min(list));
/**
* Object min(Collection,Comparator): 根据Comparator 指定的顺序,返回给定集合中的最小元素
* 例如:返回字符串长度最小的
*/
Object min = Collections.min(list, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return ((String) o1).length() - ((String) o2).length();
}
});
System.out.println("字符串长度最小值: " + min);
/**
* int frequency(Collection,Object): 返回指定集合中元素的出现次数
*/
System.out.println("Tom出现的次数: " + Collections.frequency(list, "Tom"));
/**
* void copy(List dest,List src): 将src中的内容复制到dest中
*/
ArrayList dest = new ArrayList();
// 将后一个的内容拷贝到前一个list中
// 注意,下面这么写会抛出数组下标越界异常,因为dest长度为0,而list长度明显不是0
// Collections.copy(dest, list);
// 因此为了完成拷贝,我们需要先给dest赋值,大小和list.size相同
for (int i = 0; i < list.size(); i++) {
// 增加空间的时候直接将值给添加了
// dest.add(list.get(i));
dest.add("");
}
Collections.copy(dest, list);
System.out.println("dest: " + dest);
/**
* bollean replaceAll(List list,Object oldVal,object newVal): 使用新的值替换list中所有的旧值
*/
// 如果list中有Tom,就替换为中文的汤姆
Collections.replaceAll(list, "Tom", "汤姆");
System.out.println("最终的列表: " + list);
}
}
总结
如何选择集合类?
如何选择集合,主要取决于业务操作特点,根据集合实现类的特性进行选择
- 先判断存储的是一组对象或是一组键值对
- 一组对象(单列):Collection接口
允许重复:List:
改删多:LinkedList(底层维护了一个双向链表)
改查多:ArrayList(底层维护 Object 类型的可变数组,大多数时候用这个)
不允许重复:Set:
无序:HashSet(底层是HashMap,维护了一个哈希表,即(数组+链表+红黑树))
排序:TreeSet
插入和取出顺序一致:LinkedHashSet(底层是LinkedHashMap,维护 数组+双向链表) - 一组键值对(双列):Map
键无序:HashMap(底层是哈希表,数组+链表+红黑树)
键排序:TreeMap
键插入和取出顺序一致:LinkedHashMap(底层是HashMap)
读取文件:Properties
本章作业
第一问

package javaSEStudy.collection_Map.homework;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.ListIterator;
// 包装新闻类...
@SuppressWarnings({"all"})
public class Test01 {
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add(new News("新冠确诊病例超过千万,数百万印度教信徒赴恒河\"圣浴\"引民众担忧"));
list.add(new News("男子突然想起2个月前钓的鱼还在网兜之中,捞起来一看,赶紧放生"));
// 不是倒序遍历
// Iterator iterator = list.iterator();
// while (iterator.hasNext()) {
// News news = (News) iterator.next();
// String title = news.getTitle();
//
// if (title.length() > 15) {
// title = title.substring(0, 15)+"...";
// }
// System.out.println(title);
// }
// 使用ListIterator 倒序遍历
// ListIterator<News> iterator = list.listIterator(list.size());
// while (iterator.hasPrevious()) {
// News news = iterator.previous();
// String title = news.getTitle();
//
// // 如果标题长度 >15,截取前15个字符并加 "..."
// if (title.length() > 15) {
// title = title.substring(0, 15) + "...";
// }
// System.out.println(title);
// }
int size = list.size();
for (int i = size - 1; i >= 0; i--) {
News news = (News)list.get(i);
System.out.println(ProcessTitle(news.getTitle()));
}
}
// 专门写一个方法对标题进行处理 process 处理
public static String ProcessTitle(String title){
if (title == null){
return "";
}
if (title.length() > 15){
title = title.substring(0, 15)+"...";
}
return title;
}
}
class News{
private String title;
private String content;
public News(String title) {
this.title = title;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
@Override
public String toString() {
return "title: " + title;
}
}
第二问

package javaSEStudy.collection_Map.homework;
import java.util.ArrayList;
import java.util.Iterator;
// 对汽车进行处理
@SuppressWarnings({"all"})
public class Test02 {
public static void main(String[] args) {
ArrayList list = new ArrayList();
Car car1 = new Car("宝马",400000);
Car car2 = new Car("宾利", 50000000);
Car car3 = new Car("法拉利", 50000000);
Car car4 = new Car("劳斯莱斯", 50000000);
Car car5 = new Car("布加迪", 50000000);
Car car6 = new Car("五菱宏光", 5000);
Car car7 = new Car("雪铁龙", 10000);
/**
* 1.添加操作
*/
list.add(car1);
list.add(car2);
list.add(car3);
list.add(car4);
list.add(car5);
list.add("小牛");
System.out.println("添加后的列表: "+list);
/**
* 2.删除
*/
list.remove(4);
list.remove(car4);
list.remove("小牛");
System.out.println("删除后的列表: "+list);
/**
* 3.查找元素是否存在
*/
System.out.println("是否存在 "+list.contains(car1));
System.out.println("是否存在 "+list.contains(car5));
/**
* 4.获取元素个数
*/
System.out.println("元素的个数为 "+list.size());
/**
* 5.是否为空
*/
System.out.println("是否为空 "+list.isEmpty());
/**
* 6.添加多个元素
*/
ArrayList list1 = new ArrayList();
list1.add(car6);
list1.add(car7);
list.addAll(list1);
System.out.println(list);
/**
* 7.查找多个元素是否存在
*/
System.out.println("多个元素是否存在 "+list.containsAll(list1));
/**
* 8.删除多个元素
*/
list.removeAll(list1);
System.out.println("将多个元素删除后的列表 "+list);
/**
* 9.清空
*/
list.clear();
System.out.println("清空后的列表 "+list);
System.out.println("------------------------------------------");
list.add(car1);
list.add(car2);
list.add(car3);
list.add(car4);
list.add(car5);
/**
* 使用迭代器
*/
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
Car car = (Car) iterator.next();
System.out.println(car);
}
System.out.println("========");
/**
* 使用增强for
*/
for (Object car : list) {
System.out.println(car);
}
}
}
class Car{
private String name;
private double price;
public Car(String name, double price) {
this.name = name;
this.price = price;
}
@Override
public String toString() {
return "name: " + name +
", price: " + price;
}
}
第三问

package javaSEStudy.collection_Map.homework;
import java.util.*;
// 员工工资问题
@SuppressWarnings({"all"})
public class Test03 {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
map.put("Jack", 650);
map.put("Tom", 1200);
map.put("Smith", 3000);
/**
* 1. 更改员工工资
*/
map.put("Jack", 2600);
System.out.println(map);
/**
* 2.为所有员工工资加薪100
*/
// map.replaceAll((key, value) -> value+100);
// 复杂版
for (String key : map.keySet()) {
// 替换,更新
map.put(key, map.get(key) + 100);
}
/**
* 3.遍历所有的员工
*/
for (Object key : map.keySet()) {
System.out.println(key);
}
System.out.println("-------------------------");
/**
* 4.遍历所有的工资
*/
Set<Map.Entry<String, Integer>> entrySet = map.entrySet();
Iterator<Map.Entry<String, Integer>> iterator = entrySet.iterator();
while (iterator.hasNext()) {
Map.Entry<String, Integer> entry = iterator.next();
System.out.println(entry.getValue());
}
}
}
第四问

1.HashSet去重机制:HashCode + equals(), 底层首先存入一个对象,根据这个对象计算出哈希值,根据哈希值存放到对应table表中的索引位置,如果该索引位置没有值,则直接放入;如果有,进行equals(可重写)遍历比较,如果相同则不添加,不相同则放到末尾.
2.TreeSet去重机制:如果传入了一个Comparator(比较器)匿名对象,就使用实现的compare方法去重,如果方返回值为0,就认为是相同的元素,不添加;如果没有传入一个Comparator匿名对象,就以你添加的对象实现的Comparable接口的compareTo去重.
PS:字符串实现了Comparable接口,这里向上转型为Comparable,再调用compareTo方法去重
第五问

package javaSEStudy.collection_Map.homework;
import java.util.TreeSet;
// TreeSet添加对象
@SuppressWarnings({"all"})
public class Test05 {
public static void main(String[] args) {
TreeSet treeSet = new TreeSet();
treeSet.add(new Person());
/**
* 源码分析
* add方法,因为TreeSet()构造器没有传入Comparator接口的匿名内部类
* 底层: return comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2)
* 即把 Person 转成 Comparable 类型
* 所以报错 ClassCastException 类型转换错误
*/
/**
* 如果想要成功加入,需要让Person实现Comparable接口
*/
}
}
class Person {}
第六问

package javaSEStudy.collection_Map.homework;
import java.util.HashSet;
import java.util.Objects;
// 关于重写HashCode与equals
@SuppressWarnings({"all"})
public class Test06 {
public static void main(String[] args) {
HashSet set = new HashSet();
// 下面两个对象的哈希值由id和name共同计算得到
Person1 p1 = new Person1(1001, "AA");
Person1 p2 = new Person1(1002, "BB");
set.add(p1);
set.add(p2);
p1.name = "CC";
set.remove(p1); // p1的哈希值由1001,CC计算得到新值,此时删除操作由当前新值去查找,对应位置可能不同,无法删除
System.out.println(set); // 还是会输出p1和p2
/**
* 虽然哈希值与修改后的AA相同,但是存储位置可能不同,虽然值被更改,但是存储位置未更新
* 新增的对象被认为是一个新的实例,也能添加
*/
set.add(new Person1(1001,"CC"));
System.out.println(set); // 显示3个对象
/**
* 这里由于哈希值已被更改,这里是new的对象,HashMap只会比较他的哈希值是否相同
* 因为new出来的两个对象是两个不同的内存地址,不会调用equals方法比较
*/
set.add(new Person1(1001,"AA"));
System.out.println(set); // 显示4个对象
// 这道题这里发生了内存泄漏
}
}
class Person1 {
public int id;
public String name;
public Person1(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
Person1 person1 = (Person1) o;
return id == person1.id && Objects.equals(name, person1.name);
}
@Override
public int hashCode() {
return Objects.hash(id, name);
}
@Override
public String toString() {
return "Person1{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
第七问

| 底层结构 | 版本 | 线程安全(同步) 效率 | 扩容倍数 | |
|---|---|---|---|---|
| ArrayList | 可变数组 | JDK1.2 | 不安全,效率高 | 如果是有参构造,则是按照给的大小扩容1.5倍;如果是无参构造,第一次为10,第二次为1.5倍 |
| Vector | 可变数组 | JDK1.0 | 安全,效率不高 | 如果是无参构造,默认为10,满后按照两倍扩容;如果指定大小,则每次直接扩大两倍 |







浙公网安备 33010602011771号