欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java Stream的distinct去重原理分析

 更新時(shí)間:2025年06月22日 11:32:37   作者:潛意識(shí)Java  
Java stream中的distinct方法用于去除流中的重復(fù)元素,它返回一個(gè)包含過濾后唯一元素的新流,該方法會(huì)根據(jù)元素的hashcode和equals方法來判斷是否為重復(fù)元素,本文給大家詳細(xì)分析了Java Stream的distinct去重原理,需要的朋友可以參考下

一、distinct 的基礎(chǔ)用法與核心特性

distinct()是 Stream API 中的有狀態(tài)中間操作,用于移除流中的重復(fù)元素,其底層依賴元素的hashCode()equals()方法。用法示例:

List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 4, 4);
List<Integer> unique = numbers.stream()
    .distinct()
    .collect(Collectors.toList());  // [1, 2, 3, 4]

核心特性

  • 去重邏輯基于元素的唯一性標(biāo)識(shí),而非內(nèi)存地址;
  • 保持元素首次出現(xiàn)的順序;
  • 屬于有狀態(tài)操作,處理過程中需維護(hù)已出現(xiàn)元素的集合。

二、distinct 的底層實(shí)現(xiàn)原理

1. 順序流中的去重實(shí)現(xiàn)

順序流中,distinct()通過HashSet存儲(chǔ)已處理元素,流程如下:

  • 遍歷流中的每個(gè)元素;
  • 對(duì)每個(gè)元素計(jì)算hashCode(),檢查HashSet中是否存在相同哈希值的元素;
  • 若存在,進(jìn)一步通過equals()比較內(nèi)容,相同則過濾;
  • 若不存在,將元素添加到HashSet并保留在流中。

源碼關(guān)鍵片段(JDK 17):

// ReferencePipeline.java
public final Stream<P_OUT> distinct() {
    return new DistinctOps<P_OUT, P_OUT>(this);
}
 
// DistinctOps.java
@Override
public void accept(P_OUT t) {
    if (set.add(t)) {  // 調(diào)用HashSet的add方法,返回false表示重復(fù)
        down.accept(t);
    }
}

2. 并行流中的去重優(yōu)化

并行流中,distinct()使用ConcurrentHashMap或分段處理提升性能:

  • 將流分割為多個(gè)子任務(wù),每個(gè)子任務(wù)維護(hù)獨(dú)立的HashSet
  • 子任務(wù)處理完成后,合并所有HashSet的結(jié)果;
  • 合并時(shí)使用HashMap去重,避免并發(fā)沖突。

并行處理示意圖

+----------------+     +----------------+     +----------------+
|  子任務(wù)1: HashSet |---->|  子任務(wù)2: HashSet |---->|  合并階段: HashMap |
|  存儲(chǔ)元素A,B,C   |     |  存儲(chǔ)元素B,D,E   |     |  最終結(jié)果A,B,C,D,E |
+----------------+     +----------------+     +----------------+

三、去重邏輯的核心依賴:hashCode 與 equals

1. 自定義對(duì)象的去重規(guī)則

若需對(duì)自定義對(duì)象去重,必須正確重寫hashCode()equals()

class User {
    private String id;
    private String name;
    
    @Override
    public int hashCode() {
        return Objects.hash(id);  // 僅用id計(jì)算哈希值
    }
    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return Objects.equals(id, user.id);  // 僅比較id
    }
    // 其他方法省略
}
 
// 使用示例
List<User> users = Arrays.asList(
    new User("1", "Alice"),
    new User("1", "Bob"),  // id相同,會(huì)被去重
    new User("2", "Charlie")
);
List<User> uniqueUsers = users.stream()
    .distinct()
    .collect(Collectors.toList());  // 保留兩個(gè)用戶

2. 常見誤區(qū):僅重寫 equals 不重寫 hashCode

若只重寫equals,會(huì)導(dǎo)致去重失效,因?yàn)?code>HashSet首先通過hashCode判斷元素是否存在:

class ErrorUser {
    private String id;
    // 錯(cuò)誤:未重寫hashCode
    @Override
    public boolean equals(Object o) {
        // 正確實(shí)現(xiàn)equals...
    }
}
// 使用distinct時(shí),兩個(gè)id相同的ErrorUser可能因hashCode不同被視為不同元素

四、distinct 的性能影響與優(yōu)化策略

