HashSet集合原理,Stream流

文章详细阐述了HashSet在JDK1.7和1.8版本的实现原理,1.7版本基于哈希表(数组+链表),1.8版本引入了红黑树。HashMap同样基于哈希表,依赖hashCode和equals保证键的唯一性。文章还介绍了Stream流的获取、中间操作(如filter,limit,skip,distinct)和终结操作(如forEach),以及重要的函数接口Predicate,Function,Consumer和Supplier。

HashSet:

1.7jdk版本

1,创建一个默认长度为16,默认加载因子为0.75的数组,数组名table(当数组存满到16*0.75=12时,就会自动扩充,每次扩容原先的两倍)。
2,根据元素的hash值跟数组的长度计算出应存入的位置。
3,判断当前位置是否为null,如果是null直接存入。
4,如果位置不为null,表示有元素,则调用equals方法比较属性值。
5,如果一样,则不存,如果不一样,则存入数组,老元素挂在新元素的下面。
所以,1.7版本的jdk,HashSet底层结构是哈希表(数组+链表)

1.8jdk版本

底层结构:哈希表(数组,链表,红黑树的结合体)。
当挂在下面的元素过多,那么不利于查询,所以在jdk8以后,当链表长度超过8的时候,自动转化为红黑树。
存储流程和1.7版本一样不变。
利用HashSet存储自定义元素,必须重写hashCode和equals方法

HashMap:

底层是哈希表结构
依赖hashCode方法和equals方法保证健的唯一。
如果键要存的是自定义对象,需要重写hashcode和equals方法。

Stream流

Stream流的三类方法:

获得Stream流:创建一条流水线,并把数据放到流水线上准备进行操作
中间方法:流水线上的操作。可以执行多个操作。
终结方法:一个Stream流只能有一个终结方法,是流上的最后一个操作。

Stream流的获得方法

单列集合

可以使用Collection接口中的默认方法stream()生成流
default Stream stream()

双列集合

间接的生成流
可以先通过keySet或者entrySet获得一个Set集合,在获得Stream流

数组

Arrays中的静态方法stream生成流

同种数据类型的多个数据

如:1,3,4,21,2和"a",“b”,“c” …
使用Stream.of(T…values)生成流

Stream流的常见中间操作方法

1,Stream filter(Predicate predicate):用于对流中的数据进行过滤
==Predicate(输入T类型,返回boolean类型)==接口中的方法
boolean test(T t):对给定的参数进行判断,返回一个Boolean值。

	List<Integer> list = new ArrayList<>();
       List<Integer> collect = list.stream().filter((n) -> { // n表示集合中的一个元素
           return n > 100;
       }).collect(Collectors.toList());//将集合过滤(过滤掉小于等于100的集合)

2,Stream limit(long maxSize):截取指定参数个数的数据

	List<Integer> list = new ArrayList<>();
       List<Integer> collect = list.stream().filter((n) -> {
           return n > 100;
       }).limit(5).collect(Collectors.toList());//过滤掉小于等于100的集合后截取前五个元素

3,Stream skip(long n):跳过指定参数个数的数据

	List<Integer> list = new ArrayList<>();
       List<Integer> collect = list.stream().filter((n) -> {
           return n > 100;
       }).skip(2).collect(Collectors.toList());//过滤掉小于等于100的集合后跳过前两个元素

4,Stream distinct():去除流中重复的元素,依赖(hashCode和equals方法)
5,static Stream contat(Stream a,Stream b):合并a和b两个流作为一个流

Stream流的常见终结操作方法

1,void forEach(Consumer action):对此流的每个元素执行操作
==consumer(输入T类型,没有返回的)==接口中的方法
void accept(T t):对给定的参数执行此操作

List<Integer> list = new ArrayList<>();
        list.stream().filter((n) -> {
            return n > 100;
        }).forEach(System.out::println);

java里面几个重要的函数接口

Predicate:T——》boolean(输入T类型,返回boolean类型)
Function:T——》R (输入为T类型,返回值为R类型)
Consumer:T——》void(输入T类型,没有返回的)
Supplier:None——》T(没有输入,返回T类型)

