面试题答案
一键面试- 去重逻辑实现
- 要使用
Stream.distinct
方法对自定义类对象进行去重,需要在自定义类中正确重写equals
和hashCode
方法。 - 例如,假设有一个自定义类
Person
:
public class Person { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } // 重写equals方法 @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Person person = (Person) o; return age == person.age && Objects.equals(name, person.name); } // 重写hashCode方法 @Override public int hashCode() { return Objects.hash(name, age); } }
- 然后在使用
Stream
时,可以这样进行去重:
List<Person> personList = Arrays.asList( new Person("Alice", 25), new Person("Bob", 30), new Person("Alice", 25) ); List<Person> distinctList = personList.stream() .distinct() .collect(Collectors.toList());
- 要使用
- 性能优化思路
- 哈希算法调整:
- 在重写
hashCode
方法时,尽量保证哈希值的均匀分布。对于包含多个属性的自定义类,使用Objects.hash
这种综合多个属性生成哈希值的方法是比较好的选择。如果属性值的分布有一定规律,可以根据属性的特点手动调整哈希算法。例如,如果某个属性是时间戳,且时间戳的变化范围大且均匀,可以在哈希计算中给予该属性更高的权重,这样可以减少哈希冲突的概率。
- 在重写
- 缓存策略:
- 局部缓存:如果流中的元素具有一定的局部性,可以使用局部缓存。例如,如果流中的元素是按照某个属性分组后依次出现的,可以在处理每个分组时,使用一个小的缓存来存储已经处理过的元素。以
Person
类为例,如果流中的元素是按照年龄分组后依次出现的,可以在处理每个年龄组时,使用一个Set<Person>
作为缓存,在处理当前年龄组的元素时,先检查缓存中是否已经存在该元素,若存在则跳过,这样可以减少equals
和hashCode
方法的调用次数。 - 全局缓存:对于整个流处理过程,可以考虑使用全局缓存。如果流中的元素总量不是特别大,可以在处理流之前,先将所有元素放入一个
Set
中,然后再对流进行处理。这样在Stream.distinct
操作时,实际上是对已经去重的集合进行操作,从而提高性能。但这种方法需要额外的内存来存储全局缓存,所以适用于流元素总量较小的情况。
- 局部缓存:如果流中的元素具有一定的局部性,可以使用局部缓存。例如,如果流中的元素是按照某个属性分组后依次出现的,可以在处理每个分组时,使用一个小的缓存来存储已经处理过的元素。以
- 减少不必要计算:
- 如果在计算
hashCode
或equals
方法中有一些复杂的计算,且这些计算结果不会随对象状态变化而变化,可以将这些计算结果缓存起来。例如,如果Person
类中有一个属性是根据其他属性计算得到的复杂值,可以在对象创建时计算一次并缓存,在hashCode
和equals
方法中直接使用缓存值,避免重复计算。
- 如果在计算
- 哈希算法调整: