目录
集合
1.概述
单列集合Collection,双列集合Map。
Collection是单列集合的祖宗,它的功能是全部单列集合都可以继承使用的,划分为List,Set两个系列,这三个是接口,实现两个系列的集合是实现类。
List系列:添加的元素是有序(存,取),可重复,有索引。
Set系列:添加的元素是无序,不重复,无索引。
2.Collection
2.1概述
java.util.Collection<E>
接口定义了操作一组对象(元素)的核心方法签名。它提供了查询集合大小的 int size()
方法(返回元素数量)和 boolean isEmpty()
方法(判断集合是否为空)。用于检查元素存在性的方法是 boolean contains(Object o)
,它依据元素的 equals()
方法判断指定对象 o
是否在集合中,添加的对象的类没重写Object的equals则不会判断属性值。修改集合的方法包括:boolean add(E e)
(尝试添加元素 e
,是可选操作,成功添加或集合因调用改变时返回 true
),boolean remove(Object o)
(移除元素 o
的单个实例,是可选操作,成功移除返回 true
),以及 void clear()
(移除所有元素,是可选操作)。
在批量操作方面,提供了 boolean containsAll(Collection<?> c)
(检查是否包含集合 c
的所有元素),boolean addAll(Collection<? extends E> c)
(添加集合 c
的所有元素,是可选操作,集合改变返回 true
),boolean removeAll(Collection<?> c)
(移除所有也存在于集合 c
中的元素,即求差集,是可选操作,集合改变返回 true
),和 boolean retainAll(Collection<?> c)
(仅保留也存在于集合 c
中的元素,即求交集,是可选操作,集合改变返回 true
)。
集合的相等性判断通过 boolean equals(Object o)
方法实现,而 int hashCode()
方法则返回集合的哈希码值(约定为所有元素哈希码之和)。集合与数组的转换通过 Object[] toArray()
(返回包含所有元素的新对象数组)和 <T> T[] toArray(T[] a)
(返回指定类型数组,包含所有元素)两个方法完成。
遍历集合元素的标准方式是使用 Iterator<E> iterator()
方法返回的迭代器进行安全遍历。此外,Java 8 及以后版本引入了几个默认方法:default Spliterator<E> spliterator()
(创建用于遍历和分割的 Spliterator
),default Stream<E> stream()
(返回顺序流),default Stream<E> parallelStream()
(返回可能并行的流),以及 default boolean removeIf(Predicate<? super E> filter)
(根据谓词条件移除元素,是可选操作,移除了元素返回 true
)。
2.2遍历方式
2.2.1迭代器遍历
迭代器在Java中的类Iterator,方法boolean hasNext(),判断当位置是否有元素。E next(),获取当前位置的元素,并将迭代器对象移到下一位。
Collection<String> coll = new ArrayList<>();
coll.add("aaa");
coll.add("bbb");
Iterator<String> it = coll.iterator();
while(it.hasNext()){
String str = it.next;
}
当循环结束之后再调用next因为会获取元素但没元素会异常报错NoSuchElementException,迭代器循环完之后不会复位,想要再一次循环需要重新创建新的,next一个循环中只调用一次,如果调用多次可能会导致取元素异常。
迭代器遍历时,不能用集合的方法增加或者删除,实在要删除可以使用迭代器的remove方法删除。
2.2.2增强for遍历
增强for的底层就是迭代器,为了简化迭代器的代码书写,所有的单列集合和数组才能使用增强for进行遍历。
for中的变量是一个记录的第三方变量,不会改变集合中原本的数据。
Collection<String> coll = new ArrayList<>();
coll.add("aaa");
coll.add("bbb");
coll.add("ccc");
coll.add("ddd");
//coll.for + \n
//s只是一个记录的第三方变量,不会改变集合中原本的数据
for (String s : coll) {
s = "ggg";
}
System.out.println(coll);
2.2.3lambda表达式遍历
forEach,依次遍历集合得到每一个元素然后返回给accept方法中的参数。
匿名内部类的方式:
Collection<String> coll = new ArrayList<>();
coll.add("aaa");
coll.add("bbb");
coll.add("ccc");
coll.add("ddd");
coll.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});
简化为lambda表达式:
Collection<String> coll = new ArrayList<>();
coll.add("aaa");
coll.add("bbb");
coll.add("ccc");
coll.add("ddd");
coll.forEach((String s)-> {
System.out.println(s);
}
);
继续简化:
Collection<String> coll = new ArrayList<>();
coll.add("aaa");
coll.add("bbb");
coll.add("ccc");
coll.add("ddd");
coll.forEach(s-> System.out.println(s));
3.List集合
3.1概述
有序,有索引,可重复,继承Collection。
add(int index, E e) 和数组一样添加元素到指定的位置,但不是修改,索引之后的元素向后移动,不使用index则直接添加在末尾。remove(Object o / int index) 可以额外通过索引删除元素,
这里会删除元素2,因为Object o需要包装类对象,这里1是默认int优先调用更接近的int index而不是包装类Integer。
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.remove(1);
3.2五种遍历方式
迭代器遍历,增强for遍历,lambda表达式遍历。
匿名内部类到lambda表达式:
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.forEach(new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
System.out.println(integer);
}
});
list.forEach(integer -> System.out.println(integer));
独有的普通fori循环,List内部类迭代器类对象遍历。
for循环:
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
列表迭代器遍历,ListIterator新的方法previous,hasPrevious和添加元素的方法add,注意对象是迭代器对象不是list。
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
ListIterator<Integer> it = list.listIterator();
while (it.hasNext()) {
Integer i = it.next();
if (i.equals(4)) {
it.add(5);
}
}
System.out.println(list);
4.线性数据结构
栈:后进先出,先进后出。
队列:从后端进入队列模型的过程为:入队列,从前端离开队列模型的过程为:出队列,先进先出,后进后出。
数组:内存中连续查询速度快,删除,添加效率低,后面的元素需要移动
链表:每一个节点都有自己独立的地址(对象),在内存中不连续,每一个节点对象包含数据值和下一个节点的地址(对象)。链表查询慢,增删快,首尾操作快。
5.ArrayList集合
ArrayList底层是数组结构的。
利用空参构造时,在底层创建一个默认长度为0的数组。
添加第一个元素时,底层会创建一个新的长度为10的数组,其他元素为null。
当存满时,会扩容1.5倍,扩容时会创建新的数组然后拷贝数据。
如果一次添加多个数据,1.5倍还放不下,则新数组的长度则扩容成添加后的实际大小。
Node是ArrayList的内部类。
6.LinkedList集合
LinkedList底层数据结构是双向链表。
添加第一个元素时头尾节点和新节点的变量名的值都是同一个地址(对象)。
7.迭代器
比如ArrayList,Iterator是其内部类,创建迭代器对象就是创建一个内部类对象,在ArrayList中hasNext就是判断当前索引是不是ArrayList的size,Next就是取值,索引+1。
成员变量modCount表示集合变化的次数,每使用集合的add和remove一次,这个变量都会自增,使用next的时候会检查这个值和最开始进行对比如果不同说明使用了集合中的方法。所以在迭代器中不要使用集合的增加和删除。
8.泛型
8.1泛型类,泛型方法,泛型接口
不给集合指定类型,默认认为所有的数据类型都是Object,此时可以添加任何类型的数据结构,但因为多态没法调用子类不是重写的特有方法,泛型使得集合数据的类型得到统一,把运行时期的问题提前到了编译期间。
Java中的泛型是伪泛型,编译时检测类型,但编译之后字节码是当作Object(类型擦除)。
当某一个类中中,某个变量的数据类型不确定时,就可以定义带有泛型的类。
泛型方法<E>写在public,static这些修饰符的后面。
public class ListUtil<E> {
private ListUtil(){}
public static<E> void addAll(ArrayList<E> list,E...e) {
for (E e1 : e) {
list.add(e1);
}
}
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
ListUtil.addAll(list,"111","ewr");
System.out.println(list);
}
}
泛型接口的两种使用方式:
1.实现类在实现接口时,给出接口的数据类型。
2.实现类延续泛型给出<E>,接口也使用<E>。
8.2泛型的继承和通配符
泛型具有不变性 Java 泛型是不变的:List<Dog>不是 List<Animal>的子类型,即使 Dog是 Animal的子类,methond(list2); 错误。methond(new ArrayList<>());正确,相当于ArrayList<Animal> list = new ArrayList<>(); 。list1.add(new Dog());正确相当于Animal e = new Dog();。
class Animal {}
class Dog extends Animal{}
class BigDog extends Dog{}
public class Main {
public static void methond(ArrayList<Animal> list) {}
public static void main(String[] args) {
ArrayList<Animal> list1 = new ArrayList<>();
ArrayList<Dog> list2 = new ArrayList<>();
ArrayList<BigDog> list3 = new ArrayList<>();
//错误
//Java 泛型是不变的:List<Dog>不是 List<Animal>的子类型,即使 Dog是 Animal的子类。
methond(list2);
//正确
//相当于ArrayList<Animal> list = new ArrayList<>();
methond(new ArrayList<>());
//正确
//相当于Animal e = new Dog();
list1.add(new Dog());
}
}
无界通配符 (?
):表示完全未知的类型。 只能从集合中读取元素(读取为 Object
),不能向集合中添加元素(除了 null
),因为你不知道实际类型是什么,添加任何非 null
元素都可能破坏类型安全。上界通配符 (? extends T
):表示未知类型,但该类型是 T
或其子类。用途 (Producer Extends): 当你需要从一个泛型结构中安全地读取元素,并且这些元素是某种类型或其子类时。非常适合作为数据的“生产者”。下界通配符 (? super T
):表示未知类型,但该类型是 T
或其父类(超类)。用途 (Consumer Super): 当你需要安全地向一个泛型结构中写入 T
类型的元素时。非常适合作为数据的“消费者”。
class Animal {}
class Dog extends Animal{}
class BigDog extends Dog{}
public class Main {
public static void methond1(ArrayList<? extends Animal> list) {}
public static void methond2(ArrayList<? super Animal> list) {}
public static void main(String[] args) {
ArrayList<Animal> list1 = new ArrayList<>();
ArrayList<Dog> list2 = new ArrayList<>();
ArrayList<BigDog> list3 = new ArrayList<>();
//正确
methond1(list3);
//错误
methond2(list2);
}
}
9.非线性数据结构Tree(树)
Node包含Data,父节点,左右子节点。
9.1二叉查找树
二叉查找(排序)树,每个节点最多连个子节点,任意节点的左子节点都小于当前节点,意节点的右子节点都大于当前节点。查找时间复杂度和树的层数相关,时间复杂度O()。
中序遍历,从最左边的左子树的左子节点开始,按左子树(左中右)中右子树顺序遍历,将一个小的包含左中右树遍历后开始遍历中。后序遍历左子树(左右中)右子树中,前序遍历中左子树(中左右)右子树(最最上层的根节点开始从最大的树向下遍历子树)同理。
中序遍历二叉查找树得到递增的数列。
层序遍历从第一层根节点向下层依层遍历。
9.2平衡二叉树
***任意*** 节点左右子树的高度不超过1,注意是任意但凡有一颗小的子树的左右子树不平衡就不是平衡二叉树。
二叉查找树的弊端如果根节点是递增数列的首尾数据,树的左右二叉树高度差会很高,使用平衡二叉树,添加节点的位置和二叉查找树一样左小右大。
9.2.1旋转机制
***添加节点不平衡后首先需要确定支点***,从添加的节点开始,不断的向上去寻找父节点直到找到一个不平衡的父节点,该节点当作支点,开始进行旋转。
左旋,将支点变为新的父节点,将原父节点变为支点的左子节点,并将支点的左字节点让给原父节点当作其右节点。
右旋同左旋。
9.2.2不平衡的四种情况
左左,根节点的高1层的左子树的左子树插入节点,一次右旋
左右,根节点的高1层的左子树的右子树插入节点,先一次局部左旋,再一次右旋
右右,根节点的高1层的右子树的右子树插入节点,一次左旋
右左,根节点的高1层的右子树的左子树插入节点,先一次局部右旋,再一次左旋
9.3红黑树
Node包含Data,父节点,左右子节点,颜色。颜色只能是红色和黑色。
9.3.1构造规则:
1.根节点是黑色的,叶节点是黑色的。
2.两个红色节点不能相连。
3.任意一个节点到所有后代叶节点的简单路径中黑色节点的个数相同。
9.3.2添加规则
添加新节点遵循以下逻辑:
新节点按照二叉查找树插入,颜色默认为红色
1.节点为根节点,着色为黑
2.节点非根节点
2.1节点的父节点为黑色,不操作
2.2节点的父节点为红色
2.2.1节点的叔叔节点为红色
将父节点和叔节点设为黑色
将祖父设为红色
将祖父设为当前节点重新开始第一步判断
2.2.2节点的叔叔节点为黑色(不存在时会默认添加了黑色根节点Nil)
2.2.2.1当前节点是父节点的右孩子
以父为支点进行左旋
将父节点当作当前节点重新开始第一步的判断
2.2.2.2当前节点是父节点的左孩子
将父节点设为黑色
将祖父设设为红色
以祖父为支点进行右旋
逻辑图如下:
下面的代码实现了构造红黑树和节点插入。
import java.util.ArrayList;
import java.util.List;
class RedBlackTree {
private static final boolean RED = true;
private static final boolean BLACK = false;
// ANSI 颜色代码
private static final String RED_COLOR = "\u001B[31m";
private static final String BLACK_COLOR = "\u001B[30m";
private static final String RESET_COLOR = "\u001B[0m";
private class Node {
int key;
Node left, right, parent;
boolean color;
Node(int key) {
this.key = key;
this.color = RED; // 新节点默认为红色
this.left = this.right = this.parent = null;
}
@Override
public String toString() {
if (color == RED) {
return RED_COLOR + key + "R" + RESET_COLOR;
} else {
return BLACK_COLOR + key + "B" + RESET_COLOR;
}
}
}
private Node root;
private final Node NIL; // 哨兵节点(叶子节点)
public RedBlackTree() {
NIL = new Node(0);
NIL.color = BLACK;
root = NIL;
}
// 插入操作
public void insert(int key) {
Node newNode = new Node(key);
newNode.left = NIL;
newNode.right = NIL;
Node parent = null;
Node current = root;
// 查找插入位置
while (current != NIL) {
parent = current;
if (key < current.key) {
current = current.left;
} else {
current = current.right;
}
}
newNode.parent = parent;
if (parent == null) {
root = newNode; // 树为空
} else if (key < parent.key) {
parent.left = newNode;
} else {
parent.right = newNode;
}
// 如果新节点是根节点,直接设为黑色
if (newNode.parent == null) {
newNode.color = BLACK;
return;
}
// 如果祖父节点不存在,不需要修复
if (newNode.parent.parent == null) {
return;
}
// 修复红黑树性质
fixInsert(newNode);
}
// 修复插入后的红黑树性质
private void fixInsert(Node node) {
Node parent = node.parent;
// 只有当父节点是红色时才需要修复
while (parent != null && parent.color == RED) {
Node grandparent = parent.parent;
if (parent == grandparent.left) {
Node uncle = grandparent.right;
// Case 1: 叔叔节点是红色
if (uncle.color == RED) {
parent.color = BLACK;
uncle.color = BLACK;
grandparent.color = RED;
node = grandparent;
parent = node.parent;
} else {
// Case 2: 节点是父节点的右孩子
if (node == parent.right) {
node = parent;
leftRotate(node);
parent = node.parent;
grandparent = parent.parent;
}
// Case 3: 节点是父节点的左孩子
parent.color = BLACK;
grandparent.color = RED;
rightRotate(grandparent);
}
} else {
Node uncle = grandparent.left;
// Case 1: 叔叔节点是红色
if (uncle.color == RED) {
parent.color = BLACK;
uncle.color = BLACK;
grandparent.color = RED;
node = grandparent;
parent = node.parent;
} else {
// Case 2: 节点是父节点的左孩子
if (node == parent.left) {
node = parent;
rightRotate(node);
parent = node.parent;
grandparent = parent.parent;
}
// Case 3: 节点是父节点的右孩子
parent.color = BLACK;
grandparent.color = RED;
leftRotate(grandparent);
}
}
// 如果node成为根节点,跳出循环
if (node == root) {
break;
}
}
// 确保根节点为黑色
root.color = BLACK;
}
// 左旋操作
private void leftRotate(Node x) {
Node y = x.right;
x.right = y.left;
if (y.left != NIL) {
y.left.parent = x;
}
y.parent = x.parent;
if (x.parent == null) {
root = y;
} else if (x == x.parent.left) {
x.parent.left = y;
} else {
x.parent.right = y;
}
y.left = x;
x.parent = y;
}
// 右旋操作
private void rightRotate(Node y) {
Node x = y.left;
y.left = x.right;
if (x.right != NIL) {
x.right.parent = y;
}
x.parent = y.parent;
if (y.parent == null) {
root = x;
} else if (y == y.parent.left) {
y.parent.left = x;
} else {
y.parent.right = x;
}
x.right = y;
y.parent = x;
}
// 改进的树形打印方法(使用传统连接线)
public void printTree() {
if (root == NIL) {
System.out.println("树为空");
return;
}
List<List<String>> lines = new ArrayList<>();
List<Node> level = new ArrayList<>();
List<Node> next = new ArrayList<>();
level.add(root);
int nn = 1;
int widest = 0;
while (nn != 0) {
List<String> line = new ArrayList<>();
nn = 0;
for (Node n : level) {
if (n == NIL) {
line.add(null);
next.add(NIL);
next.add(NIL);
} else {
String aa = n.toString();
// 计算原始字符串长度(不带颜色代码)
String plainText = n.key + (n.color == RED ? "R" : "B");
if (plainText.length() > widest) {
widest = plainText.length();
}
line.add(aa);
next.add(n.left);
next.add(n.right);
if (n.left != NIL) {
nn++;
}
if (n.right != NIL) {
nn++;
}
}
}
if (widest % 2 == 1) {
widest++;
}
lines.add(line);
List<Node> tmp = level;
level = next;
next = tmp;
next.clear();
}
int perpiece = lines.get(lines.size() - 1).size() * (widest + 4);
for (int i = 0; i < lines.size(); i++) {
List<String> line = lines.get(i);
int hpw = (int) Math.floor(perpiece / 2f) - 1;
// 打印连接线
if (i > 0) {
for (int j = 0; j < line.size(); j++) {
// 绘制节点间的连接线
char c = ' ';
if (j % 2 == 1) {
if (line.get(j - 1) != null && line.get(j) != null) {
// 左右节点都存在
c = '┴';
} else if (line.get(j - 1) != null) {
// 只有左节点
c = '┘';
} else if (line.get(j) != null) {
// 只有右节点
c = '└';
}
}
System.out.print(c);
// 绘制水平连接线
if (line.get(j) == null) {
for (int k = 0; k < perpiece - 1; k++) {
System.out.print(" ");
}
} else {
for (int k = 0; k < hpw; k++) {
System.out.print(j % 2 == 0 ? " " : "─");
}
System.out.print(j % 2 == 0 ? "┌" : "┐");
if(j % 2 == 0) {
for (int k = 0; k < hpw; k++) {
System.out.print("─");
}
} else {
for (int k = 0; k < hpw; k++) {
System.out.print(" ");
}
}
}
}
System.out.println();
}
// 打印节点
for (int j = 0; j < line.size(); j++) {
String f = line.get(j);
if (f == null) {
f = "";
}
// 计算原始字符串长度(不带颜色代码)
String plainText = f.replaceAll("\u001B\\[[;\\d]*m", "");
float gap = perpiece / 2f - plainText.length() / 2f;
int gap1 = (int) Math.ceil(gap);
int gap2 = (int) Math.floor(gap);
// 左侧填充
for (int k = 0; k < gap1; k++) {
System.out.print(" ");
}
System.out.print(f);
// 右侧填充
for (int k = 0; k < gap2; k++) {
System.out.print(" ");
}
}
System.out.println();
perpiece /= 2;
}
}
// 测试代码
public static void main(String[] args) {
RedBlackTree tree = new RedBlackTree();
System.out.println("创建红黑树并插入元素: 7, 3, 18, 10, 22, 8, 11, 26");
// 插入测试数据
int[] values = {7, 3, 18, 10, 22, 8, 11, 26};
for (int value : values) {
tree.insert(value);
}
// 打印树结构
System.out.println("\n红黑树结构:");
tree.printTree();
// 插入更多元素
System.out.println("\n插入元素: 15, 2, 6");
tree.insert(15);
tree.insert(2);
tree.insert(6);
System.out.println("\n更新后的红黑树结构:");
tree.printTree();
}
}
10.HashSet集合
Set系列集合,无序,不重复,无索引。
Set遍历方式,迭代器,增强for,lambda表达式,没有索引。
10.1哈希值
根据hashCode方法计算出来的int类型的整数,方法定义在Object类中,所有对象都可以调用,默认使用地址值进行计算,一般会重写hashCode方法,利用对象内部属性计算hash值。
没重写时,不同对象计算出来的hash值是不同的。如果已经重写了hashCode那么对象的属性值相同,计算出来的hash值也就相同。部分情况下,不同的属性值或地址值计算出来了相同的hash值,这就是哈希碰撞。
10.2HashSet底层原理
HashSet底层采取hash表存储数据。
HashSet,无序,不重复,无索引。
JDK8以前hash表组成:数组,链表。
JDK8后hash表组成:数组,链表,红黑树。
创建16长度,加载因为0.75的数组,数组名table,根据hash值根数组的长度计算出应存入的位置,判断当前位置是否为null,不是null则徐娅用equals比较这个位置里的元素们,不同则插入在后面,当数组曾在元素的位置为16 * 0.75时数组进行扩容为2倍,当位置上链表长度大于8而且数组长度大于等于64时改用红黑树存储。
如果集合中存储的是自定义对象,必须重写hashCode和equals方法。
10.3HashSet的特点
无序,由于hash遍历是按数组的顺序遍历的这样得到的第一个元素不一定是第一个添加的元素。
无索引,由于数组每个位置可能存储链表和红黑树,所以没有索引。
去重,由hashCode和equals保证。
11.LinkedHashSet集合
有序,不重复,无索引
有序实现,底层依然使用hash表存储,同时还增加一个双向链表,元素不仅要添加进hash表还要添加进双向链表用以记录位置。
12.TreeSet集合
可排序,不重复,无索引
TreeSet集合底层是基于红黑树的数据结构实现排序的,增删查改性能都好。
对于数值类型,默认按照从小到大的顺序进行排序。
12.1默认排序
对于自定义的对象,使用默认的接口implements Comparable<E>
compareTo中,this是当前节点,o是比较的节点,从红黑树的根节点开始比较,如果返回值为负则表示小放左边,为正表示大放右边然后按照红黑树规则插入,这样便使用红黑树完成了排序。
import java.util.TreeSet;
public class TreeSetCompare implements Comparable<TreeSetCompare>{
String name;
int age;
public static void main(String[] args) {
TreeSet<TreeSetCompare> set = new TreeSet<>();
TreeSetCompare treeSetCompare1 = new TreeSetCompare("lihua", 13);
TreeSetCompare treeSetCompare2 = new TreeSetCompare("lele", 15);
TreeSetCompare treeSetCompare3 = new TreeSetCompare("huahua", 17);
System.out.println("插入第1个元素");
set.add(treeSetCompare1);
System.out.println("插入第2个元素");
set.add(treeSetCompare2);
System.out.println("插入第3个元素");
set.add(treeSetCompare3);
System.out.println(set);
}
public TreeSetCompare(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 "TreeSetCompare{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public int compareTo(TreeSetCompare o) {
//按照年龄排序
System.out.println();
System.out.println(this.age);
System.out.println(o.age);
return this.getAge() - o.getAge();
}
}
12.2比较器排序
比较器比较,传递比较器Comparator指定规则。
默认使用默认比较,当默认比较不能满足需求时用比较器进行排序,使用TreeSet的Comparator构造方法,o1表示当前节点,o2表示要在红黑树中比较的节点。
public static void main(String[] args) {
TreeSet<String> ts = new TreeSet<>(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
int i = o1.length() - o2.length();
i = i == 0 ? o1.compareTo(o2) : i;
return i;
}
});
ts.add("dgdg");
ts.add("kk");
ts.add("jo");
ts.add("gjjjlsg");
System.out.println(ts);
}
自定义对象实现TreeSet的两种排序。
import java.util.Comparator;
import java.util.TreeSet;
public class People implements Comparable<People>{
String name;
int age;
int height;
int weight;
public People() {
}
public People(String name, int age, int height, int weight) {
this.name = name;
this.age = age;
this.height = height;
this.weight = weight;
}
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 int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
@Override
public String toString() {
return "People{" +
"name='" + name + '\'' +
", age=" + age +
", height=" + height +
", weight=" + weight +
'}';
}
public static void main(String[] args) {
People p1 = new People("kkdlfy", 18, 164, 57);
People p2 = new People("kkdlff", 17, 157, 54);
People p3 = new People("kkdlhg", 17, 177, 70);
People p4 = new People("kkyyyg", 17, 177, 65);
TreeSet<People> ts = new TreeSet<>();
TreeSet<People> ts1 = new TreeSet<>((o1, o2)-> {
int i = o1.age - o2.age;
i = i == 0 ? o1.height - o2.height : i;
i = i == 0 ? o1.weight - o2.weight : i;
return i;
});
ts1.add(p1);
ts1.add(p2);
ts1.add(p3);
System.out.println(ts1);
}
@Override
public int compareTo(People o) {
int i = this.age - o.age;
i = i == 0 ? this.height - o.height : i;
i = i == 0 ? this.weight - o.weight : i;
return i;
}
}