亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Java Stream的distinct去重原理分析

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

一、distinct 的基礎用法與核心特性

distinct()是 Stream API 中的有狀態(tài)中間操作,用于移除流中的重復元素,其底層依賴元素的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]

核心特性

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

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

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

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

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

源碼關鍵片段(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)) {  // 調用HashSet的add方法,返回false表示重復
        down.accept(t);
    }
}

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

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

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

并行處理示意圖

+----------------+     +----------------+     +----------------+
|  子任務1: HashSet |---->|  子任務2: HashSet |---->|  合并階段: HashMap |
|  存儲元素A,B,C   |     |  存儲元素B,D,E   |     |  最終結果A,B,C,D,E |
+----------------+     +----------------+     +----------------+

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

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

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

class User {
    private String id;
    private String name;
    
    @Override
    public int hashCode() {
        return Objects.hash(id);  // 僅用id計算哈希值
    }
    
    @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相同,會被去重
    new User("2", "Charlie")
);
List<User> uniqueUsers = users.stream()
    .distinct()
    .collect(Collectors.toList());  // 保留兩個用戶

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

若只重寫equals,會導致去重失效,因為HashSet首先通過hashCode判斷元素是否存在:

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

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

1. 性能損耗的主要原因

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

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

  • 預排序 + 相鄰去重:對有序流使用distinct()效率更高,因重復元素相鄰時哈希表查找次數(shù)減少
// 優(yōu)化前:無序流去重
List<Integer> randomData = getRandomNumbers(1000000);
randomData.stream().distinct().count();  // 全量哈希表查找
 
// 優(yōu)化后:先排序再去重
randomData.stream()
    .sorted()
    .distinct()
    .count();  // 相鄰重復元素只需一次比較
  • 使用 Primitive Stream 減少裝箱
// 低效:對象流裝箱
Stream<Integer> boxedStream = data.stream().distinct();
 
// 高效:IntStream直接操作
IntStream primitiveStream = data.stream().mapToInt(Integer::intValue).distinct();
  • 分塊處理大集合:避免一次性加載所有元素到內存
// 分塊去重示例
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()));
}
// 最后再去重一次合并結果
List<Integer> finalResult = result.stream().distinct().collect(Collectors.toList());

3. 并行流去重的參數(shù)調優(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);  // 啟用并行

五、特殊場景的去重方案

1. 基于部分屬性的去重

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

class Product {
    private String id;
    private String name;
    private double price;
    // 構造器、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. 去重并保留最新元素

在日志等場景中,需按時間戳去重并保留最新記錄:

class LogEntry {
    private String message;
    private long timestamp;
    // 構造器、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());

六、性能對比:distinct 與其他去重方式

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

總結

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

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

理解distinct()的底層實現(xiàn)原理,不僅能避免自定義對象去重時的常見錯誤,更能在處理大規(guī)模數(shù)據(jù)時選擇合適的優(yōu)化策略。記?。喝ブ夭僮鞯谋举|是空間與時間的權衡,根據(jù)具體業(yè)務場景(數(shù)據(jù)規(guī)模、有序性、精確性要求)選擇最優(yōu)方案,才能實現(xiàn)性能與功能的平衡。

以上就是Java Stream的distinct去重原理分析的詳細內容,更多關于Java Stream distinct去重的資料請關注腳本之家其它相關文章!

相關文章

  • Spring前后端跨域請求設置代碼實例

    Spring前后端跨域請求設置代碼實例

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

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

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

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

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

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

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

    Mybatis插件之自動生成不使用默認的駝峰式操作

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

    SWT(JFace) 體驗之FontRegistry

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

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

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

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

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

    IDEA中database使用教程

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

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

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

最新評論