S01-16 JavaSE-集合
[TOC]
集合的理解和好处
数组的不足
- 长度开始时必须指定,而且一旦指定,不能更改
- 保存的必须为同一类型的元素
- 增加/删除元素操作麻烦
数组扩容示意代码:
Person[] pers = new Person[1];// 大小是1
pers[0] = new Person();
// 增加新的Person对象
Person[] pers2 = new Person[pers.length + 1];// 新创建数组
// 拷贝pers数组的元素到pers2
for (int i = 0; i < pers.length; i++) {
pers2[i] = pers[i];
}
pers2[pers2.length - 1] = new Person();// 添加新的对象集合的好处
- 可以动态保存任意多个对象,使用比较方便
- 提供了一系列方便的操作对象的方法:add、remove、set、get等
- 增加/删除元素简洁
集合的框架体系
Java 的集合类主要分为两大类:
- 单列集合(Collection):
- List(有序、可重复):ArrayList、LinkedList、Vector
- Set(无序、不可重复):HashSet、TreeSet
- 双列集合(Map):HashMap、TreeMap、Hashtable、LinkedHashMap、Properties
集合框架图
Iterable
└── Collection
├── List
│ ├── ArrayList
│ ├── LinkedList
│ └── Vector
└── Set
├── HashSet
└── TreeSet
└── Map
├── HashMap
├── TreeMap
├── Hashtable
├── LinkedHashMap
└── PropertiesCollection_.java
package com.hspedu.collection_;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
/**
* @author 韩顺平
* @version 1.0
*/
public class Collection_ {
@SuppressWarnings({"all"})
public static void main(String[] args) {
// 单列集合
Collection arrayList = new ArrayList();
arrayList.add("jack");
arrayList.add("tom");
// 双列集合
Map hashMap = new HashMap();
hashMap.put("NO1", "北京");
hashMap.put("NO2", "上海");
}
}第601页
Collection 接口和常用方法
Collection 接口实现类的特点
- Collection实现子类可以存放多个元素,每个元素可以是Object
- 有些Collection的实现类,可以存放重复的元素(List),有些不可以(Set)
- 有些Collection的实现类,是有序的(List),有些不是有序(Set)
- Collection接口没有直接的实现子类,是通过它的子接口Set和List来实现的
Collection 接口常用方法
| 方法 | 说明 |
|---|---|
| add(Object ele) | 添加单个元素 |
| addAll(Collection eles) | 添加多个元素 |
| remove(Object ele) | 删除指定元素 |
| removeAll(Collection eles) | 删除多个元素 |
| contains(Object ele) | 查找元素是否存在 |
| containsAll(Collection eles) | 查找多个元素是否都存在 |
| size() | 获取元素个数 |
| isEmpty() | 判断是否为空 |
| clear() | 清空集合 |
CollectionMethod.java
package com.hspedu.collection_;
import java.util.ArrayList;
import java.util.List;
/**
* @author 韩顺平
* @version 1.0
*/
public class CollectionMethod {
@SuppressWarnings({"all"})
public static void main(String[] args) {
List list = new ArrayList();
// 1. add:添加单个元素
list.add("jack");
list.add(10);// 自动装箱
list.add(true);
list.add("jack");// 可重复
System.out.println("list=" + list);// [jack, 10, true, jack]
// 2. remove:删除指定元素
list.remove(true);// 删除元素true
// list.remove(0);// 删除索引0的元素
System.out.println("list=" + list);// [jack, 10, jack]
// 3. contains:查找元素是否存在
System.out.println(list.contains("jack"));// true
// 4. size:获取元素个数
System.out.println(list.size());// 3
// 5. isEmpty:判断是否为空
System.out.println(list.isEmpty());// false
// 6. addAll:添加多个元素
ArrayList list2 = new ArrayList();
list2.add("红楼梦");
list2.add("三国演义");
list.addAll(list2);
System.out.println("list=" + list);// [jack, 10, jack, 红楼梦, 三国演义]
// 7. containsAll:查找多个元素是否都存在
System.out.println(list.containsAll(list2));// true
// 8. removeAll:删除多个元素
list.removeAll(list2);
System.out.println("list=" + list);// [jack, 10, jack]
// 9. clear:清空
list.clear();
System.out.println("list=" + list);// []
}
}第603页
Collection 接口遍历元素方式1-使用Iterator(迭代器)
基本介绍
- Iterator对象称为迭代器,主要用于遍历Collection集合中的元素。
- 所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象。
- Iterator仅用于遍历集合,Iterator本身并不存放对象。
迭代器的执行原理
Iterator iterator = coll.iterator();// 得到集合的迭代器
while(iterator.hasNext()){// 判断是否还有下一个元素
// next()作用:1.下移 2.将下移以后集合位置上的元素返回
Object obj = iterator.next();
System.out.println(obj);
}Iterator接口的方法
| 方法 | 说明 |
|---|---|
| hasNext() | 判断是否还有下一个元素,返回boolean |
| next() | 返回迭代的下一个元素 |
| remove() | 移除迭代器返回的最后一个元素(可选操作) |
老韩提示:在调用iterator.next()方法之前必须要调用iterator.hasNext()进行检测。若不调用,且下一条记录无效,直接调用it.next()会抛出NoSuchElementException异常。
迭代器使用案例 CollectionIterator.java
package com.hspedu.collection_;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
/**
* @author 韩顺平
* @version 1.0
*/
public class CollectionIterator {
@SuppressWarnings({"all"})
public static void main(String[] args) {
Collection col = new ArrayList();
col.add(new Book("红楼梦", "曹雪芹", 100.0));
col.add(new Book("三国演义", "罗贯中", 10.1));
col.add(new Book("小李飞刀", "古龙", 5.1));
// 1. 得到迭代器
Iterator iterator = col.iterator();
// 2. 第一次遍历
System.out.println("===第一次遍历===");
while (iterator.hasNext()) {
Object obj = iterator.next();
System.out.println("obj=" + obj);
}
// 3. 再次遍历需要重置迭代器
iterator = col.iterator();
System.out.println("===第二次遍历===");
while (iterator.hasNext()) {
Object obj = iterator.next();
System.out.println("obj=" + 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;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return "Book{" +
"name='" + name + '\'' +
", author='" + author + '\'' +
", price=" + price +
'}';
}
}第609页
Collection 接口遍历对象方式2-for 循环增强
基本语法
for(元素类型 元素名: 集合名或数组名){
访问元素
}特点
增强for就是简化版的iterator,本质一样。只能用于遍历集合或数组。
案例演示
// 增强for遍历Collection
for (Object object : col) {
System.out.println(object);
}课堂练习 CollectionExercise.java
题目:
- 创建3个Dog {name, age}对象,放入到ArrayList中,赋给List引用
- 用迭代器和增强for循环两种方式来遍历
- 重写Dog 的toString方法,输出name和age
代码实现
package com.hspedu.collection_;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* @author 韩顺平
* @version 1.0
*/
public class CollectionExercise {
@SuppressWarnings({"all"})
public static void main(String[] args) {
List list = new ArrayList();
list.add(new Dog("小黑", 3));
list.add(new Dog("大壮", 100));
list.add(new Dog("大黄", 8));
// 1. 增强for遍历
System.out.println("===增强for遍历===");
for (Object dog : list) {
System.out.println("dog=" + dog);
}
// 2. 迭代器遍历
System.out.println("===迭代器遍历===");
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
Object dog = iterator.next();
System.out.println("dog=" + dog);
}
}
}
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 +
'}';
}
}第612页
List 接口和常用方法
List 接口基本介绍
- List集合类中元素有序(即添加顺序和取出顺序一致)、且可重复。
- List集合中的每个元素都有其对应的顺序索引,即支持索引。
- List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素。
- JDK API中List接口的实现类有:ArrayList、LinkedList和Vector。
案例 List_.java
package com.hspedu.list_;
import java.util.ArrayList;
import java.util.List;
/**
* @author 韩顺平
* @version 1.0
*/
public class List_ {
@SuppressWarnings({"all"})
public static void main(String[] args) {
// 1. 元素有序、可重复
List list = new ArrayList();
list.add("jack");
list.add("tom");
list.add("mary");
list.add("hsp");
list.add("tom");// 重复元素
System.out.println("list=" + list);// [jack, tom, mary, hsp, tom]
// 2. 支持索引
System.out.println(list.get(3));// hsp
}
}第614页
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在集合中首次出现的位置 |
| int lastIndexOf(Object obj) | 返回obj在当前集合中末次出现的位置 |
| Object remove(int index) | 移除指定index位置的元素,并返回此元素 |
| Object set(int index, Object ele) | 设置指定index位置的元素为ele,相当于是替换 |
| List subList(int fromIndex, int toIndex) | 返回从fromIndex到toIndex位置的子集合(左闭右开) |
ListMethod.java
package com.hspedu.list_;
import java.util.ArrayList;
import java.util.List;
/**
* @author 韩顺平
* @version 1.0
*/
public class ListMethod {
@SuppressWarnings({"all"})
public static void main(String[] args) {
List list = new ArrayList();
list.add("张三丰");
list.add("贾宝玉");
System.out.println("list=" + list);// [张三丰, 贾宝玉]
// 1. add(int index, Object ele)
list.add(1, "韩顺平");
System.out.println("list=" + list);// [张三丰, 韩顺平, 贾宝玉]
// 2. addAll(int index, Collection eles)
List list2 = new ArrayList();
list2.add("jack");
list2.add("tom");
list.addAll(1, list2);
System.out.println("list=" + list);// [张三丰, jack, tom, 韩顺平, 贾宝玉]
// 3. indexOf(Object obj)
System.out.println(list.indexOf("tom"));// 2
// 4. lastIndexOf(Object obj)
list.add("韩顺平");
System.out.println("list=" + list);// [张三丰, jack, tom, 韩顺平, 贾宝玉, 韩顺平]
System.out.println(list.lastIndexOf("韩顺平"));//Set 接口
Set 接口的特点
- 无序(添加和取出的顺序不一致),没有索引
- 不允许重复元素,所以最多包含一个 null
- JDK API 中 Set 接口的实现类有:
HashSet、TreeSet - 所有超级接口:
Collection<E>、Iterable<E> - 所有已知子接口:
NavigableSet<E>、SortedSet<E>
Set 接口的常用方法
和 List 接口一样,Set 接口也是 Collection 的子接口,因此常用方法和 Collection 接口一致。
Set 接口的遍历方式
同 Collection 的遍历方式(Set 是 Collection 子接口):
- 迭代器
- 增强 for
- 不能使用索引方式获取元素
Set 接口的常用方法举例(SetMethod.java)
package com.hspedu.set_;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
@SuppressWarnings({"all"})
public class SetMethod {
// 老韩解读
// 1. 以 Set 接口的实现类 HashSet 来讲解 Set 接口的方法
// 2. set 接口的实现类的对象(Set 接口对象), 不能存放重复的元素, 可以添加一个 null
// 3. set 接口对象存放数据是无序(即添加的顺序和取出的顺序不一致)
public static void main(String[] args) {
Set set = new HashSet();
// 添加元素
set.add("john");
set.add("lucy");
set.add("john");// 重复元素,添加失败
set.add("jack");
set.add("hsp");
set.add("mary");
set.add(null);
set.add(null);// 再次添加 null,失败
// 多次打印,验证取出顺序固定(虽与添加顺序不同)
for (int i = 0; i < 10; i++) {
System.out.println("set=" + set);
}
// 遍历方式1:迭代器
System.out.println("=====使用迭代器====");
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
Object obj = iterator.next();
System.out.println("obj=" + obj);
}
// 删除 null
set.remove(null);
// 遍历方式2:增强 for
System.out.println("=====增强 for====");
for (Object o : set) {
System.out.println("o=" + o);
}
// 注意:set 接口对象不能通过索引获取元素
}
}Set 接口实现类 - HashSet
HashSet 的全面说明
- HashSet 实现了 Set 接口
- HashSet 实际上是 HashMap(源码:
public HashSet() { map = new HashMap<>(); }) - 可以存放 null 值,但只能有一个 null
- 不保证存放元素的顺序和取出顺序一致
- 不能有重复元素/对象
HashSet 案例说明(HashSet01.java)
package com.hspedu.set_;
import java.util.HashSet;
import java.util.Set;
@SuppressWarnings({"all"})
public class HashSet01 {
public static void main(String[] args) {
HashSet set = new HashSet();
// add 方法返回 boolean:添加成功返回 true,失败返回 false
System.out.println(set.add("john"));// T
System.out.println(set.add("lucy"));// T
System.out.println(set.add("john"));// F(重复)
System.out.println(set.add("jack"));// T
System.out.println(set.add("Rose"));// T
System.out.println("set=" + set);
// 删除元素
set.remove("john");
System.out.println("set=" + set);
// 重置 set
set = new HashSet();
System.out.println("set=" + set);
set.add("lucy");
set.add("lucy");// 重复,添加失败
// 添加自定义对象(未重写 hashCode 和 equals)
set.add(new Dog("tom"));// OK
set.add(new Dog("tom"));// OK(视为不同对象)
System.out.println("set=" + set);
// 经典面试题:String 类型重复添加失败
set.add(new String("hsp"));// OK
set.add(new String("hsp"));// 失败(String 重写了 hashCode 和 equals)
System.out.println("set=" + set);
}
}
// 自定义 Dog 类
class Dog {
private String name;
public Dog(String name) {
this.name = name;
}
@Override
public String toString() {
return "Dog{" + "name='" + name + '\'' + '}';
}
}HashSet 底层机制说明
核心原理
HashSet 底层是 HashMap,HashMap 底层是 数组 + 链表 + 红黑树(Java 8)。添加元素的核心流程:
- 先获取元素的哈希值(
hashCode()方法) - 对哈希值进行运算,得出索引值(确定存储位置)
- 若该位置无元素,直接存放
- 若该位置有元素,进行
equals判断:- 相等:不添加
- 不相等:以链表形式添加到末尾
- Java 8 中:若链表长度 ≥ 8 且数组大小 ≥ 64,链表转为红黑树(树化)
源码解读(HashSetSource.java)
package com.hspedu.set_;
import java.util.HashSet;
@SuppressWarnings({"all"})
public class HashSetSource {
public static void main(String[] args) {
HashSet hashSet = new HashSet();
hashSet.add("java");// 第 1 次 add
hashSet.add("php");// 第 2 次 add
hashSet.add("java");// 重复添加
System.out.println("set=" + hashSet);
}
}
// 老韩对 HashSet 源码解读
/*
1. 执行 HashSet() 构造器:初始化 HashMap
public HashSet() {
map = new HashMap<>();
}
2. 执行 add(E e):调用 HashMap 的 put 方法
public boolean add(E e) {
return map.put(e, PRESENT) == null; // PRESENT 是静态常量 Object 对象
}
3. 执行 HashMap 的 put(K key, V value):计算 key 的 hash 值
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
4. 执行 hash(key):哈希值运算(h = key.hashCode() ^ (h >>> 16))
5. 执行 putVal():核心逻辑(扩容、节点添加、树化等)
*/HashSet 扩容和树化机制
- 第一次添加元素时,数组扩容到 16,临界值(threshold)= 16 × 加载因子(0.75)= 12
- 当数组使用到临界值 12 时,扩容到 32,新临界值 = 32 × 0.75 = 24,依次类推
- 树化条件:
- 链表长度 ≥ TREEIFY_THRESHOLD(默认 8)
- 数组大小 ≥ MIN_TREEIFY_CAPACITY(默认 64)
- 不满足则先扩容,不树化
HashSet 课堂练习1(HashSetExercise.java)
需求:定义 Employee 类(name, age),当 name 和 age 相同时视为同一员工,不能重复添加到 HashSet。
package com.hspedu.set_;
import java.util.HashSet;
import java.util.Objects;
@SuppressWarnings({"all"})
public class HashSetExercise {
public static void main(String[] args) {
HashSet hashSet = new HashSet();
hashSet.add(new Employee("milan", 18));// OK
hashSet.add(new Employee("smith", 28));// OK
hashSet.add(new Employee("milan", 18));// 失败(重复)
System.out.println("hashSet=" + hashSet);
}
}
class Employee {
private String name;
private int age;
public Employee(String name, int age) {
this.name = name;
this.age = age;
}
// 重写 equals 和 hashCode,基于 name 和 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);
}
@Override
public String toString() {
return "Employee{" + "name='" + name + '\'' + ", age=" + age + '}';
}
// getter/setter
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; }
}HashSet 课后练习2
需求:定义 Employee 类(name, sal, birthday),birthday 为 MyDate 类(year, month, day)。当 name 和 birthday 相同时视为同一员工,不能重复添加到 HashSet。
Set 接口实现类 - LinkedHashSet
LinkedHashSet 的全面说明
- LinkedHashSet 是 HashSet 的子类
- 底层是 LinkedHashMap,维护 数组 + 双向链表
- 根据元素的 hashCode 确定存储位置,同时用链表维护插入顺序(遍历顺序与插入顺序一致)
- 不允许重复元素
LinkedHashSet 底层机制示意图
LinkedHashSet 底层机制示意图
- 维护哈希表(数组)和双向链表
- 每个节点有 before 和 after 属性,形成双向链表
- 添加元素时:先通过 hash 确定数组位置,再加入双向链表(重复元素不添加)
LinkedHashSet 课后练习题(LinkedHashSetExercise.java)
需求:Car 类(name, price),name 和 price 相同时视为相同元素,不能添加到 LinkedHashSet。
package com.hspedu.set_;
import java.util.LinkedHashSet;
import java.util.Objects;
@SuppressWarnings({"all"})
public class LinkedHashSetExercise {
public static void main(String[] args) {
LinkedHashSet linkedHashSet = new LinkedHashSet();
linkedHashSet.add(new Car("奥拓", 1000));// OK
linkedHashSet.add(new Car("奥迪", 300000));// OK
linkedHashSet.add(new Car("法拉利", 10000000));// OK
linkedHashSet.add(new Car("奥迪", 300000));// 失败(重复)
linkedHashSet.add(new Car("保时捷", 70000000));// OK
linkedHashSet.add(new Car("奥迪", 300000));// 失败(重复)
System.out.println("linkedHashSet=" + linkedHashSet);
}
}
class Car {
private String name;
private double price;
public Car(String name, double price) {
this.name = name;
this.price = price;
}
// 重写 equals 和 hashCode
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Car car = (Car) o;
return Double.compare(car.price, price) == 0 && Objects.equals(name, car.name);
}
@Override
public int hashCode() {
return Objects.hash(name, price);
}
@Override
public String toString() {
return "\nCar{" + "name='" + name + '\'' + ", price=" + price + '}';
}
// getter/setter
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public double getPrice() { return price; }
public void setPrice(double price) { this.price = price; }
}Map 接口和常用方法
Map 接口实现类的特点
- Map 与 Collection 并列存在,用于存储 Key-Value 映射关系数据
- Key 和 Value 可以是任意引用类型,封装到 HashMap$Node 对象中
- Key 不允许重复(原理同 HashSet)
- Value 可以重复
- Key 可以为 null(最多一个),Value 可以为 null(多个)
- 常用 String 作为 Key
- Key 和 Value 是单向一对一关系(通过 Key 总能找到唯一 Value)
Map 接口常用方法(MapMethod.java)
package com.hspedu.map_;
import java.util.HashMap;
import java.util.Map;
@SuppressWarnings({"all"})
public class MapMethod {
public static void main(String[] args) {
Map map = new HashMap();
// 1. put:添加/替换元素(Key 存在则替换 Value)
map.put("no1", "韩顺平");// K-V
map.put("no2", "张无忌");// K-V
map.put("no1", "张三丰");// 替换 Value
map.put("no3", "张三丰");// Value 重复
map.put(null, null);// K-V
map.put(null, "abc");// 替换 null Key 的 Value
map.put("no4", null);// Value 为 null
map.put("no5", null);// Value 为 null
map.put(1, "赵敏");// Key 为 Integer
map.put(new Object(), "金毛狮王");// Key 为 Object
System.out.println("map=" + map);
// 2. remove:根据 Key 删除映射关系
map.remove(null);
System.out.println("map=" + map);
// 3. get:根据 Key 获取 Value
Object val = map.get("no2");
System.out.println("val=" + val);// 张无忌
// 4. size:获取元素个数
System.out.println("k-v 个数=" + map.size());
// 5. isEmpty:判断是否为空
System.out.println(map.isEmpty());// false
// 6. containsKey:判断 Key 是否存在
System.out.println("是否包含 Key 'hsp'=" + map.containsKey("hsp"));// false
// 7. clear:清空所有 K-V(注释避免影响后续操作)
// map.clear();
}
}
class Book {
private String name;
private int num;
public Book(String name, int num) {
this.name = name;
this.num = num;
}
}Map 接口遍历方法(MapFor.java)
Map 遍历有 3 种核心方式:
- 遍历所有 Key,通过 Key 获取 Value
- 直接遍历所有 Value
- 遍历 Key-Value 对(EntrySet)
package com.hspedu.map_;
import java.util.*;
@SuppressWarnings({"all"})
public class MapFor {
public static void main(String[] args) {
Map map = new HashMap();
map.put("邓超", "孙俪");
map.put("王宝强", "马蓉");
map.put("宋喆", "马蓉");
map.put("刘令博", null);
map.put(null, "刘亦菲");
map.put("鹿晗", "关晓彤");
// 第一组:遍历所有 Key -> 获取 Value
Set keySet = map.keySet();
// 方式1:增强 for
System.out.println("-----第一种方式(KeySet 增强 for)-------");
for (Object key : keySet) {
System.out.println(key + "-" + map.get(key));
}
// 方式2:迭代器
System.out.println("-----第二种方式(KeySet 迭代器)-------");
Iterator iterator = keySet.iterator();
while (iterator.hasNext()) {
Object key = iterator.next();
System.out.println(key + "-" + map.get(key));
}
// 第二组:直接遍历所有 Value
Collection values = map.values();
// 方式1:增强 for
System.out.println("-----第三种方式(Values 增强 for)-------");
for (Object value : values) {
System.out.println(value);
}
// 方式2:迭代器
System.out.println("-----第四种方式(Values 迭代器)-------");
Iterator iterator2 = values.iterator();
while (iterator2.hasNext()) {
Object value = iterator2.next();
System.out.println(value);
}
// 第三组:遍历 EntrySet(Key-Value 对)
Set entrySet = map.entrySet();
// 方式1:增强 for
System.out.println("-----第五种方式(EntrySet 增强 for)-------");
for (Object entry : entrySet) {
Map.Entry m = (Map.Entry) entry;
System.out.println(m.getKey() + "-" + m.getValue());
}
// 方式2:迭代器
System.out.println("-----第六种方式(EntrySet 迭代器)-------");
Iterator iterator3 = entrySet.iterator();
while (iterator3.hasNext()) {
Object entry = iterator3.next();
Map.Entry m = (Map.Entry) entry;
System.out.println(m.getKey() + "-" + m.getValue());
}
}
}Map 接口课堂练习(MapExercise.java)
需求:使用 HashMap 存储员工(Key:员工 id,Value:员工对象),遍历并显示工资 > 18000 的员工(至少两种遍历方式)。
package com.hspedu.map_;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
@SuppressWarnings({"all"})
public class MapExercise {
public static void main(String[] args) {
Map hashMap = new HashMap();
// 添加员工对象
hashMap.put(1, new Emp("jack", 30000, 1));
hashMap.put(2, new Emp("tom", 21000, 2));
hashMap.put(3, new Emp("milan", 12000, 3));
// 方式1:KeySet + 增强 for
System.out.println("====第一种遍历方式(KeySet 增强 for)====");
Set keySet = hashMap.keySet();
for (Object key : keySet) {
Emp emp = (Emp) hashMap.get(key);
if (emp.getSal() > 18000) {
System.out.println(emp);
}
}
// 方式2:EntrySet + 迭代器
System.out.println("====第二种遍历方式(EntrySet 迭代器)====");
Set entrySet = hashMap.entrySet();
Iterator iterator = entrySet.iterator();
while (iterator.hasNext()) {
Map.Entry entry = (Map.Entry) iterator.next();
Emp emp = (Emp) entry.getValue();
if (emp.getSal() > 18000) {
System.out.println(emp);
}
}
}
}
// 员工类
class Emp {
private String name;
private double sal;
private int id;
public Emp(String name, double sal, int id) {
this.name = name;
this.sal = sal;
this.id = id;
}
// getter/setter
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public double getSal() { return sal; }
public void setSal(double sal) { this.sal = sal; }
public int getId() { return id; }
public void setId(int id) { this.id = id; }
@Override
public String toString() {
return "Emp{" + "name='" + name + '\'' + ", sal=" + sal + ", id=" + id + '}';
}
}Map 接口实现类 - HashMap
HashMap 小结
- Map 接口常用实现类:HashMap、Hashtable、Properties
- HashMap 是 Map 接口使用频率最高的实现类
- 以 Key-Value 对存储(HashMap$Node 类型),Node 实现 Map.Entry 接口
- Key 不能重复,Value 可以重复;允许 null Key(1 个)和 null Value(多个)
- 相同 Key 添加时,Value 会覆盖(Key 不变)
- 不保证映射顺序(底层哈希表),Java 8 底层是 数组 + 链表 + 红黑树
- 线程不安全(无 synchronized 修饰),效率高
HashMap 底层机制及源码剖析
底层结构
- JDK 7:数组 + 链表
- JDK 8:数组 + 链表 + 红黑树(链表长度 ≥ 8 且数组大小 ≥ 64 时树化)
扩容机制
- 底层维护 Node 数组
table,默认初始化为 null - 加载因子(loadFactor)默认 0.75
- 第一次添加元素时,数组扩容到 16,临界值 = 16 × 0.75 = 12
- 后续扩容:数组大小翻倍(16 → 32 → 64...),临界值也翻倍
- 当数组使用到临界值时,触发扩容
源码核心流程(HashMapSource1.java)
package com.hspedu.map_;
import java.util.HashMap;
@SuppressWarnings({"all"})
public class HashMapSource1 {
public static void main(String[] args) {
HashMap map = new HashMap();
map.put("java", 10);// OK
map.put("php", 10);// OK
map.put("java", 20);// 替换 Value
System.out.println("map=" + map);
}
}
// 源码解读核心步骤
/*
1. 执行 new HashMap():初始化加载因子 loadFactor = 0.75,table = null
2. 执行 put(K key, V value):调用 hash(key) 计算哈希值
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
3. 执行 hash(key):h = key.hashCode() ^ (h >>> 16)(哈希值优化)
4. 执行 putVal():
a. 若 table 为 null 或长度为 0,扩容到 16
b. 根据 hash 值计算索引 i = (n - 1) & hash
c. 若 table[i] 为 null,直接创建 Node 放入
d. 若 table[i] 不为 null:
i. 若 Key 相同,替换 Value
ii. 若为红黑树节点,调用 putTreeVal 添加
iii. 若为链表,遍历比较,无相同 Key 则添加到链表末尾;链表长度 ≥8 则树化
e. size 超过临界值,扩容
*/Map 接口实现类 - Hashtable
Hashtable 的基本介绍
- 存储 Key-Value 对,底层结构同 HashMap(JDK 8 后也支持红黑树)
- Key 和 Value 都不能为 null(否则抛 NullPointerException)
- 方法使用与 HashMap 基本一致
- 线程安全(方法加了 synchronized 修饰),效率低
- 版本:JDK 1.0 推出
Hashtable 和 HashMap 对比
| 特性 | HashMap | Hashtable |
|---|---|---|
| 版本 | JDK 1.2 | JDK 1.0 |
| 线程安全(同步) | 不安全 | 安全 |
| 效率 | 高 | 低 |
| 允许 null Key/Value | 允许(Key 1个,Value 多个) | 不允许 |
Hashtable 应用案例
package com.hspedu.map_;
import java.util.Hashtable;
public class HashtableExercise {
public static void main(String[] args) {
Hashtable table = new Hashtable();
table.put("john", 100);// OK
// table.put(null, 100);// 异常(null Key)
// table.put("john", null);// 异常(null Value)
table.put("lucy", 100);// OK
table.put("lic", 100);// OK
table.put("lic", 88);// 替换 Value
System.out.println(table);
}
}Map 接口实现类 - Properties
基本介绍
- 继承自 Hashtable,实现 Map 接口
- 存储 Key-Value 对,Key 和 Value 都不能为 null
- 常用于读取配置文件(.properties)
- 方法使用与 Hashtable 类似
基本使用(Properties_.java)
package com.hspedu.map_;
import java.util.Properties;
@SuppressWarnings({"all"})
public class Properties_ {
public static void main(String[] args) {
Properties properties = new Properties();
// 添加 K-V
properties.put("john", 100);
properties.put("lucy", 100);
properties.put("lic", 100);
properties.put("lic", 88);// 替换 Value
System.out.println("properties=" + properties);
// 根据 Key 获取 Value
System.out.println("lic 的值=" + properties.get("lic"));// 88
// 删除 K-V
properties.remove("lic");
System.out.println("properties=" + properties);
// 修改 Value
properties.put("john", "约翰");
System.out.println("properties=" + properties);
}
}开发中如何选择集合实现类
核心选择逻辑
- 先判断存储类型:
- 单列数据(一组对象):Collection 接口
- 允许重复:List
- 增删多:LinkedList(双向链表)
- 改查多:ArrayList(可变数组)
- 不允许重复:Set
- 无序:HashSet(哈希表)
- 有序(插入顺序):LinkedHashSet(数组 + 双向链表)
- 排序:TreeSet(红黑树)
- 允许重复:List
- 双列数据(Key-Value):Map
- 无序:HashMap(哈希表)
- 有序(插入顺序):LinkedHashMap(数组 + 双向链表)
- 排序:TreeMap(红黑树)
- 配置文件:Properties(继承 Hashtable)
- 单列数据(一组对象):Collection 接口
Collections 工具类
Collections 工具类介绍
- 操作 Set、List、Map 等集合的工具类
- 提供静态方法:排序、查询、修改等
常用方法(Collections_.java)
package com.hspedu.collections_;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
@SuppressWarnings({"all"})
public class Collections_ {
public static void main(String[] args) {
List list = new ArrayList();
list.add("tom");
list.add("smith");
list.add("king");
list.add("milan");
list.add("tom");
// 1. reverse(List):反转列表
Collections.reverse(list);
System.out.println("反转后:" + list);
// 2. shuffle(List):随机排序(每次结果不同)
System.out.println("随机排序:");
for (int i = 0; i < 3; i++) {
Collections.shuffle(list);
System.out.println(list);
}
// 3. sort(List):自然排序(默认升序)
Collections.sort(list);
System.out.println("自然排序后:" + list);
// 4. sort(List, Comparator):定制排序(按字符串长度降序)
Collections.sort(list, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return ((String) o2).length() - ((String) o1).length();
}
});
System.out.println("按字符串长度降序:" + list);
// 5. swap(List, int, int):交换指定索引元素
Collections.swap(list, 0, 1);
System.out.println("交换索引 0 和 1 后:" + list);
// 6. max(Collection):自然顺序最大值
System.out.println("自然顺序最大值:" + Collections.max(list));
// 7. max(Collection, Comparator):定制排序最大值(长度最大)
Object maxLenObj = Collections.max(list, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return ((String) o1).length() - ((String) o2).length();
}
});
System.out.println("长度最大的元素:" + maxLenObj);
// 8. frequency(Collection, Object):元素出现次数
System.out.println("tom 出现次数:" + Collections.frequency(list, "tom"));
// 9. copy(List dest, List src):复制 src 到 dest(dest 需提前初始化大小)
List dest = new ArrayList();
for (int i = 0; i < list.size(); i++) {
dest.add("");// 初始化 dest 大小
}
Collections.copy(dest, list);
System.out.println("复制后 dest:" + dest);
// 10. replaceAll(List, Object oldVal, Object newVal):替换所有旧值
Collections.replaceAll(list, "tom", "汤姆");
System.out.println("替换后 list:" + list);
}
}本章作业
编程题 Homework01.java
需求:
- 封装新闻类(标题、内容),重写 toString 只打印标题
- 带参构造器初始化标题,实例化 2 个新闻对象
- 添加到 ArrayList,倒序遍历
- 标题超过 15 字时,保留前 15 字 + "..."
- 打印处理后的标题
编程题 Homework02.java
需求:使用 ArrayList 操作 Car 类(name, price),实现:
- add、remove、contains、size、isEmpty、clear、addAll、containsAll、removeAll
- 增强 for 和迭代器遍历(重写 toString)
编程题 Homework03.java
需求:
- 使用 HashMap 存储员工(姓名-工资):jack-650,tom-1200,smith-2900
- 修改 jack 工资为 2600
- 所有员工加薪 100 元
- 遍历所有员工
- 遍历所有工资
简答题 Homework04.java
问题:HashSet 和 TreeSet 分别如何实现去重?
- HashSet:
hashCode() + equals()。先通过 hash 值确定索引,索引位置无元素则添加;有元素则通过 equals 比较,相等则不添加。 - TreeSet:
- 传入 Comparator 匿名对象:通过
compare()方法判断,返回 0 则视为重复。 - 未传入 Comparator:通过元素实现的
Comparable接口的compareTo()方法判断。
- 传入 Comparator 匿名对象:通过
代码分析题 Homework05.java
代码:
TreeSet treeSet = new TreeSet();
treeSet.add(new Person());问题:是否抛出异常?说明原因。
- 答:会抛出
ClassCastException。TreeSet 底层需要排序,Person 类未实现Comparable接口,也未传入 Comparator,无法比较元素大小,导致异常。
代码输出题 Homework06.java
已知:Person 类按 id 和 name 重写了 hashCode 和 equals 方法。 代码:
HashSet set = new HashSet();
Person p1 = new Person(1001, "AA");
Person p2 = new Person(1002, "BB");
set.add(p1);
set.add(p2);
p1.name = "CC";// 修改 p1 的 name
set.remove(p1);// 能否删除?
System.out.println(set);
set.add(new Person(1001, "CC"));// 能否添加?
System.out.println(set);
set.add(new Person(1001, "AA"));// 能否添加?
System.out.println(set);输出:
[Person{id=1001, name='CC'}, Person{id=1002, name='BB'}]
[Person{id=1001, name='CC'}, Person{id=1002, name='BB'}, Person{id=1001, name='CC'}]
[Person{id=1001, name='CC'}, Person{id=1002, name='BB'}, Person{id=1001, name='CC'}, Person{id=1001, name='AA'}]原因:p1 添加时的 hash 值基于原 name(AA),修改 name 后 hash 值改变,remove 时无法找到原索引,删除失败;后续添加的 Person(1001, "CC") 与修改后的 p1 hash 值相同,但 equals 比较时因 id 和 name 都相同,会视为重复吗?需注意:修改 p1 的 name 后,p1 的 hashCode 改变,但集合中存储的 p1 节点的 hash 值未更新,因此新添加的 Person(1001, "CC") 会计算新的 hash 值,可能与原 p1 所在索引不同,导致添加成功。
Vector 和 ArrayList 的比较 Homework07.java
| 特性 | ArrayList | Vector |
|---|---|---|
| 底层结构 | 可变数组 | 可变数组 |
| 版本 | JDK 1.2 | JDK 1.0 |
| 线程安全(同步) | 不安全,效率高 | 安全(方法加 synchronized),效率低 |
| 扩容倍数 | 无参构造:10 → 1.5 倍扩容;有参构造:1.5 倍扩容 | 无参构造:10 → 2 倍扩容;有参构造:2 倍扩容 |