1. 性能損耗的主要原因

  • 內(nèi)存占用:需存儲(chǔ)所有已出現(xiàn)元素,大數(shù)據(jù)集可能導(dǎo)致 OOM;
  • 哈希計(jì)算開銷:每個(gè)元素需計(jì)算hashCode并進(jìn)行哈希表查找;
  • 并行流的合并開銷:多線程環(huán)境下的集合合并操作耗時(shí)。

2. 大數(shù)據(jù)集的去重優(yōu)化

  • 預(yù)排序 + 相鄰去重:對(duì)有序流使用distinct()效率更高,因重復(fù)元素相鄰時(shí)哈希表查找次數(shù)減少
// 優(yōu)化前:無序流去重
List<Integer> randomData = getRandomNumbers(1000000);
randomData.stream().distinct().count();  // 全量哈希表查找
 
// 優(yōu)化后:先排序再去重
randomData.stream()
    .sorted()
    .distinct()
    .count();  // 相鄰重復(fù)元素只需一次比較
  • 使用 Primitive Stream 減少裝箱
// 低效:對(duì)象流裝箱
Stream<Integer> boxedStream = data.stream().distinct();
 
// 高效:IntStream直接操作
IntStream primitiveStream = data.stream().mapToInt(Integer::intValue).distinct();
  • 分塊處理大集合:避免一次性加載所有元素到內(nèi)存
// 分塊去重示例
int chunkSize = 100000;
List<Integer> result = new ArrayList<>();
for (int i = 0; i < data.size(); i += chunkSize) {
    int end = Math.min(i + chunkSize, data.size());
    List<Integer> chunk = data.subList(i, end);
    result.addAll(chunk.stream().distinct().collect(Collectors.toList()));
}
// 最后再去重一次合并結(jié)果
List<Integer> finalResult = result.stream().distinct().collect(Collectors.toList());

3. 并行流去重的參數(shù)調(diào)優(yōu)

通過自定義Spliterator控制分塊大小,減少合并開銷:

class EfficientSpliterator implements Spliterator<Integer> {
    private final List<Integer> list;
    private int index;
    private static final int CHUNK_SIZE = 10000;  // 分塊大小
    
    public EfficientSpliterator(List<Integer> list) {
        this.list = list;
        this.index = 0;
    }
    
    @Override
    public Spliterator<Integer> trySplit() {
        int size = list.size() - index;
        if (size < CHUNK_SIZE) return null;
        int splitPos = index + size / 2;
        Spliterator<Integer> spliterator = 
            new EfficientSpliterator(list.subList(index, splitPos));
        index = splitPos;
        return spliterator;
    }
    // 其他方法省略...
}
 
// 使用示例
List<Integer> data = ...;
Stream<Integer> optimizedStream = StreamSupport.stream(
    new EfficientSpliterator(data), true);  // 啟用并行

五、特殊場(chǎng)景的去重方案

1. 基于部分屬性的去重

若需根據(jù)對(duì)象的部分屬性去重(而非全部屬性),可結(jié)合mapcollect

class Product {
    private String id;
    private String name;
    private double price;
    // 構(gòu)造器、getter省略
}
 
// 按id去重
List<Product> uniqueProducts = products.stream()
    .collect(Collectors.collectingAndThen(
        Collectors.toMap(Product::getId, p -> p, (p1, p2) -> p1),
        map -> new ArrayList<>(map.values())
    ));

2. 去重并保留最新元素

在日志等場(chǎng)景中,需按時(shí)間戳去重并保留最新記錄:

class LogEntry {
    private String message;
    private long timestamp;
    // 構(gòu)造器、getter省略
}
 
List<LogEntry> latestLogs = logs.stream()
    .collect(Collectors.toMap(
        LogEntry::getMessage, 
        entry -> entry, 
        (oldEntry, newEntry) -> newEntry.getTimestamp() > oldEntry.getTimestamp() 
            ? newEntry : oldEntry
    ))
    .values()
    .stream()
    .collect(Collectors.toList());

3. 模糊去重(非精確匹配)

如需基于相似度去重(如字符串編輯距離),需自定義去重邏輯:

List<String> fuzzyUnique = strings.stream()
    .filter(s -> !strings.stream()
        .anyMatch(t -> s != t && levenshteinDistance(s, t) < 2))
    .collect(Collectors.toList());

六、性能對(duì)比:distinct 與其他去重方式

去重方式大數(shù)據(jù)集性能內(nèi)存占用實(shí)現(xiàn)復(fù)雜度適用場(chǎng)景
Stream.distinct()高(存儲(chǔ)所有元素)通用去重
先排序 + 相鄰去重有序數(shù)據(jù)去重
HashSet 直接去重簡(jiǎn)單集合去重
分塊去重超大數(shù)據(jù)集去重

