若文章内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系博主删除。
写这篇博客旨在制作笔记,方便个人在线阅览,巩固知识。无他用。
学习视频
- 【黑马程序员 | Java 基础教程 | 零基础快速掌握 Java 集合框架】
- 【黑马程序员 | Java 零基础视频教程 | 上部】
- 【黑马程序员 | Java 零基础视频教程 | 下部】
学习资料: 【黑马程序员】Java 入门到起飞(提取码:9987)
推荐阅读博客:【漫画:什么是红黑树?】
吐槽:基础不牢,地动山摇
ArrayList 集合的特点:长度可以变化,只能存储引用数据类型。
下面介绍一下 ArrayList 类的常用方法
方法名 | 说明 |
---|---|
public ArrayList() |
创建一个空的集合对象 |
方法名 | 说明 |
---|---|
public boolean add(E e) |
将指定的元素追加到此集合的末尾 |
public boolean remove(E e) |
删除指定元素,返回值表示是否删除成功 |
public E remove(int index) |
删除指定索引处的元素,返回被删除的元素 |
public E set(int index, E element) |
修改指定索引处的元素,返回被修改的元素 |
public E get(int index) |
返回指定索引处的元素 |
public int size() |
返回集合中的元素的个数 |
诸位可以自行运行一下下方的代码,加深理解
Student.java
public class Student {
String name;
Integer age;
// 无参构造方法、带参构造方法、get()/set()方法、toString()方法 略
}
ArrayListDemo.java
public class ArrayListDemo {
public static void main(String[] args) {
System.out.println("------------------------------------------------");
// operateIntegerList();
operateStrList();
// createStudentList();
System.out.println("------------------------------------------------");
}
private static void operateIntegerList() {
ArrayList<Integer> arrayInteger = new ArrayList<>();
Collections.addAll(arrayInteger, 1, 2, 3, 4, 5);
for (Integer integer : arrayInteger) {
System.out.print(integer + " ");
}
}
private static void operateStrList() {
// 1.创建一个集合
ArrayList<String> strList = new ArrayList<>();
// 2.添加元素
strList.add("张无忌");
strList.add("张翠山");
strList.add("张三丰");
strList.add("宋远桥");
strList.add("俞莲舟");
System.out.println("最初的集合:" + strList);
// 3.删除元素
boolean result_1 = strList.remove("张翠山");
boolean result_2 = strList.remove("殷素素"); // 删除一个不存在的元素
System.out.println(result_1); // true
System.out.println("元素 [张翠山] 被成功删除");
System.out.println("【boolean remove(element)】删除元素后的集合:" + strList);
System.out.println(result_2); // false
System.out.println("集合中不存在 [殷素素] 元素");
String removeStr = strList.remove(1);
System.out.println("索引 1 处的元素 [" + removeStr + "] 被删除");
System.out.println("【E remove(int index)】删除元素后的集合:" + strList);
// 4.修改元素
System.out.println("修改 索引 1 处的元素:[" + strList.get(1) + "]");
strList.set(1, "宋远桥_Plus");
System.out.println("【E set(int index, E element)】修改后的集合:" + strList);
// 5.查询元素
String searchStr = strList.get(0);
System.out.println("【E get(int index)】查询集合中索引 0 处的元素:[" + searchStr + "]");
// 6.遍历
System.out.println("-------【遍历集合】-------");
System.out.print("[");
for (int i = 0; i < strList.size(); i++) {
if (i == strList.size() - 1) {
System.out.print(strList.get(i));
} else {
String str = strList.get(i);
System.out.print(str + " ");
}
}
System.out.println("]" + "\n" + "------------------------");
}
private static void createStudentList() {
List<Student> students = new ArrayList<>();
Student s1 = new Student("吉良吉影", 33);
Student s2 = new Student("东方仗助", 16);
Student s3 = new Student("广濑康一", 15);
Student s4 = new Student("虹村亿泰", 17);
Collections.addAll(students, s1, s2, s3, s4);
System.out.println("---------------------------------------");
for (int i = 0; i < students.size(); i++) {
System.out.println("[" + students.get(i).getName() + ":" + students.get(i).getAge() + "]");
}
System.out.println("---------------------------------------");
for (Student student : students) {
System.out.println(student);
}
System.out.println("---------------------------------------");
}
}
方法名 | 说明 |
---|---|
public static String toString(数组) |
把数组拼接成一个字符串 |
public static int binarySearch(数组, 查找的元素) |
使用二分查找法查找元素 |
public static int[] copyOf(原数组, 新数组的长度) |
拷贝数组 |
public static boolean[] copyOfRange(原数组, 起始索引, 结束索引) |
拷贝数组(指定范围:包头不包尾) |
public static void fill(数组, 元素) |
用输入的元素覆盖填充数组中的所有数据 |
public static void sort(数组) |
按照默认方式进行数组排序(升序排序) |
public static void sort(数组, 排序规则) |
按照指定的规则进行数组排序 |
这里详细介绍一下 public static void sort(数组, 排序规则)
方法。
java/util/Arrays.java
public static <T> void sort(T[] a, Comparator<? super T> c)
底层原理:该方法是利用 插入排序 + 二分查找 的方式来进行排序操作的。
这里我们说的再形象一点
java/util/Comparator.java
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);
}
显然,这是很典型的插入排序的算法。
诸位可以自行测试一下下方代码,帮助自己理解
public class ArraysDemo {
public static void main(String[] args) {
System.out.println("-------------------------------------------------");
Integer[] data_5 = {2, 1, 9, 3, 5, 4, 8, 6, 7};
System.out.println("排序前的数组 data_5:" + Arrays.toString(data_5));
System.out.println("-------------------------------------------------");
Arrays.sort(data_5, new Comparator<Integer>() {
@Override
public int compare(Integer t1, Integer t2) {
System.out.print("[t1:" + t1 + "] ");
System.out.print("[t2:" + t2 + "]\n");
// return t1 - t2; // 升序排序
return t2 - t1; // 降序排序
}
});
System.out.println("-------------------------------------------------");
System.out.println("排序后的数组 data_5:" + Arrays.toString(data_5));
System.out.println("-------------------------------------------------");
}
}
Collection 集合概述
创建 Collection 集合的对象
方法名 | 说明 |
---|---|
public boolean add(E e) |
添加元素(Set 集合中不会添加重复的元素) |
public boolean remove(Object o) |
从集合中移除指定的元素 |
public boolean removeIf(Object o) |
根据条件进行移除 |
public void clear() |
清空集合中的元素 |
public boolean contains(Object o) |
判断集合中是否存在指定的元素 |
public boolean isEmpty() |
判断集合是否为空 |
ipublic nt size() |
集合的长度,也就是集合中元素的个数 |
public Object[] toArray() |
把集合中的元素存储到对象数组中 |
boolean remove(Object o)
:从集合中移除指定的元素
boolean contains(Object o)
:判断集合中是否包含该元素
诸位可自行运行一下下方代码,加深理解。
Student.java
public class Student {
String name;
Integer age;
// 无参构造方法、带参构造方法、get()/set()方法、toString()方法 略
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student_2 student_2 = (Student_2) o;
return age == student_2.age && Objects.equals(name, student_2.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
CollectionDemo_1.java
public class CollectionDemo_1 {
public static void main(String[] args) {
testArrayList_1();
// testHashSet();
// testArrayList_2();
}
private static void testArrayList_1() {
Collection<String> collection = new ArrayList<>();
collection.add("张三");
collection.add("李四");
collection.add("王五");
collection.add("赵六");
System.out.println("集合:" + collection);
// collection.clear(); // 清空
boolean element1 = collection.remove("张三");
System.out.println("删除存在的元素的返回值:" + element1); // true
boolean element2 = collection.remove("朱元璋");
System.out.println("删除不存在的元素的返回值:" + element2); // false
System.out.println("被删除元素后的集合:" + collection);
System.out.println("判断元素是否包含 [李四]:" + collection.contains("李四"));
System.out.println("判断元素是否包含 [李三四]:" + collection.contains("李三四"));
boolean isEmpty = collection.isEmpty();
System.out.println("集合是否为空:" + isEmpty);
System.out.println("集合的长度是:" + collection.size());
}
private static void testArrayList_2() {
Collection<Student> collection = new ArrayList<>();
Collections.addAll(collection, new Student("LiSiSi", 23), new Student("LiSi", 24), new Student("WangWu", 25));
System.out.println(collection);
boolean result = collection.contains(new Student("LiSi", 24));
System.out.println("集合中是否包含 LiSi 元素:" + result);
}
private static void testHashSet() {
Collection<String> collection = new HashSet<>();
collection.add("张三");
collection.add("李四");
collection.add("王五");
collection.add("赵六");
collection.add("田七");
System.out.println(collection);
collection.add("张三"); // 不会添加重复的元素
System.out.println(collection);
}
}
迭代器介绍
java/util/Iterator.java
Iterator iterator()
:返回此集合中元素的迭代器,默认指向当前集合的 0 索引(通过集合对象的 iterator() 方法得到)Iterator<String> iterator = list.iterator();
Iterator 中的常用方法
boolean hasNext()
:判断当前位置是否有元素可以被取出,有元素则返回 true,无元素则返回 falseE next()
:获取当前位置的元素,并将迭代器对象移向下一个索引位置boolean flag = iterator.hasNext();
String nextString = iterator.next();
Collection 集合的遍历
List<String> list = new ArrayList<>();
Collections.addAll(list, "刘备", "曹操", "孙权");
Iterator<String> it = list.iterator(); // 创建指针
// 利用循环去不断地获取集合中的每一个元素
while (it.hasNext()) { // 判断是否有元素
String str = it.next(); // 获取当前元素,并移动指针
System.out.print(str + " ");
}
java.util.NoSuchElementException
。诸位可自行运行下方代码块中的代码,以加深理解。
public class CollectionDemo_2 {
public static void main(String[] args) {
List<String> collection = new ArrayList<>();
Collections.addAll(collection, "刘备", "曹操", "孙权", "司马懿", "刘裕");
// test_1(collection);
// test_2(collection);
test_3(collection);
}
private static void test_1(List<String> collection) {
Iterator<String> it = collection.iterator();
while (it.hasNext()) {
String str = it.next();
System.out.println(str);
}
// 上面的循环结束后,迭代器的指针已经指向了最后方没有元素的位置了。
try {
it.next(); // NoSuchElementException
} catch (NoSuchElementException e) {
e.printStackTrace();
}
System.out.println("---------------------------------");
// 迭代器遍历完毕后,指针不会复位
System.out.println(it.hasNext()); // false
System.out.println("---------------------------------");
// 若是想再对集合进行一次遍历操作,只能是再次获取一个新的迭代器对象
Iterator<String> it2 = collection.iterator();
while (it2.hasNext()) {
System.out.println(it2.next());
}
}
private static void test_2(List<String> collection) {
Iterator<String> iterator = collection.iterator();
try {
while (iterator.hasNext()) {
// 下面移动了两次指针,直接超过了集合的最大长度。
// 故在遍历完集中中的所有元素之后,最终报错:NoSuchElementException
String str = iterator.next();
System.out.println(str);
System.out.println(iterator.next());
System.out.println(iterator.next());
}
} catch (NoSuchElementException e) {
e.printStackTrace();
}
}
private static void test_3(List<String> collection) {
System.out.println(collection);
Iterator<String> it = collection.iterator();
while (it.hasNext()) {
String str = it.next();
if ("司马懿".equals(str)) collection.remove("司马懿"); // JAVA11 版本中,该方法未报错,且删除成功了
if ("刘裕".equals(str)) collection.add("萧道成"); // 添加失败
}
System.out.println(collection); // 并没有出现添加的元素
collection.add("萧衍");
System.out.println(collection);
}
}
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String str = iterator.next();
System.out.println(str);
}
NoSucheElementException
错误基本介绍
格式
for(集合/数组中元素的数据类型 变量名 : 集合/数组名) {
// 已经将当前遍历到的元素封装到变量中了,直接使用变量即可
}
for (String s : list)
中的 s,它其实就是记录每一次循环遍历到的元素数据的第三方变量。具体案例
List<String> list = new ArrayList<>();
Collections.addAll(list, "刘备", "曹操", "孙权", "司马懿", "刘裕");
// 在增强 for 中修改变量,并不会改变集合中原本的数据
// 下方的 s 只不过是一个记录数据的第三方变量名罢了
for (String s : list) {
s = "陈霸先";
System.out.print(s + " ");
}
System.out.println("\n" + list);
控制台输出信息
陈霸先 陈霸先 陈霸先 陈霸先 陈霸先
[刘备, 曹操, 孙权, 司马懿, 刘裕]
得益于 JDK 8 开始提供的新技术(Lambda 表达式),我们可以使用到一种更简单、更直接的遍历集合的方式。
方法名称 | 说明 |
---|---|
default void forEach(Consumer super T> action) |
可以结合 Lambda 遍历 |
java/lang/Iterable.java
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
其实 forEach() 方法的底层就是一个循环遍历,依次得到集合中的每一个元素,并且把每一个元素传递给 accept() 方法。accpet() 方法的形式参数,其依次代表着集合中的每一个元素。
具体案例
List<String> list = new ArrayList<>();
Collections.addAll(list, "刘备", "曹操", "孙权", "司马懿", "刘裕");
list.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.print(s + " ");
}
});
list.forEach((s) -> System.out.print(s + " "));
list.forEach(System.out::print);
List 集合的概述
集合的特点:存取有序、集合中的元素可以重复、有索引
方法名 | 描述 |
---|---|
void add(int index, E element) |
在此集合中的指定位置插入指定的元素 |
E remove(int index) |
删除指定索引处的元素,返回被删除的元素 |
E set(int index, E element) |
修改指定索引处的元素,返回被修改的元素 |
E get(int index) |
返回指定索引处的元素 |
注意事项
void add(int index, E element)
:在此集合中的指定位置插入指定的元素,该索引上的旧元素和其后的元素会依次向后移动E remove(int index)
示例代码-1
List<String> list = new ArrayList<>();
Collections.addAll(list, "李世民", "侯君集", "尉迟恭", "李靖", "秦琼");
list.add("徐世绩");
// 在指定位置出插入元素,原先索引上的元素会依次向后移动
list.add(1, "程知节");
System.out.println(list); // [李世民, 程知节, 侯君集, 尉迟恭, 李靖, 秦琼, 徐世绩]
// 删除指定索引处的元素,并返回被删除的元素
System.out.println(list.remove(3)); // 尉迟恭
System.out.println(list); // [李世民, 程知节, 侯君集, 李靖, 秦琼, 徐世绩]
示例代码-2
public class ListDemo_2 {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
Collections.addAll(list, 1, 2, 3, 4, 5);
System.out.println("初始元素:" + list);
// testRemove(list);
Integer set = list.set(0, 111);
System.out.println("被修改元素的原值:" + set);
System.out.println(list);
System.out.println("集合中索引 2 处的值是:" + list.get(2));
}
private static void testRemove(List<Integer> list) {
// 2 和 2 同为 int 类型,故此处使用的是索引
System.out.println(list.remove(2)); // 返回 3
System.out.println(list); // [1, 2, 4, 5]
Integer i = Integer.valueOf(1); // 手动装箱
// 此处的 i 和集合中的元素都是 Integer 类型,故使用的是集合中的元素的数据
boolean isRemoved = list.remove(i);
System.out.println(isRemoved); // true
System.out.println(list); // [2, 4, 5]
}
}
创建集合
List<String> list = new ArrayList<>();
Collections.addAll(list, "李世民", "侯君集", "尉迟恭", "李靖", "秦琼", "程知节", "徐世绩");
Iterator<String> it = list.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
for (String s : list) {
System.out.println(s);
}
// 匿名内部类
list.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.print(s + " ");
}
});
System.out.println();
// Lambda 表达式
list.forEach((s) -> System.out.print(s + " "));
for (int i = 0; i < list.size(); i++) {
if (i == list.size() - 1) System.out.print(list.get(i));
else System.out.print(list.get(i) + " ");
}
ListIterator<String> listIT = list.listIterator();
while (listIT.hasNext()) {
String s = listIT.next();
if ("侯君集".equals(s)) {
listIT.add("李承乾");
}
}
System.out.println(list); // [李世民, 侯君集, 李承乾, 尉迟恭, 李靖, 秦琼, 程知节, 徐世绩]
五种遍历方式对比
关于 ArrayList 的相关方法在之前的 【ArrayList 入门】一节中已经讲过了,这里不再赘述。
ArrayList 集合底层原理
ArrayList 集合部分源码
java/util/ArrayList.java
private static final int DEFAULT_CAPACITY = 10;
transient Object[] elementData;
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
private int size;
public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
}
}
方法名 | 说明 |
---|---|
public void addFirst(E e) |
在该列表开头插入指定的元素 |
public void addLast(E e) |
将指定的元素追加到此列表的末尾 |
public E getFirst() |
返回此列表中的第一个元素 |
public E getLast() |
返回此列表中的最后一个元素 |
public E removeFirst() |
从此列表中删除并返回第一个元素 |
public E removeLast() |
从此列表中删除并返回最后一个元素 |
示例代码
public class LinkedListDemo {
public static void main(String[] args) {
LinkedList<String> list = new LinkedList<>();
Collections.addAll(list, "长孙无忌", "褚遂良", "魏征", "房玄龄", "杜如晦", "封德彝");
System.out.println("list:" + list);
method1(list); // addFirst()
method2(list); // addLast()
method3(list); // getLast()、getFirst()
method4(list); // removeFirst() + removeLast()
}
private static void method4(LinkedList<String> list) {
String first = list.removeFirst();
System.out.println("removeFirst():" + first);
String last = list.removeLast();
System.out.println("removeLast():" + last);
System.out.println("list:" + list);
}
private static void method3(LinkedList<String> list) {
String first = list.getFirst();
String last = list.getLast();
System.out.println("list.getFirst():" + first);
System.out.println("list.getLast():" + last);
}
private static void method2(LinkedList<String> list) {
list.addLast("马周");
System.out.println("list.addLast(\"马周\"):" + list);
}
private static void method1(LinkedList<String> list) {
list.addFirst("萧瑀");
System.out.println("list.addFirst(\"萧瑀\"):" + list);
}
}
控制台输出信息
list:[长孙无忌, 褚遂良, 魏征, 房玄龄, 杜如晦, 封德彝]
list.addFirst("萧瑀"):[萧瑀, 长孙无忌, 褚遂良, 魏征, 房玄龄, 杜如晦, 封德彝]
list.addLast("马周"):[萧瑀, 长孙无忌, 褚遂良, 魏征, 房玄龄, 杜如晦, 封德彝, 马周]
list.getFirst():萧瑀
list.getLast():马周
removeFirst():萧瑀
removeLast():马周
list:[长孙无忌, 褚遂良, 魏征, 房玄龄, 杜如晦, 封德彝]
java/util/LinkedList.java
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
transient int size = 0;
transient Node<E> first;
transient Node<E> last;
public LinkedList() { }
... ...
// 这个是 LinkedList 类里的一个比较核心的内部类
private static class Node<E> {
E item; // 存储元素
Node<E> next; // 后继节点
Node<E> prev; // 前驱节点
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
... ...
}
java/util/Iterator.java
public interface Iterator<E> {
boolean hasNext();
E next();
default void remove() {
throw new UnsupportedOperationException("remove");
}
default void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (hasNext())
action.accept(next());
}
}
可见迭代器是一个接口,具体的代码实现是在集合里的。
java/util/ArrayList.java
private class Itr implements Iterator<E> {
... ...
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException(); // 抛出并发异常
}
... ...
}
树的相关概念
树的子节点的内部结构
二叉树的特点:在二叉树中,任意一个节点的度要小于等于 2
二叉树的结构图
二叉查找树,又称二叉排序树或二叉搜索树
二叉查找树的特点:
二叉查找树添加节点的规则:小的存左边,大的存右边,一样的不存。
二叉树的遍历方式:
相关视频:【Java 基础 | 集合 | 数据结构 | 平衡二叉树旋转机制】
下面写的有点简略,如果看不懂的话,可以看看上面推荐的这个视频。
平衡二叉树旋转
推荐阅读博客:《漫画:什么是红黑树?》
相关视频:【Java 基础 | 集合 | 数据结构 | 红黑树、红黑规则、添加节点处理方案】
红黑树添加节点后如何保持红黑规则这块儿是难点,这里建议看看上面推荐的视频,讲的蛮好的(通俗易懂)。
平衡二叉树
红黑树的基本概念
红黑树的红黑规则
Set 接口中的方法上基本与 Collection 的 API 一致
// Set 集合的三种遍历方式,诸位可以自行运行一下下方的代码
public class SetDemo_1 {
public static void main(String[] args) {
Set<String> hashSet = new HashSet<>();
Collections.addAll(hashSet, "兆敏", "海兰察", "阿桂");
System.out.println(hashSet);
// ergodic_iterator(hashSet);
// ergodic_forPlus(hashSet);
ergodic_forEach(hashSet);
}
private static void ergodic_forEach(Set<String> hashSet) {
hashSet.forEach(s -> System.out.print(s + " "));
}
private static void ergodic_forPlus(Set<String> hashSet) {
for (String string : hashSet) {
System.out.print(string + " ");
}
}
private static void ergodic_iterator(Set<String> hashSet) {
Iterator<String> iterator = hashSet.iterator();
while (iterator.hasNext()) {
String s = iterator.next();
System.out.print(s + " ");
}
}
}
public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable
HashSet 集合的底层采用哈希表来存储数据
哈希表的组成
哈希值简介
如何获取哈希值
public int hashCode()
(返回对象的哈希码值),默认使用地址值进行计算对象哈希值的特点
System.out.println(s1.hashCode()); // 24022543
System.out.println(s2.hashCode()); // 24022543
既然两个对象有相同的哈希值时,属性值也不一定相同,那我们如何准确的比较对象的属性值呢?
此时就需要重写 equals() 方法了,目的是比较对象内部的属性值。
参考书籍:《Head First Java》
当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashCode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashCode 值作比较,如果没有相符的 hashCode,HashSet 会假设对象没有重复出现。但是如果发现有相同 hashCode 值的对象,这时会调用 equals() 方法来检查 hashCode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。
哈希表的组成
HashSet<String> table = new HashSet<>();
int index = (数组长度 - 1) & 哈希值;
JDK 8 之前:数组 + 链表
JDK 8 以后
注意事项(总结)
两张图总结上面的描述
问题 1:HashSet 为什么是无序的?
在 Java 中的哈希表里,是通过 (数组长度 - 1) & 哈希值
计算出元素应存入的数组位置的,之后再插入该位置的链表/红黑树里。
HashSet 在遍历查找元素的过程中,其是按照数组的顺序一个个遍历的,我们并不能保证我们查找到的数组位置上的链表(或红黑树)上的目标值的位置与存入集合的顺序是一致的。例如我们要找的目标值在数组的第一个位置的链表头(这里就默认头结点有数据的吧),但这里我们并不能保证它就是第一个放入集合的元素。
问题 2:HashSet 为什么没有索引?
其底部是数组 + 链表(或数组 + 链表 +红黑树 )组成的,并不好规定相应的索引规则。
说的通俗一点,数组底下还挂着链表呢,给链表的索引统一为同一数字?
此外还有扩容的情况,哈希值的重新计算等问题 … … 是不好制定一套相应的索引规则的。
问题 3:HashSet 是利用什么机制保证数据去重的?
利用 hashCode() 方法得到哈希值,通过哈希值确定元素在的数组中的位置,之后再调用 equals() 方法比较对象属性的值是否相同。
强调:如果 HashSet 中存储的是自定义对象,那么一定要在自定义对象中重写 hashCode() 方法和 equals() 方法。
Student.java
public class Student {
private String name;
private int age;
// 无参构造方法、带参构造方法、get()/set()方法、toString()方法 略
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
HashSetDemo.java
public class HashSetDemo {
public static void main(String[] args) {
Student s1 = new Student("徐阶", 80);
Student s2 = new Student("高拱", 70);
Student s3 = new Student("张居正", 50);
Student s4 = new Student("张居正", 50);
HashSet<Student> hashSet = new HashSet<>();
hashSet.add(s1);
hashSet.add(s2);
hashSet.add(s3);
hashSet.add(s4);
System.out.println(hashSet);
// 打印结果:[Student{name='张居正', age=50}, Student{name='徐阶', age=80}, Student{name='高拱', age=70}]
}
}
LinkedHahSet 继承了 HashSet
public class LinkedHashSet<E> extends HashSet<E> implements Set<E>, Cloneable, java.io.Serializable
TreeSet 是一个基于 TreeMap 的 NavigableSet 实现
public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>, Cloneable, java.io.Serializable
TreeSet 的特点
TreeSet 集合的默认排序规则
诸位可以自行运行一下下方的代码,加深理解
Student_3.java
public class Student_3 implements Comparable<Student_3> {
private String name;
private int age;
// 无参构造方法、带参构造方法、get()/set()方法、toString()方法 略
@Override
public int compareTo(Student_3 s) {
System.out.println("--------------------------------------------------------------------------");
System.out.println("this(当前要添加的元素,未排序):" + this);
System.out.println("s(当前已经在红黑树中的元素,已排序):" + s);
System.out.println("--------------------------------------------------------------------------");
/*
* 首先默认是升序排序(未排序 - 已排序)
* 返回值是负数,表示当前要添加的元素更大,需要存储在左边
* 返回值是正数,表示当前要添加的元素更小,需要存储在右边
* 返回值是 0,表示当前要添加的元素已经存在,舍弃
*/
int result = this.getAge() - s.getAge(); // 按照年龄大小升序排序
return result;
}
}
TreeSetDemo.java
public class TreeSetDemo {
public static void main(String[] args) {
Student_3 s1 = new Student_3("zhangsan", 23);
Student_3 s3 = new Student_3("wangwu", 25);
Student_3 s2 = new Student_3("lisi", 24);
Student_3 s4 = new Student_3("zhaoliu", 26);
Student_3 s5 = new Student_3("lisi", 24);
TreeSet<Student_3> students = new TreeSet<>();
Collections.addAll(students, s1, s4, s3, s2, s5);
System.out.println(students);
}
}
TreeSet 集合添加数据的过程
java/lang/Comparable.java
public interface Comparable<T> {
public int compareTo(T o);
}
练习案例
Student.java
public class Student implements Comparable<Student>{
private String name;
private int age;
// 无参构造方法、带参构造方法、get()/set()方法、toString()方法 略
@Override
public int compareTo(Student o) {
// 主要判断条件: 按照对象的年龄从小到大排序
int result = this.age - o.age;
// 次要判断条件: 在对象的年龄相同时,按照对象的姓名的字母顺序排序
result = result == 0 ? this.name.compareTo(o.getName()) : result;
return result;
}
}
MyTreeSet_1.java
public class MyTreeSet_1 {
public static void main(String[] args) {
// 创建集合对象
TreeSet<Student> ts = new TreeSet<>();
// 创建学生对象
Student s1 = new Student("zhangsan", 28);
Student s2 = new Student("lisi", 27);
Student s3 = new Student("wangwu", 29);
Student s4 = new Student("zhaoliu", 28);
Student s5 = new Student("qianqi", 30);
// 把学生添加到集合
Collections.addAll(ts, s3, s2, s1, s5, s4);
// 遍历集合
for (Student student : ts) {
System.out.println(student);
}
}
}
输出结果
Student{name='lisi', age=27}
Student{name='zhangsan', age=28}
Student{name='zhaoliu', age=28}
Student{name='wangwu', age=29}
Student{name='qianqi', age=30}
java/util/TreeSet.java
public Comparator<? super E> comparator() {
return m.comparator();
}
java/util/Comparator.java
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);
// ... ...
}
练习案例
Teacher.java
public class Teacher {
private String name;
private int age;
// 无参构造方法、带参构造方法、get()/set()方法、toString()方法 略
}
MyTreeSet_2.java
public class MyTreeSet_2 {
public static void main(String[] args) {
// 创建集合对象
TreeSet<Teacher> ts = new TreeSet<>(new Comparator<Teacher>() {
@Override
public int compare(Teacher o1, Teacher o2) {
// o1 表示现在要存入的那个元素(尚未排序)
// o2 表示已经存入到集合中的元素(已经在红黑树中了,是已经排序过了的元素)
int result = o1.getAge() - o2.getAge(); // 主要条件
result = result == 0 ? o1.getName().compareTo(o2.getName()) : result; // 次要条件
return result;
}
});
// 创建老师对象
Teacher t1 = new Teacher("zhangsan", 23);
Teacher t2 = new Teacher("lisi", 22);
Teacher t3 = new Teacher("wangwu", 24);
Teacher t4 = new Teacher("zhaoliu", 24);
// 把老师添加到集合
Collections.addAll(ts, t2, t4, t1, t3);
// 打印集合
ts.forEach(System.out::println);
}
}
控制台输出
Teacher{name='lisi', age=22}
Teacher{name='zhangsan', age=23}
Teacher{name='wangwu', age=24}
Teacher{name='zhaoliu', age=24}
java/util/HashSet.java
public HashSet() {
map = new HashMap<>();
}
java/util/LinkedHashSet.java
public LinkedHashSet() {
super(16, .75f, true);
}
java/util/HashSet.java
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
java/util/TreeSet.java
public TreeSet() {
this(new TreeMap<E,Object>());
}
显然,这三个 Set 接口的实现类都与 Map 接口的实现类息息相关,故真正的源码分析会放到 Map 集合的篇章中再讲。
方法名 | 说明 |
---|---|
V put(K key, V value) |
添加元素 |
V remove(Object key) |
根据键删除键值对元素 |
void clear() |
移除所有的键值对元素 |
boolean containsKey(Object key) |
判断集合是否包含指定的键 |
boolean containsValue(Object value) |
判断集合是否包含指定的值 |
boolean isEmpty() |
判断集合是否为空 |
int size() |
集合的长度,也就是集合中键值对的个数 |
V put(K key, V value)
细节
诸位可以自行运行一下下方代码
public class MapDemo_1 {
public static void main(String[] args) {
Map<Integer, String> map = new HashMap<>();
System.out.println("-----------------------------------");
System.out.println(map.put(1, "阿朱")); // null
System.out.println(map.put(2, "黄蓉")); // null
System.out.println(map.put(3, "小龙女")); // null
System.out.println("被覆盖的值:" + map.put(3, "赵敏")); // 返回被覆盖的值:小龙女
System.out.println(map); // {1=阿朱, 2=黄蓉, 3=赵敏}
System.out.println("当前键值对的数量:" + map.size()); // 3
System.out.println("-----------------------------------");
System.out.println(map.containsKey(2)); // true
System.out.println(map.containsKey(10)); // false
System.out.println(map.containsValue("赵敏")); // true
System.out.println(map.containsValue("王语嫣")); // false
System.out.println(map.remove(1)); // 阿朱
System.out.println(map); // {2=黄蓉, 3=赵敏}
System.out.println("-----------------------------------");
System.out.println(map.isEmpty()); // false
map.clear(); // 清空集合里所有的键值对元素,没有返回值
System.out.println(map); // {}
System.out.println(map.isEmpty()); // true
System.out.println("-----------------------------------");
}
}
方法名 | 说明 |
---|---|
V get(Object key) |
根据键获取值 |
Set |
获取所有键的集合 |
Collection |
获取所有值的集合 |
Set |
获取所有键值对对象的集合 |
诸位可以自行运行一下下方代码
public class MapDemo_2 {
public static void main(String[] args) {
//创建集合对象
Map<Integer, String> map = new HashMap<>();
//添加元素
map.put(1, "乔峰");
map.put(2, "郭靖");
map.put(3, "杨过");
map.put(4, "张无忌");
System.out.println(map.get(1)); // 乔峰
System.out.println(map.get(3)); // 杨过
Set<Integer> keySet = map.keySet();
System.out.println("keySet:" + keySet); // keySet:[1, 2, 3, 4]
Collection<String> values = map.values();
System.out.println("values:" + values); // values:[乔峰, 郭靖, 杨过, 张无忌]
Set<Map.Entry<Integer, String>> entries = map.entrySet();
System.out.println("entries:" + entries); // entries:[1=乔峰, 2=郭靖, 3=杨过, 4=张无忌]
}
}
对于 Map 的遍历方式,在大的方向上可以分为 4 种。
分的再具体点的话,可以分成 7 种
示例代码 |
首先我们创建一个 Map 集合
Map<Integer, String> map = new HashMap();
map.put(1, "Spring");
map.put(2, "SpringMVC");
map.put(3, "SpringBoot");
map.put(4, "SpringCloud");
map.put(5, "MyBatis");
map.put(6, "MyBatisPlus");
Iterator KeySet
private static void ergodic_KeySet_1(Map<Integer, String> map) {
Set<Integer> keySet = map.keySet();
Iterator<Integer> iterator = keySet.iterator();
while (iterator.hasNext()) {
Integer key = iterator.next();
System.out.println(key + ";" + map.get(key));
}
}
ForEach KeySet
private static void ergodic_KeySet_2(Map<Integer, String> map) {
Set<Integer> keySet = map.keySet();
for (Integer key : keySet) {
System.out.println(key + ":" + map.get(key));
}
}
Iterator EntrySet
private static void ergodic_EntrySet_1(Map<Integer, String> map) {
Set<Map.Entry<Integer, String>> entries = map.entrySet();
Iterator<Map.Entry<Integer, String>> iterator = entries.iterator();
while (iterator.hasNext()) {
Map.Entry<Integer, String> entry = iterator.next();
System.out.println(entry.getKey() + ";" + entry.getValue());
}
}
ForEach EntrySet
private static void ergodic_EntrySet_2(Map<Integer, String> map) {
Set<Map.Entry<Integer, String>> entrySet = map.entrySet();
for (Map.Entry<Integer, String> entry : entrySet) {
System.out.println(entry.getKey() + ";" + entry.getValue());
}
}
ForEach Lambda
private static void ergodic_Lambda_ForEach(Map<Integer, String> map) {
map.forEach((key, value) -> {
System.out.println(key + ":" + value);
}
);
}
private static void ergodic_Stream_1(Map<Integer, String> map) {
map.entrySet().stream().forEach((entry) -> {
System.out.println(entry.getKey() + ":" + entry.getValue());
});
}
private static void ergodic_Stream_2(Map<Integer, String> map) {
map.entrySet().parallelStream().forEach((entry) -> {
System.out.println(entry.getKey() + ":" + entry.getValue());
});
}
其中 Map 的 forEach() 方法源码如下
java/util/Map.java
default void forEach(BiConsumer<? super K, ? super V> action) {
Objects.requireNonNull(action);
for (Map.Entry<K, V> entry : entrySet()) {
K k;
V v;
try {
k = entry.getKey();
v = entry.getValue();
} catch(IllegalStateException ise) {
// this usually means the entry is no longer in the map.
throw new ConcurrentModificationException(ise);
}
action.accept(k, v);
}
}
我们使用 forEach() 遍历 Map 集合时,主要就是编写 accept() 方法
// 匿名内部类的方式
map.forEach(new BiConsumer<Integer, String>() {
@Override
public void accept(Integer key, String value) {
System.out.println(key + ":" + value);
}
});
// 用 Lambda 表达式简化一下
map.forEach((key, value) -> System.out.println(key + ":" + value));
HashMap 的特点
HashMap 的底层原理
这里需要重点说的还是 JAVA 中的 哈希表 的结构
HashMap 通过 键的哈希值 来找到元素在数组中的位置(hashCode() 方法)。
在 HashMap 插入元素到数组的时候(map.put(key, value)
)
80 人去旅游,有 [A, B, C, D] 四个景点。每个人只能选一个景点,请统计出哪个景点要去的人最多。
public class HashMapDemo_1 {
public static void main(String[] args) {
String[] strings = {"A", "B", "C", "D"};
System.out.println("景点名称:" + Arrays.toString(strings));
List<String> list = new ArrayList<>();
Random random = new Random();
for (int i = 0; i < 80; i++) {
int index = random.nextInt(strings.length);
list.add(strings[index]);
}
Map<String, Integer> hashMap = new HashMap<>();
for (String name : list) {
if (hashMap.containsKey(name)) {
Integer count = hashMap.get(name);
count++;
hashMap.put(name, count);
} else {
hashMap.put(name, 1);
}
}
System.out.println("统计情况:" + hashMap);
int max = 0;
Set<Map.Entry<String, Integer>> entries = hashMap.entrySet();
for (Map.Entry<String, Integer> entry : entries) {
int count = entry.getValue();
if (count > max) {
max = count;
}
}
System.out.println("最大值:" + max);
for (Map.Entry<String, Integer> entry : entries) {
int count = entry.getValue();
if (count == max) {
System.out.println("投票最多的景点:" + entry.getKey());
}
}
}
}
代码书写两种排序规则
案例需求:
public class TreeMapDemo_1 {
public static void main(String[] args) {
Map<Integer, String> treeMap = new TreeMap<>(new Comparator<Integer>() {
@Override
public int compare(Integer unsorted, Integer sorted) {
return unsorted - sorted; // 升序排序
// return sorted - unsorted; // 降序排序
}
});
treeMap.put(3, "九个核桃");
treeMap.put(1, "粤利粤");
treeMap.put(2, "康帅傅");
treeMap.put(4, "雷碧");
treeMap.put(4, "可恰可乐");
System.out.println(treeMap);
}
}
案例需求
Student.java
public class Student implements Comparable<Student> {
private String name;
private int age;
// 构造方法、get()/set() 方法、toString() 方法、hashCode() 方法、equals() 方法 省略 ... ...
@Override
public int compareTo(Student unsorted) {
int result = unsorted.age - this.age;
result = result == 0 ? unsorted.name.compareTo(this.name) : result;
return result;
}
}
TreeMapDemo_2.java
public class TreeMapDemo_2 {
public static void main(String[] args) {
// 创建 TreeMap 集合对象
Map<Student, String> treeMap = new TreeMap<>();
// 创建学生对象
Student s1 = new Student("Tom", 6);
Student s2 = new Student("Jerry", 6);
Student s3 = new Student("Spike", 8);
// 将学生对象添加到 TreeMap 集合中
treeMap.put(s1, "A 区");
treeMap.put(s2, "B 区");
treeMap.put(s3, "C 区");
// 遍历 TreeMap 集合,打印每个学生的信息
treeMap.forEach((student, address) -> System.out.println(student + ":" + address));
}
}
案例需求:
public class TreeMapDemo_3 {
public static void main(String[] args) {
String strings = "aababcabcdabcde";
Map<Character, Integer> treeMap = new TreeMap<>();
for (int i = 0; i < strings.length(); i++) {
char c = strings.charAt(i);
if (treeMap.containsKey(c)) {
int count = treeMap.get(c);
count++;
treeMap.put(c, count);
} else {
treeMap.put(c, 1);
}
}
System.out.println(treeMap);
// 遍历集合,并且按照指定的方式进行拼接
StringBuilder stringBuilder = ergodic_1(treeMap);
System.out.println(stringBuilder);
StringJoiner stringJoiner = ergodic_2(treeMap);
System.out.println(stringJoiner);
}
private static StringJoiner ergodic_2(Map<Character, Integer> treeMap) {
StringJoiner stringJoiner = new StringJoiner("", "", "");
treeMap.forEach((key, value) -> stringJoiner.add(key + "").add("(").add(value + "").add(")"));
return stringJoiner;
}
private static StringBuilder ergodic_1(Map<Character, Integer> treeMap) {
StringBuilder stringBuilder = new StringBuilder();
treeMap.forEach(new BiConsumer<Character, Integer>() {
@Override
public void accept(Character key, Integer value) {
stringBuilder.append(key).append("(").append(value).append(")");
}
});
return stringBuilder;
}
}
java/util/HashMap.java
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable
HashMap 里面每一个节点对象的内部属性
int hash;
:键的哈希值final K key;
:键V value;
:值Node next;
:下一个节点的地址值int hash;
:键的哈希值final K key;
:键V value;
:值TreeNode parent;
:父节点的地址值TreeNode left;
:左子节点的地址值TreeNode right;
:右子节点的地址值boolean red;
:节点的颜色transient Node<K,V>[] table; // 记录了节点组成的数组的地址值
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 数组的默认长度是 16
static final float DEFAULT_LOAD_FACTOR = 0.75f; // 默认的加载因子
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
// 节点组成的数组在我们添加元素的时候才会创建
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
// 利用键计算出对应的哈希值,再对哈希值进行一些额外的处理
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
下方的代码是更改删减版,更易于阅读,把握关键信息
/**
* @param hash 键的哈希值
* @param key 键
* @param value 值
* @param onlyIfAbsent 如果键重复了,是否保留?
* true:表示老元素的值保留,不会覆盖
* false:表示老元素的值不保留,会进行覆盖
* @param evict
* @return
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
Node<K, V>[] tab; // 定义一个局部变量,用来记录哈希表中数组的地址值。
Node<K, V> p; // 临时的第三方变量,用来记录键值对对象的地址值
int n; // 表示当前数组的长度
int i; // 表示索引
tab = table; // 把哈希表中数组的地址值,赋值给局部变量 tab
if (tab == null || (n = tab.length) == 0) {
// 1.如果当前是第一次添加数据,底层会创建一个默认长度为 16,加载因子为 0.75 的数组
// 2.如果不是第一次添加数据,会看数组中的元素是否达到了扩容的条件
// 如果没有达到扩容条件,底层不会做任何操作
// 如果达到了扩容条件,底层会把数组扩容为原先的两倍,并把数据全部转移到新的哈希表中
tab = resize();
// 表示把当前数组的长度赋值给n
n = tab.length;
}
// 拿着数组的长度跟键的哈希值进行计算,计算出当前键值对对象,在数组中应存入的位置
i = (n - 1) & hash; // index
p = tab[i]; // 获取数组中对应元素的数据
if (p == null) {
// 底层会创建一个键值对对象,直接放到数组当中
tab[i] = newNode(hash, key, value, null);
} else {
Node<K, V> e;
K k;
// 等号的左边:数组中键值对的哈希值
// 等号的右边:当前要添加键值对的哈希值
// 如果键不一样,此时返回 false
// 如果键一样,返回 true
boolean b1 = p.hash == hash;
if (b1 && ((k = p.key) == key || (key != null && key.equals(k)))) {
e = p;
} else if (p instanceof TreeNode) {
// 判断数组中获取出来的键值对是不是红黑树中的节点
// 如果是,则调用方法 putTreeVal,把当前的节点按照红黑树的规则添加到树当中。
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,如果超过 8,就会调用方法 treeifyBin
// treeifyBin 方法的底层还会继续判断
// 判断数组的长度是否大于等于 64
// 如果同时满足这两个条件,就会把这个链表转成红黑树
if (binCount >= TREEIFY_THRESHOLD - 1)
treeifyBin(tab, hash);
break;
}
// 举例:e 为[0x0044 ddd 444],要添加的元素为[0x0055 ddd 555]
// 如果哈希值一样,就会调用 equals 方法比较内部的属性值是否相同
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) {
break;
}
p = e;
}
}
// 如果 e 为 null,表示当前不需要覆盖任何元素
// 如果 e 不为 null,表示当前的键是一样的,值会被覆盖
// 举例:e 为 [0x0044 ddd 444],要添加的元素为 [0x0055 ddd 555]
if (e != null) {
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null) {
// 等号的右边:当前要添加的值(0x0055 的 555)
// 等号的左边:旧值(0x0044 的 444)
e.value = value;
}
afterNodeAccess(e);
return oldValue;
}
}
// 这里的 threshold 记录的就是(数组的长度 * 0.75),可以理解为其就是哈希表的扩容时机(16 * 0.75 = 12)
if (++size > threshold) {
resize();
}
// 表示当前没有覆盖任何元素,返回 null
return null;
}
此处贴一下 HashMap 中的节点类的部分源码
HashMap 中的内部类:Node
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash; // 键的哈希值
this.key = key; // 键
this.value = value; // 值
this.next = next; // 记录下一个节点的地址值
}
// ... ...
}
HashMap 中的内部类:TreeNode
TreeNode 继承了 LinkedHashMap.Entry
,LinkedHashMap.Entry
又继承了 HashMap.Entry
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
TreeNode<K,V> parent; // 父节点的地址值
TreeNode<K,V> left; // 左子节点的地址值
TreeNode<K,V> right; // 右子节点的地址值
TreeNode<K,V> prev; // 删除后需要取消下一个链接
boolean red; // 节点颜色
TreeNode(int hash, K key, V val, Node<K,V> next) {
super(hash, key, val, next);
}
// ... ...
}
java/util/LinkedHashMap.java
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);
}
}
java/util/TreeMap.java
public class TreeMap<K,V> extends AbstractMap<K,V> implements NavigableMap<K,V>, Cloneable, java.io.Serializable
K key;
:键V value;
:值Entrye parent;
:父节点的地址值Entry left;
:左子节点的地址值Entry right;
:右子节点的地址值boolean red;
:节点的颜色int hash;
:键的哈希值public class TreeMap<K,V>{
private final Comparator<? super K> comparator; // 比较器对象(比较规则)
private transient Entry<K,V> root; // 根节点
private transient int size = 0; // 集合的长度
}
public TreeMap() {
comparator = null;
}
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
public V put(K key, V value) {
return put(key, value, true);
}
/**
* @param key 键
* @param value 值
* @param replaceOld 当键重复的时候,是否需要覆盖值
* true:覆盖
* false:不覆盖
* @return
*/
private V put(K key, V value, boolean replaceOld) {
// 获取根节点的地址值,赋值给局部变量 t
Entry<K, V> t = root;
// 判断根节点是否为 null
// 如果为 null,表示当前是第一次添加,会把当前要添加的元素,当做根节点
// 如果不为 null,表示当前不是第一次添加,跳过这个判断继续执行下面的代码
if (t == null) {
// 方法的底层,会创建一个 Entry 对象,把他当做根节点
addEntryToEmptyMap(key, value);
// 返回 null,表示此时没有覆盖任何的元素
return null;
}
int cmp; // 表示两个元素的键比较之后的结果
Entry<K, V> parent; // 表示当前要添加节点的父节点
// 表示当前的比较规则
// 如果我们是采取默认的自然排序,那么此时 comparator 记录的是 null,cpr 记录的也是 null
// 如果我们是采取比较去排序方式,那么此时 comparator 记录的是就是比较器
Comparator<? super K> cpr = comparator;
// 表示判断当前是否有比较器对象
// 如果传递了比较器对象,就执行 if{} 里面的代码,此时以比较器的规则为准
// 如果没有传递比较器对象,就执行 else{} 里面的代码,此时以自然排序的规则为准
if (cpr != null) {
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else {
V oldValue = t.value;
if (replaceOld || oldValue == null) {
t.value = value;
}
return oldValue;
}
} while (t != null);
} else {
// 把键进行强转,强转成 Comparable 类型的
// 要求:键必须要实现 Comparable 接口
// 如果没有实现这个接口,此时在强转的时候,就会报错
Comparable<? super K> k = (Comparable<? super K>) key;
do {
parent = t; // 此处的赋值操作即把根节点当做当前节点的父节点
// 调用 compareTo 方法,比较根节点和当前要添加节点的大小关系
cmp = k.compareTo(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);
}
// 在这个方法的底层就会把当前节点按照指定的规则进行添加
addEntry(key, value, parent, cmp < 0);
return null;
}
private void addEntry(K key, V value, Entry<K, V> parent, boolean addToLeft) {
Entry<K, V> e = new Entry<>(key, value, parent);
if (addToLeft)
parent.left = e;
else
parent.right = e;
// 添加完毕之后,需要按照红黑树的规则进行调整
fixAfterInsertion(e);
size++;
modCount++;
}
根据红黑树的规则调整
private void fixAfterInsertion(Entry<K, V> x) {
// 因为红黑树的节点默认就是红色的
x.color = RED;
/*
* 按照红黑规则进行调整
* * parentOf:获取 x 的父节点
* * parentOf(parentOf(x)):获取 x 的爷爷节点
* * leftOf:获取左子节点
*/
while (x != null && x != root && x.parent.color == RED) {
// 判断当前节点的父节点是爷爷节点的左子节点还是右子节点
// 目的:为了获取当前节点的叔叔节点
if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
// 表示当前节点的父节点是爷爷节点的左子节点
// 那么下面就可以用 rightOf() 方法获取到当前节点的叔叔节点
Entry<K, V> y = rightOf(parentOf(parentOf(x)));
if (colorOf(y) == RED) {
/* 叔叔节点为红色的处理方案 */
// 把父节点设置为黑色
setColor(parentOf(x), BLACK);
// 把叔叔节点设置为黑色
setColor(y, BLACK);
// 把爷爷节点设置为红色
setColor(parentOf(parentOf(x)), RED);
// 把爷爷节点设置为当前节点
x = parentOf(parentOf(x));
} else {
/* 叔叔节点为黑色的处理方案 */
// 表示判断当前节点是否为父节点的右子节点
if (x == rightOf(parentOf(x))) {
// 表示当前节点是父节点的右子节点
x = parentOf(x);
// 左旋
rotateLeft(x);
}
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
rotateRight(parentOf(parentOf(x)));
}
} else {
// 表示当前节点的父节点是爷爷节点的右子节点
// 那么下面就可以用 leftOf() 方法获取到当前节点的叔叔节点
Entry<K, V> y = leftOf(parentOf(parentOf(x)));
if (colorOf(y) == RED) {
setColor(parentOf(x), BLACK);
setColor(y, BLACK);
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x));
} else {
if (x == leftOf(parentOf(x))) {
x = parentOf(x);
rotateRight(x);
}
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
rotateLeft(parentOf(parentOf(x)));
}
}
}
// 把根节点设置为黑色
root.color = BLACK;
}
此处贴一下 TreeMap 中的键值对对象的部分源码
private static final boolean RED = false;
private static final boolean BLACK = true;
static final class Entry<K,V> implements Map.Entry<K,V> {
K key;
V value;
Entry<K,V> left;
Entry<K,V> right;
Entry<K,V> parent;
// 节点的颜色(此处默认是黑色)
// 在 TreeMap 调用 put() 方法的时候会调整颜色的
boolean color = BLACK;
Entry(K key, V value, Entry<K,V> parent) {
this.key = key;
this.value = value;
this.parent = parent;
}
// ... ...
}
在 JDK 1.5 之后,如果我们定义一个方法需要接受多个参数,并且多个参数类型一致,我们可以对其简化。
修饰符 返回值类型 方法名(参数类型... 形参名){ }
代码演示
public class ArgsDemo {
public static void main(String[] args) {
int sum = getSum(6, 7, 2, 12, 2121);
System.out.println(sum);
}
public static int getSum(int... arr) {
int sum = 0;
for (int a : arr) sum += a;
return sum;
}
}
应用场景:Collections
public static boolean addAll(Collection c, T... elements)
:往集合中添加一些元素。public class CollectionsDemo {
public static void main(String[] args) {
List<Integer> list = new ArrayList<Integer>();
// 采用[工具类]一次完成像集合中添加元素的操作
Collections.addAll(list, 5, 222, 1, 2);
System.out.println(list);
}
}
java.utils.Collections
是集合的工具类,用来对集合进行操作的。
Collections 中提供的一些 API
方法名称 | 说明 |
---|---|
public static void shuffle(List> list) |
打乱集合顺序 |
public static |
将集合中元素按照默认规则排序 |
public static |
将集合中元素按照指定规则排序 |
public static |
将集合中元素按照默认规则排序 |
public static ) |
将集合中元素按照指定规则排序 |
public static |
以二分查找法朝招元素 |
public static |
拷贝集合中的元素 |
public static |
使用指定的元素填充集合 |
public static |
根据默认的自然排序获得最大值/最小值 |
public static void swap(List> list, int i, int j) |
交换集合中指定位置的元素 |
Collections.sort()
不可以直接对自定义类型的 List 集合排序,除非其实现了比较规则 Comparable 接口示例代码
Student.java
public class Student {
private String name;
private int age;
// 构造方法、get/set、toString(省略)
}
CollectionsDemo_2
public class CollectionsDemo_2 {
public static void main(String[] args) {
// 创建四个学生对象 存储到集合中
List<Student> list = new ArrayList<Student>();
list.add(new Student("rose", 18));
list.add(new Student("jack", 16));
list.add(new Student("abc", 20));
Collections.sort(list, new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o1.getAge() - o2.getAge(); // 以学生的年龄升序
}
});
list.forEach(System.out::println);
}
}
Student{name='jack', age=16}
Student{name='rose', age=18}
Student{name='abc', age=20}