<think>好的,我现在需要帮助用户解决关于Java Stream中使用distinct方法进行去重的问题。首先,我要回顾一下用户提供的引用内容,确保自己理解正确。用户引用了四个资料,其中提到distinct()方法依赖hashCode和equals,以及如何通过自定义方法如distinctByKey来根据属性去重。 首先,我应该先解释distinct()的基本用法,并给出示例代码。这时候需要提到它基于对象的hashCode和equals方法,所以如果用户的自定义对象没有正确覆盖这些方法,distinct可能无法正常工作。例如,如果两个对象内容相同但hashCode不同,distinct就不会认为它们是重复的。 接下来,用户可能需要知道如何根据对象的某个属性去重,比如根据ID或者姓名。这时候需要引用资料中的distinctByKey方法,使用Predicate和ConcurrentHashMap来跟踪已经出现的属性值。这里要注意线程安全的问题,特别是并行的情况下,ConcurrentHashMap是必要的。 然后,用户可能想知道其他去重方法,比如使用filter结合HashSet,或者利用Collectors.toCollection来保持顺序。例如,通过TreeSet根据某个字段排序并去重,但需要注意这会改变元素的顺序。此外,还可以使用Collectors.groupingBy来分组并选择第一个元素,这也是一种去重方式。 需要确保示例代码正确,并且符合Java语法。比如在distinctByKey的例子中,要正确使用泛型,并展示如何在stream中调用。同时,提醒用户不同方法的适用场景,比如并行处理时使用线程安全的结构,或者需要保持顺序时使用LinkedHashSet。 用户的问题还可能涉及到性能方面的考虑。例如,distinct()在并行中的表现如何,或者自定义去重方法的效率。这时候需要结合引用中的资料,说明distinct()内部使用reduce,而自定义方法可能更灵活但需要维护额外的集合结构。 最后,生成的相关问题应该覆盖用户可能进一步询问的方向,比如如何处理对象属性去重、并行中的去重问题、或者去重后的排序需求。确保这些问题能够引导用户更深入地理解不同的去重方法及其应用场景。 总结一下,回答的结构应该是:先介绍基本用法,再讨论属性去重的自定义方法,然后提供其他替代方案,最后附上相关问题。确保每个部分都有对应的代码示例,并正确引用提供的资料,比如引用[2]和[4]关于hashCode和equals的重要性,以及引用[3]和[4]中的自定义distinctByKey方法。</think>在Java中,使用Stream的`distinct()`方法进行去重时,需要注意其实现原理和适用场景。以下是具体实现方法和示例: --- ### 一、`distinct()`基础用法 `distinct()`依赖对象的`hashCode()`和`equals()`方法判断重复性。 **示例:对简单类型去重** ```java List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 3, 3); List<Integer> distinctNumbers = numbers.stream() .distinct() .toList(); // 结果: [1, 2, 3] ``` **对象去重需重写`hashCode()`和`equals()`:** ```java class User { private Long id; private String name; // 必须重写hashCode和equals(根据id和name判断是否重复) @Override public boolean equals(Object o) { /*...*/ } @Override public int hashCode() { /*...*/ } } List<User> users = ...; List<User> distinctUsers = users.stream().distinct().toList(); ``` 若未重写这两个方法,`distinct()`会使用`Object`类的默认实现(基于内存地址比较),导致去重失败[^2]。 --- ### 二、根据属性去重的自定义方法 当需要按对象**特定属性**去重时,可用`distinctByKey`实现: ```java public static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) { Set<Object> seen = ConcurrentHashMap.newKeySet(); // 线程安全 return t -> seen.add(keyExtractor.apply(t)); // 若已存在则返回false,被filter过滤 } // 使用示例:按User的id去重 List<User> distinctUsersById = users.parallelStream() .filter(distinctByKey(User::getId)) .toList(); ``` 此方法通过`ConcurrentHashMap`保证并行的安全性[^3][^4]。 --- ### 三、其他去重方式 1. **`filter`+`HashSet`手动去重** ```java Set<String> seenNames = new HashSet<>(); List<User> distinctUsers = users.stream() .filter(u -> seenNames.add(u.getName())) .toList(); ``` 2. **`Collectors.toCollection`保留顺序** ```java List<User> distinctUsers = users.stream() .collect(Collectors.collectingAndThen( Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(User::getName))), ArrayList::new)); ``` --- ### 四、方法对比 | 方法 | 特点 | 适用场景 | |---------------------|-------------------------------|-----------------------| | `distinct()` | 依赖hashCode/equals | 简单类型或已重写方法的对象 | | `distinctByKey` | 按属性去重,支持并行 | 复杂对象按字段去重 | | `filter`+`HashSet` | 灵活但需手动管理集合 | 简单逻辑的去重 | | `Collectors`+排序 | 可排序但可能改变原顺序 | 需要排序后去重 | ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值