總結(jié)

distinct()作為 Stream API 中的基礎(chǔ)操作,其核心去重邏輯依賴于hashCode()equals()的正確實(shí)現(xiàn),而性能優(yōu)化的關(guān)鍵在于:

  • 數(shù)據(jù)有序性利用:先排序再去重可減少哈希表查找次數(shù);
  • 內(nèi)存占用控制:對(duì)大數(shù)據(jù)集采用分塊處理,避免一次性存儲(chǔ)所有元素;
  • 基礎(chǔ)類型優(yōu)化:使用IntStream等避免裝箱損耗;
  • 并行處理調(diào)優(yōu):通過自定義Spliterator控制分塊大小,減少合并開銷。

理解distinct()的底層實(shí)現(xiàn)原理,不僅能避免自定義對(duì)象去重時(shí)的常見錯(cuò)誤,更能在處理大規(guī)模數(shù)據(jù)時(shí)選擇合適的優(yōu)化策略。記住:去重操作的本質(zhì)是空間與時(shí)間的權(quán)衡,根據(jù)具體業(yè)務(wù)場(chǎng)景(數(shù)據(jù)規(guī)模、有序性、精確性要求)選擇最優(yōu)方案,才能實(shí)現(xiàn)性能與功能的平衡。

以上就是Java Stream的distinct去重原理分析的詳細(xì)內(nèi)容,更多關(guān)于Java Stream distinct去重的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Spring前后端跨域請(qǐng)求設(shè)置代碼實(shí)例

    Spring前后端跨域請(qǐng)求設(shè)置代碼實(shí)例

    這篇文章主要介紹了Spring前后端跨域請(qǐng)求設(shè)置代碼實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-07-07
  • 詳解Java是如何通過接口來創(chuàng)建代理并進(jìn)行http請(qǐng)求

    詳解Java是如何通過接口來創(chuàng)建代理并進(jìn)行http請(qǐng)求

    今天給大家?guī)淼闹R(shí)是關(guān)于Java的,文章圍繞Java是如何通過接口來創(chuàng)建代理并進(jìn)行http請(qǐng)求展開,文中有非常詳細(xì)的介紹及代碼示例,需要的朋友可以參考下
    2021-06-06
  • Java中List add添加不同類型元素的講解

    Java中List add添加不同類型元素的講解

    今天小編就為大家分享一篇關(guān)于java的List add不同類型的對(duì)象,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧
    2019-03-03
  • java8 toMap問題(key重復(fù)如何解決)

    java8 toMap問題(key重復(fù)如何解決)

    這篇文章主要介紹了java8 toMap問題(key重復(fù)如何解決),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-05-05
  • Mybatis插件之自動(dòng)生成不使用默認(rèn)的駝峰式操作

    Mybatis插件之自動(dòng)生成不使用默認(rèn)的駝峰式操作

    這篇文章主要介紹了Mybatis插件之自動(dòng)生成不使用默認(rèn)的駝峰式操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2020-11-11
  • SWT(JFace) 體驗(yàn)之FontRegistry

    SWT(JFace) 體驗(yàn)之FontRegistry

    測(cè)試代碼如下:
    2009-06-06
  • ssm實(shí)現(xiàn)分頁查詢的實(shí)例

    ssm實(shí)現(xiàn)分頁查詢的實(shí)例

    下面小編就為大家?guī)硪黄猻sm實(shí)現(xiàn)分頁查詢的實(shí)例。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-11-11
  • java判斷字符串是否為null的四種方式匯總

    java判斷字符串是否為null的四種方式匯總

    這篇文章主要介紹了java判斷字符串是否為null的四種方式匯總,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-01-01
  • IDEA中database使用教程

    IDEA中database使用教程

    idea集成了一個(gè)數(shù)據(jù)庫管理工具,可以可視化管理很多種類的數(shù)據(jù)庫,本文主要介紹了IDEA中database使用教程,具有一定的參考價(jià)值,感興趣的可以了解一下
    2023-06-06
  • java isInterrupted()判斷線程的實(shí)例講解

    java isInterrupted()判斷線程的實(shí)例講解

    在本篇內(nèi)容里小編給大家分享的是一篇關(guān)于java isInterrupted()判斷線程的實(shí)例講解內(nèi)容,有興趣的朋友們可以學(xué)習(xí)下。
    2021-05-05

最新評(píng)論