每日一句:业精于勤,荒于嬉;行成于思,毁于随。
系列介绍
今天是Java集合基础的第三篇。
集合
1. Java 对 List 有几种排序方式?
Java 中对 List 排序主要有两种核心方式:
Collections.sort()方法:这是传统的排序方式,适用于任何实现了List接口的集合。底层使用 TimSort(一种改进的归并排序),保证稳定排序(相等元素的相对顺序不变)和 O(n log n) 的时间复杂度。例如:List<Integer> list = Arrays.asList(3, 1, 2); Collections.sort(list); // 自然排序 Collections.sort(list, Comparator.reverseOrder()); // 自定义排序List.sort()方法:从 Java 8 开始,List接口提供了默认的sort()方法,可以直接调用,同样使用 TimSort。这种方式更面向对象和灵活:list.sort(Comparator.naturalOrder());
此外,还可以使用 Stream API 进行排序(详见第4点),但这本质上是将 List 转换为流操作,而非直接 List 排序方式。
2. Comparable 和 Comparator 的区别?
Comparable接口(位于java.lang包)定义对象的自然排序顺序。类实现Comparable后必须重写compareTo()方法,允许对象直接比较。例如,String、Integer等内置类都实现了Comparable,用于默认排序。这是“内部比较器”。Comparator接口(位于java.util包)定义自定义排序顺序,是一个函数式接口,需实现compare()方法。它允许在不修改类代码的情况下定义多种排序规则。这是“外部比较器”,更灵活。例如:Comparator<String> byLength = (s1, s2) -> s1.length() - s2.length();
关键区别:
Comparable用于定义默认排序,而Comparator用于临时或多种排序。Comparable需要修改类本身,而Comparator可以在类外部实现。- 从设计模式看,
Comparable是策略模式的一种体现,而Comparator更符合开放-封闭原则。
3. Collections.sort() 和 Arrays.sort() 的区别?
Collections.sort()用于对List集合进行排序,底层使用 TimSort 算法(基于归并排序和插入排序),适用于对象集合,保证稳定排序。Arrays.sort()用于对数组进行排序,但根据数组类型不同采用不同算法:- 对于对象数组(如
Integer[]),使用 TimSort,保证稳定。 - 对于基本类型数组(如
int[]),使用双轴快速排序(Dual-Pivot Quicksort),这不保证稳定,但性能更好(因为基本类型无需考虑相等对象的顺序)。
性能考虑:Arrays.sort()对基本类型排序更快,而Collections.sort()对集合操作更安全。底层实现上,Collections.sort()最终会调用Arrays.sort()来处理底层数组。
- 对于对象数组(如
4. Java 8 中如何用 Stream API 进行排序?
Java 8 的 Stream API 提供了 sorted() 方法进行排序,有两种形式:
- 自然排序:要求元素实现
Comparable接口。List<String> list = Arrays.asList("banana", "apple", "cherry"); List<String> sortedList = list.stream().sorted().collect(Collectors.toList()); - 自定义排序:使用
Comparator参数。List<String> reverseSorted = list.stream().sorted(Comparator.reverseOrder()).collect(Collectors.toList());
对于复杂对象,可以使用 Comparator.comparing() 方法链:
List<Person> people = ...;
List<Person> sortedPeople = people.stream()
.sorted(Comparator.comparing(Person::getName).thenComparing(Person::getAge))
.collect(Collectors.toList());
Stream 排序是惰性的,需要终止操作(如 collect())来触发实际排序。它不会修改原集合,而是返回新集合。
5. TreeSet 和 TreeMap 的排序原理是什么?
- TreeSet 和 TreeMap 都是基于红黑树(Red-Black Tree)实现的,红黑树是一种自平衡的二叉搜索树(BST),通过颜色约束和旋转操作保持平衡,确保最坏情况下的操作时间复杂度为 O(log n)。
- 排序原理:
- 元素(或键)必须实现
Comparable接口,或者在构造时提供Comparator,用于比较和排序。 - 插入、删除和查找操作都按照红黑树的规则进行:比较后决定左/右子树,并通过旋转和变色维持平衡。
- 迭代时,中序遍历会返回有序序列(升序或降序)。
例如,TreeMap的put()方法会调用compare()或compareTo()来定位键的位置,并调整树结构。
- 元素(或键)必须实现
6. 如何实现自定义排序(如按多个字段排序)?
实现自定义排序通常使用 Comparator 接口,特别是通过方法引用和链式调用。对于多个字段排序,使用 thenComparing() 方法:
// 假设 Person 类有 name 和 age 字段
List<Person> people = Arrays.asList(
new Person("Alice", 30),
new Person("Bob", 25),
new Person("Alice", 20)
);
// 按 name 升序,然后按 age 降序
people.sort(Comparator.comparing(Person::getName)
.thenComparing(Person::getAge, Comparator.reverseOrder()));
// 或者使用 lambda
people.sort((p1, p2) -> {
int nameCompare = p1.getName().compareTo(p2.getName());
if (nameCompare != 0) return nameCompare;
return Integer.compare(p2.getAge(), p1.getAge()); // 降序
});
这种方式简洁且易读,体现了 Java 8 函数式编程的优势。
HashMap
7. 为什么 JDK 1.8 引入红黑树优化链表?
在 JDK 1.8 之前,HashMap 的每个桶(bucket)只使用链表处理哈希冲突。在最坏情况下(如大量键哈希到同一桶),链表会变得很长,导致查找性能降为 O(n)。JDK 1.8 引入了红黑树优化:当桶中元素数量超过阈值(默认为 8)时,链表转换为红黑树;当元素减少到另一阈值(默认为 6)时,转换回链表。
- 原因:红黑树保证了最坏情况下查找、插入和删除的时间复杂度为 O(log n),显著提升了性能,尤其是在哈希碰撞攻击场景下。
- 权衡:红黑树占用更多内存,且转换需要开销,因此只在必要时触发。这体现了工程上的平衡,兼顾了平均性能和最坏性能。
8. HashMap 的 key 可以是 null 吗?value 呢?
- Key:
HashMap允许一个null键。因为HashMap的hash()方法对null键返回 0,所以可以存储在第 0 个桶中。 - Value:
HashMap允许多个null值。因为值没有唯一性约束。
注意:Hashtable不允许null键或值,否则会抛出NullPointerException。这是HashMap和Hashtable的一个重要区别。
9. HashMap 的 hash() 方法是如何计算的?
在 JDK 1.8 中,HashMap 的 hash() 方法使用扰动函数来减少哈希冲突:
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
- 步骤:
- 计算键的
hashCode()(int 类型,32位)。 - 将哈希码右移 16 位(
h >>> 16),得到高16位。 - 将原哈希码与高16位进行异或操作(
^),这样混合了高低位信息,使哈希值更均匀。
- 计算键的
- 目的:减少哈希碰撞,因为
HashMap使用(n - 1) & hash计算索引(其中 n 是桶数量),异操作后低位更随机,分布更均匀。
10. HashMap 的 get() 方法的时间复杂度是多少?
- 平均情况:O(1)。假设哈希函数良好,键均匀分布,每个桶只有少量元素,直接通过索引访问。
- 最坏情况:如果所有键都哈希到同一个桶中,则:
- 在 JDK 1.8 之前(纯链表),时间复杂度为 O(n)。
- 在 JDK 1.8 之后(链表转红黑树),时间复杂度为 O(log n)。
因此,JDK 1.8 的优化显著改善了最坏情况性能。
1254

被折叠的 条评论
为什么被折叠?



