最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java數(shù)組去重的20種實(shí)現(xiàn)方式大全

 更新時(shí)間:2026年05月05日 08:32:29   作者:刀法如飛  
數(shù)組與列表去重是最常見(jiàn)的算法,看似簡(jiǎn)單,但不同實(shí)現(xiàn)方式的性能差異可能高達(dá)幾百倍,本文整理Java數(shù)組去重的20種寫(xiě)法,按5個(gè)策略分類,幫你理解每類的核心思路,需要的朋友可以參考下

數(shù)組與列表去重是最常見(jiàn)的算法??此坪?jiǎn)單,但不同實(shí)現(xiàn)方式的性能差異可能高達(dá)幾百倍。整理Java數(shù)組去重的20種寫(xiě)法,按5個(gè)策略分類,幫你理解每類的核心思路。AI時(shí)代,可以不寫(xiě)代碼,但需要理解不同解決問(wèn)題的方式。

為什么性能差異這么大?

最簡(jiǎn)單的寫(xiě)法,新建數(shù)組,然后把不存在的添加進(jìn)來(lái)。

static Integer[] unique(Integer[] arr) {
    List<Integer> result = new ArrayList<>();
    for (Integer item : arr) {
        // ArrayList.contains 是 O(n) 線性掃描,但每次掃描則是 O(n2)
        if (!result.contains(item)) {
            result.add(item);
        }
    }
    return result.toArray(new Integer[0]);
}

問(wèn)題在于每次 contains 都要全量掃一遍 result,復(fù)雜度是 O(n²)。

優(yōu)化思路:用更快的方式判重

  • 集合結(jié)構(gòu) O(1) 查詢:HashSet、LinkedHashSet
  • 排序 O(nlogn):相同元素相鄰后去重
  • 流式APIstream().distinct()
  • 位圖BitSet 對(duì)非負(fù)整數(shù)極其高效
  • 遞歸:換種表達(dá)方式,本質(zhì)仍是上面的思路

下面具體來(lái)分析5大類,20種不同的寫(xiě)法

第1類:基礎(chǔ)循環(huán)(方法1-6)

策略原理:不依賴任何集合工具類,純靠數(shù)組下標(biāo)、嵌套循環(huán)、indexOf 這種"原始"手段來(lái)完成去重。每一步判斷都是 O(n),整體復(fù)雜度是 O(n²)。

適用場(chǎng)景:教學(xué)、面試手撕、嵌入式或受限環(huán)境,生產(chǎn)代碼不建議使用。

// 方法1:雙循環(huán)索引比較——當(dāng)前項(xiàng)跟左側(cè)逐個(gè)比較
static int[] unique1(int[] arr) {
    int[] newArr = new int[arr.length];
    int x = 0;
    // 當(dāng)前項(xiàng)跟左側(cè)全量比較,是否首次出現(xiàn)
    for (int i = 0; i < arr.length; i++) {
        for (int j = 0; j <= i; j++) {
          // i 與左側(cè)每個(gè) j 比對(duì)
            if (arr[i] == arr[j]) {
                // 值和位置均相同,表示前面沒(méi)有相同值,是首次出現(xiàn)
                if (i == j) {
                    newArr[x++] = arr[i];
                }
                break;
            }
        }
    }
    return Arrays.copyOf(newArr, x);
}

// 方法2:List.indexOf 索引法,跟第一種原理一致
// indexOf 返回首次出現(xiàn)的下標(biāo),等于當(dāng)前下標(biāo)即首次出現(xiàn)
static Integer[] unique2(Integer[] arr) {
    List<Integer> list = new ArrayList<>(Arrays.asList(arr));
    List<Integer> result = new ArrayList<>();
    for (int i = 0; i < list.size(); i++) {
        // indexOf得到下標(biāo),比較是否跟當(dāng)前下標(biāo)一致,如果一致則表示首次出現(xiàn)
        if (list.indexOf(list.get(i)) == i) {
            result.add(list.get(i));
        }
    }
    return result.toArray(new Integer[0]);
}

// 方法3:從后往前原地刪除
// 倒序遍歷,與左側(cè)任意相同則刪除自身
// 倒序刪除的好處:刪除點(diǎn)之后的元素都已處理,不會(huì)影響下標(biāo)
static Integer[] unique3(Integer[] arr) {
    List<Integer> list = new ArrayList<>(Arrays.asList(arr));
    int l = list.size();
    // 自后往前遍歷原數(shù)組
    while (l-- > 0) {
        int i = l;
        // 當(dāng)前項(xiàng)與左側(cè)逐個(gè)比較是否存在重復(fù)項(xiàng),若存在則刪除自身
        while (i-- > 0) {
            if (list.get(l).equals(list.get(i))) {
                list.remove(l);
                break;
            }
        }
    }
    return list.toArray(new Integer[0]);
}

// 方法4:從前往后原地刪除(刪除后面相同項(xiàng)),與方法3相同
static Integer[] unique4(Integer[] arr) {
    List<Integer> list = new ArrayList<>(Arrays.asList(arr));
    int l = list.size();
    // 自前往后遍歷原數(shù)組
    for (int i = 0; i < l; i++) {
        // 當(dāng)前項(xiàng)與右側(cè)全部項(xiàng)逐個(gè)比較,若有相同則刪除相同項(xiàng)
        for (int j = i + 1; j < l; j++) {
            if (list.get(i).equals(list.get(j))) {
                list.remove(j);
                // 刪除后 j 位置的元素是原來(lái)的 j+1,j 不前進(jìn)
                // l 同步減一
                j--;
                l--;
            }
        }
    }
    return list.toArray(new Integer[0]);
}

// 方法5:Iterator 遍歷,原理與方法1同
// 用 Iterator 風(fēng)格遍歷,結(jié)果列表用 contains 判重
static Integer[] unique5(Integer[] arr) {
    List<Integer> source = Arrays.asList(arr);
    List<Integer> result = new ArrayList<>();
    Iterator<Integer> it = source.iterator();
    // 逐個(gè)迭代,判斷是否包含在新數(shù)組中
    while (it.hasNext()) {
        Integer item = it.next();
        // ArrayList.contains 是 O(n),整體仍是 O(n2)
        if (!result.contains(item)) {
            result.add(item);
        }
    }
    return result.toArray(new Integer[0]);
}

// 方法6:從右往左跳過(guò)重復(fù),不刪除(使用break跳出)
// 倒序掃描,若當(dāng)前元素在左側(cè)已存在則跳過(guò),否則保留
static Integer[] unique6(Integer[] arr) {
    int len = arr.length;
    Integer[] result = new Integer[len];
    int x = len;
    // 自后往前遍歷數(shù)組
    for (int i = len - 1; i >= 0; i--) {
        boolean duplicate = false;
        // 檢查左側(cè)是否有相同元素
        for (int j = i - 1; j >= 0; j--) {
            if (arr[i].equals(arr[j])) {
                duplicate = true;
                break;   // 找到重復(fù)立即退出內(nèi)層循環(huán)
            }
        }
        // 沒(méi)有重復(fù)則保留當(dāng)前元素,倒序填入結(jié)果
        if (!duplicate) {
            result[--x] = arr[i];
        }
    }
    return Arrays.copyOfRange(result, x, len);
}

第2類:集合容器(方法7-11)

策略原理:Java 集合框架本身就提供了"鍵唯一"的語(yǔ)義。把數(shù)據(jù)塞進(jìn) Set 或 Map,去重就自然完成。

  • HashSet / HashMap:哈希結(jié)構(gòu),O(1) 判重,結(jié)果無(wú)序
  • LinkedHashSet:哈希 + 雙向鏈表,保留插入順序
  • TreeSet:紅黑樹(shù),O(logn),自動(dòng)排序
  • LinkedHashMap:保序的 Map,能在去重時(shí)攜帶額外信息

代價(jià)是元素必須正確實(shí)現(xiàn) hashCodeequals?;绢愋偷陌b類、String 都已經(jīng)實(shí)現(xiàn)好了,自定義對(duì)象需要自己重寫(xiě)。

適用場(chǎng)景:日常項(xiàng)目首選。需要保序選 LinkedHashSet,需要排序選 TreeSet,需要鍵值對(duì)選 LinkedHashMap

// 方法7:HashSet——最快,但結(jié)果無(wú)序
static Integer[] unique7(Integer[] arr) {
    // HashSet 自動(dòng)去重,但底層哈希散列后順序不可預(yù)測(cè)
    Set<Integer> set = new HashSet<>(Arrays.asList(arr));
    return set.toArray(new Integer[0]);
}

// 方法8:LinkedHashSet——保序,工程首選
static Integer[] unique8(Integer[] arr) {
    // LinkedHashSet 內(nèi)部用雙向鏈表維護(hù)插入順序
    Set<Integer> set = new LinkedHashSet<>(Arrays.asList(arr));
    return set.toArray(new Integer[0]);
}

// 方法9:TreeSet——自動(dòng)排序
static Integer[] unique9(Integer[] arr) {
    // TreeSet 基于紅黑樹(shù),插入即有序(默認(rèn)升序)
    // 需要降序就用 .descendingSet() 或自定義 Comparator
    Set<Integer> set = new TreeSet<>(Arrays.asList(arr));
    return set.toArray(new Integer[0]);
}

// 方法10:HashMap 顯式判重
// 等價(jià)于方法7,但更便于擴(kuò)展(值可以放統(tǒng)計(jì)信息、首次位置等)
static Integer[] unique10(Integer[] arr) {
    Map<Integer, Integer> map = new HashMap<>();
    List<Integer> result = new ArrayList<>();
    for (Integer item : arr) {
        // putIfAbsent 內(nèi)部判 containsKey + put,等價(jià)但更緊湊
        if (map.putIfAbsent(item, item) == null) {
            result.add(item);
        }
    }
    return result.toArray(new Integer[0]);
}

// 方法11:LinkedHashMap——保序的 Map
// 適合"按某字段去重并攜帶其他信息"的場(chǎng)景
static Integer[] unique11(Integer[] arr) {
    // 用 LinkedHashMap 保留插入順序,鍵去重,值可攜帶統(tǒng)計(jì)
    Map<Integer, Integer> map = new LinkedHashMap<>();
    for (Integer item : arr) {
        // merge:鍵不存在則放 1,存在則累加(這里相當(dāng)于做了頻次統(tǒng)計(jì))
        map.merge(item, 1, Integer::sum);
    }
    return map.keySet().toArray(new Integer[0]);
}

第3類:排序后去重(方法12-14)

策略原理:先 sort 讓相同元素相鄰,再掃一遍刪除相鄰相同項(xiàng)。復(fù)雜度由排序決定,O(nlogn)。優(yōu)點(diǎn)是不需要額外的哈希結(jié)構(gòu),"相鄰判等"是最便宜的判重方式;缺點(diǎn)是會(huì)破壞原順序,且要求元素可比較(實(shí)現(xiàn) Comparable 或提供 Comparator)。

適用場(chǎng)景:輸出本就需要排序、不在意原順序、內(nèi)存敏感(不想再開(kāi)一個(gè) Set)。

// 方法12:排序后從后往前刪
static Integer[] unique12(Integer[] arr) {
    List<Integer> list = new ArrayList<>(Arrays.asList(arr));
    Collections.sort(list); // 升序,相同元素聚到一起
    // 倒序掃描,自后往前:相鄰兩項(xiàng)相同就刪掉后一項(xiàng)
    for (int l = list.size() - 1; l > 0; l--) {
        if (list.get(l).equals(list.get(l - 1))) {
            list.remove(l);
        }
    }
    return list.toArray(new Integer[0]);
}

// 方法13:排序后從前往后刪
static Integer[] unique13(Integer[] arr) {
    List<Integer> list = new ArrayList<>(Arrays.asList(arr));
    Collections.sort(list, Collections.reverseOrder()); // 降序
    int l = list.size() - 1;
    int i = 0;
    while (i < l) {
        if (list.get(i).equals(list.get(i + 1))) {
            list.remove(i);
            // 刪完不前進(jìn),長(zhǎng)度同步減一
            i--;
            l--;
        }
        i++;
    }
    return list.toArray(new Integer[0]);
}

// 方法14:排序 + 雙指針(類似LeetCode原題)
// 先排序,再原地去重(修改原數(shù)組),最后返回去重后的新數(shù)組
static int[] unique14(int[] arr) {
    if (arr.length == 0) return arr;
    Arrays.sort(arr);
    int slow = 0; // 慢指針:指向去重后區(qū)間的末尾(初始為第一個(gè))
    for (int fast = 1; fast < arr.length; fast++) {
        // 當(dāng)前項(xiàng)與去重區(qū)間最后一個(gè)元素不同,說(shuō)明有新的值
        if (arr[fast] != arr[slow]) {
            // 將慢指針右移一步,并將新值寫(xiě)入到去重區(qū)間末尾
            arr[++slow] = arr[fast];
        }
    }
    // [0, slow] 是去重后的結(jié)果
    return Arrays.copyOf(arr, slow + 1);
}

第4類:Stream 與函數(shù)式(方法15-17)

策略原理:Java 8 引入的 Stream API 把"管道 + 操作"形式化。distinct() 內(nèi)部用 LinkedHashSet 實(shí)現(xiàn)去重;Collectors.toMap 提供按業(yè)務(wù)鍵去重的便捷工具;reduce 則可以把累加過(guò)程顯式表達(dá)出來(lái)。

適用場(chǎng)景:現(xiàn)代 Java 工程的常態(tài)寫(xiě)法??勺x性高、鏈?zhǔn)浇M合方便,特別適合"先過(guò)濾再去重再排序再收集"的多步處理。

// 方法15:基于 Stream.distinct() 一行去重
static Integer[] unique15(Integer[] arr) {
    // distinct 內(nèi)部基于 LinkedHashSet,保序、O(n)、寫(xiě)法最短
    return Arrays.stream(arr)
            .distinct()
            .toArray(Integer[]::new);
}

// 方法16:通過(guò) Stream.filter + 外部 Set
// 演示如何在函數(shù)式管道里攜帶"已出現(xiàn)"的狀態(tài)
static Integer[] unique16(Integer[] arr) {
    Set<Integer> seen = new HashSet<>();
    // filter 謂詞帶副作用:seen.add 返回 true 表示首次加入
    // 整個(gè)表達(dá)式語(yǔ)義:僅保留首次見(jiàn)到的元素
    return Arrays.stream(arr)
            .filter(seen::add)
            .toArray(Integer[]::new);
}

// 方法17:通過(guò) Collectors.toMap 按業(yè)務(wù)鍵去重
// 適合自定義對(duì)象按某字段去重的場(chǎng)景
static Integer[] unique17(Integer[] arr) {
    // 第三個(gè)參數(shù)是沖突合并函數(shù):(existing, replacement) -> existing 保留首項(xiàng)
    // 用 LinkedHashMap::new 作為 Map 工廠,保留插入順序
    Map<Integer, Integer> map = Arrays.stream(arr).collect(
            Collectors.toMap(
                    item -> item,                  // keyMapper:用值本身做鍵
                    item -> item,                  // valueMapper
                    (existing, replacement) -> existing,
                    LinkedHashMap::new
            )
    );
    return map.values().toArray(new Integer[0]);
}

第5類:遞歸與位圖(方法18-20)

策略原理:遞歸用自調(diào)用替代循環(huán),是函數(shù)式思維的體現(xiàn),主要用于教學(xué)與算法練習(xí);位圖(BitSet)用每一位標(biāo)記一個(gè)非負(fù)整數(shù)是否出現(xiàn)過(guò),對(duì)整數(shù)集合有極致的空間效率(10 億個(gè) int 只要 128MB),是大數(shù)據(jù)去重的常見(jiàn)選型。

適用場(chǎng)景:遞歸教學(xué)、算法刷題;BitSet——大規(guī)模非負(fù)整數(shù)(如用戶 ID、訂單號(hào))的去重統(tǒng)計(jì)。

// 方法18:遞歸--從后往前構(gòu)建去重列表,跟方法1類似,也是新建數(shù)組獲得不重復(fù)項(xiàng)
// 遞歸檢查末尾元素是否在前面出現(xiàn)過(guò),若不重復(fù)則插入到結(jié)果列表頭部,最終保持原順序
static Integer[] uniqueRecursive(Integer[] arr, int length, List<Integer> result) {
    // 遞歸終止:只剩一項(xiàng),直接收尾
    if (length <= 1) {
        if (length == 1) result.add(0, arr[0]);
        return result.toArray(new Integer[0]);
    }

    int last = length - 1;
    Integer lastItem = arr[last];
    boolean isRepeat = false;
    // 在 [0, last) 區(qū)間找是否已出現(xiàn)過(guò) lastItem
    for (int i = last - 1; i >= 0; i--) {
        if (lastItem.equals(arr[i])) {
            isRepeat = true;
            break;
        }
    }
    // 不重復(fù)則把 lastItem 加入結(jié)果(往前插入以保持原順序)
    if (!isRepeat) {
        result.add(0, lastItem);
    }
    return uniqueRecursive(arr, length - 1, result);
}

// 方法19:遞歸--拼接返回(純函數(shù)式,不修改原數(shù)組和外部集合)
// 核心思路:先遞歸處理前 n-1 個(gè)元素得到去重結(jié)果,再判斷第 n 個(gè)元素是否在它前面出現(xiàn)過(guò)。
//         若未出現(xiàn)過(guò),則追加到結(jié)果末尾;否則直接返回。最終保持原數(shù)組的首次出現(xiàn)順序。
static List<Integer> uniqueRecursiveConcat(List<Integer> list, int length) {
    if (length <= 1) {
        // 長(zhǎng)度≤1時(shí),直接返回原列表的前 length 個(gè)元素(避免后續(xù)越界)
        return new ArrayList<>(list.subList(0, length));
    }

    int last = length - 1;
    Integer lastItem = list.get(last);
    boolean isRepeat = false;
    // 檢查當(dāng)前項(xiàng) lastItem 是否在它前面的子數(shù)組中出現(xiàn)過(guò)
    for (int i = last - 1; i >= 0; i--) {
        if (lastItem.equals(list.get(i))) {
            isRepeat = true;
            break;
        }
    }

    // 遞歸深入:此時(shí)尚未得到前 length-1 項(xiàng)的去重結(jié)果,暫停當(dāng)前層,進(jìn)入子問(wèn)題求解
    List<Integer> head = uniqueRecursiveConcat(list, length - 1);
    // 遞歸回溯:子問(wèn)題已返回結(jié)果(head 中已包含前 length-1 項(xiàng)的去重結(jié)果,且保序)
  
    // 當(dāng)前項(xiàng)不重復(fù)則追加到結(jié)果末尾(保持原順序)
    if (!isRepeat) {
        head.add(lastItem);
    }
    return head;
}

// 方法20:BitSet 位圖法(僅適用于非負(fù)整數(shù))
// BitSet 是用一個(gè) long[] 數(shù)組來(lái)模擬一個(gè)無(wú)限長(zhǎng)的位序列。(0 表示 false/未出現(xiàn),1 表示 true/已出現(xiàn))
// 每個(gè) int 用一位標(biāo)記是否出現(xiàn)過(guò),10 億規(guī)模也只要 ~128MB
static int[] uniqueBitSet(int[] arr) {
    BitSet bitSet = new BitSet();
    int[] result = new int[arr.length];
    int x = 0;
    for (int item : arr) {
        // 注意 BitSet 的下標(biāo)必須非負(fù),負(fù)數(shù)需要先做偏移
        if (item < 0) {
            throw new IllegalArgumentException("BitSet 不支持負(fù)數(shù),需要先偏移");
        }
        // 第 item 位為 0 表示首次出現(xiàn)
        if (!bitSet.get(item)) {
            bitSet.set(item); // 標(biāo)記為已出現(xiàn)
            result[x++] = item;
        }
    }
    return Arrays.copyOf(result, x);
}

這么多寫(xiě)法該如何選擇?

類別時(shí)間復(fù)雜度是否保序主要場(chǎng)景
基礎(chǔ)循環(huán)O(n²)學(xué)習(xí)算法、理解編程
集合容器O(n)看具體類日常項(xiàng)目首選
排序后去重O(nlogn)否(變排序)順便要排序
StreamO(n)是(distinct)現(xiàn)代 Java 寫(xiě)法
遞歸 / 位圖視實(shí)現(xiàn)看實(shí)現(xiàn)教學(xué) / 海量整數(shù)

性能實(shí)測(cè)

10 萬(wàn)個(gè)隨機(jī)整數(shù)(約 5 萬(wàn)不重復(fù)):

方法耗時(shí)
HashSet8 ms
LinkedHashSet10 ms
stream().distinct()12 ms
BitSet4 ms
排序+相鄰去重35 ms
雙循環(huán)1500 ms
List.contains 循環(huán)1400 ms

100 萬(wàn)個(gè)數(shù)據(jù)時(shí)差距進(jìn)一步拉開(kāi):

方法耗時(shí)相對(duì) HashSet
HashSet80 ms
LinkedHashSet110 ms1.4×
BitSet30 ms0.4×
雙循環(huán)估算 150 秒約 1900×

數(shù)據(jù)從 10 萬(wàn)到 100 萬(wàn):O(n) 方法慢 10 倍左右,O(n²) 方法慢 100 倍以上。算法選擇的影響在數(shù)據(jù)變大時(shí)呈非線性放大。

實(shí)際項(xiàng)目里怎么選

絕大多數(shù)情況一行就夠,你不需要記住具體的寫(xiě)法,但你需要告訴AI該怎么選:

// 保序、O(n)、寫(xiě)法最短,工程首選
List<Integer> result = list.stream().distinct().collect(Collectors.toList());

// 或顯式用 LinkedHashSet,語(yǔ)義更清晰
List<Integer> result = new ArrayList<>(new LinkedHashSet<>(list));

不在意順序:

List<Integer> result = new ArrayList<>(new HashSet<>(list));

需要排序:

List<Integer> result = new ArrayList<>(new TreeSet<>(list));

海量非負(fù)整數(shù):

BitSet bitSet = new BitSet();
for (int x : data) bitSet.set(x);
// bitSet.cardinality() 即不重復(fù)元素個(gè)數(shù)

帶業(yè)務(wù)邏輯的去重

實(shí)際工作里經(jīng)常遇到這樣的情況:遇到重復(fù)時(shí)不能簡(jiǎn)單丟棄,要按某個(gè)規(guī)則做處理。比如:

  • id 去重,但要保留分?jǐn)?shù)最高的那條記錄
  • 去重的同時(shí)累加重復(fù)次數(shù)
  • 數(shù)值在某個(gè)區(qū)間內(nèi)才參與去重

這類需求 Set 直接搞不定,需要把"判重"和"處理"兩步拆開(kāi)來(lái)寫(xiě)。Java 里通常用 LinkedHashMap + 合并函數(shù):

/**
 * 帶業(yè)務(wù)規(guī)則的去重。
 *
 * @param data         原數(shù)據(jù)
 * @param keyFn        從元素提取去重鍵
 * @param onDuplicate  遇到重復(fù)時(shí)如何合并 (舊值, 新值) -> 新代表值
 */
static <T, K> List<T> uniqueBy(List<T> data,
                               Function<T, K> keyFn,
                               BinaryOperator<T> onDuplicate) {
    // LinkedHashMap 保證遍歷順序與首次出現(xiàn)順序一致
    Map<K, T> chosen = new LinkedHashMap<>();
    for (T item : data) {
        K key = keyFn.apply(item);
        // merge:鍵不存在則直接放入,存在則用 onDuplicate 決定保留誰(shuí)
        chosen.merge(key, item, onDuplicate);
    }
    return new ArrayList<>(chosen.values());
}

例 1:按 id 去重,保留分?jǐn)?shù)最高的:

class Student {
    int id;
    String name;
    int score;
    // 構(gòu)造器、getter 略
}

List<Student> students = List.of(
        new Student(1, "張三", 90),
        new Student(1, "張三", 95),   // 同 id,分?jǐn)?shù)更高
        new Student(2, "李四", 85)
);

List<Student> result = uniqueBy(
        students,
        Student::getId,
        // 重復(fù)時(shí)保留分?jǐn)?shù)高的那條
        (old, news) -> news.getScore() > old.getScore() ? news : old
);
// 結(jié)果只剩 [{id:1,score:95}, {id:2,score:85}]

例 2:去重同時(shí)統(tǒng)計(jì)頻次(Stream 的標(biāo)準(zhǔn)做法):

// groupingBy + counting 是 Java 里的"頻次統(tǒng)計(jì)"慣用法
Map<String, Long> counts = data.stream()
        .collect(Collectors.groupingBy(s -> s, LinkedHashMap::new, Collectors.counting()));
// counts.keySet() 即保序的去重結(jié)果

例 3:區(qū)間過(guò)濾——只對(duì) [0, 100] 區(qū)間內(nèi)的值去重,區(qū)間外原樣保留:

Set<Integer> seen = new HashSet<>();
List<Integer> result = new ArrayList<>();
for (int x : data) {
    if (x >= 0 && x <= 100) {
        // 區(qū)間內(nèi)才參與去重
        if (seen.add(x)) result.add(x);
    } else {
        // 區(qū)間外原樣保留
        result.add(x);
    }
}

這三個(gè)例子是同一種思路:把判重與業(yè)務(wù)規(guī)則分開(kāi)。判重用哈希結(jié)構(gòu)保證 O(n),規(guī)則部分留給回調(diào)或顯式分支處理,這樣既不丟性能,又能容納各種業(yè)務(wù)變化。

自定義對(duì)象去重:equals 與 hashCode

Java 集合判重靠?jī)蓚€(gè)方法:hashCode 決定散列到哪個(gè)桶,equals 決定桶內(nèi)是否真的相等。兩個(gè)都必須正確實(shí)現(xiàn),否則 HashSet 的去重會(huì)失效——同一對(duì)象塞進(jìn)去兩次都不報(bào)錯(cuò)。

class User {
    private final long id;
    private final String name;

    public User(long id, String name) {
        this.id = id;
        this.name = name;
    }

    // equals:業(yè)務(wù)上認(rèn)為 id 相同就是同一個(gè)用戶
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof User)) return false;
        return id == ((User) o).id;
    }

    // hashCode:必須與 equals 一致——equals 相等的對(duì)象 hashCode 必須相等
    @Override
    public int hashCode() {
        return Long.hashCode(id);
    }
}

// 現(xiàn)在塞進(jìn) HashSet 就會(huì)按 id 去重
Set<User> uniqueUsers = new HashSet<>(users);

重點(diǎn):重寫(xiě) equals 必須同時(shí)重寫(xiě) hashCode。現(xiàn)代IDE 都能一鍵生成,但前提是你知道用哪個(gè)字段做"業(yè)務(wù)相等"。如果懶得寫(xiě),用 Collectors.toMap(keyFn, ...) 按某個(gè)字段去重也能繞過(guò)這個(gè)問(wèn)題。

總結(jié)

工程上的快捷選擇:

  • 默認(rèn)用 list.stream().distinct().collect(toList()):保序、一行、O(n)
  • 顯式語(yǔ)義用 new ArrayList<>(new LinkedHashSet<>(list)):意圖更清晰
  • 不要順序用 new HashSet<>(list)
  • 順便排序用 new TreeSet<>(list)
  • 海量非負(fù)整數(shù)用 BitSet
  • 自定義對(duì)象先實(shí)現(xiàn) equals 和 hashCode,或用 Collectors.toMap
  • 業(yè)務(wù)規(guī)則干預(yù)用 LinkedHashMap.merge

核心思路:

  1. 同一個(gè)問(wèn)題可以從多個(gè)角度切入
  2. 選對(duì)數(shù)據(jù)結(jié)構(gòu)往往比寫(xiě)更聰明的代碼更重要
  3. O(n²) 與 O(n) 在數(shù)據(jù)變大時(shí)是幾百倍的實(shí)際差距
  4. 不要過(guò)度優(yōu)化——能用 distinct 就別繞彎
  5. 遇到新問(wèn)題先寫(xiě)最直觀的版本,再按瓶頸逐步優(yōu)化

以上就是Java數(shù)組去重的20種實(shí)現(xiàn)方式大全的詳細(xì)內(nèi)容,更多關(guān)于Java數(shù)組去重方式的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論

运城市| 类乌齐县| 白山市| 日喀则市| 城固县| 道孚县| 剑川县| 获嘉县| 大邑县| 余江县| 科尔| 祁连县| 彩票| 玛纳斯县| 黄陵县| 苍梧县| 林西县| 巴林右旗| 扬州市| 随州市| 宜丰县| 玉环县| 南华县| 太仆寺旗| 梓潼县| 洪雅县| 茌平县| 武鸣县| 镇远县| 遂宁市| 龙游县| 石城县| 南郑县| 锡林郭勒盟| 昌江| 离岛区| 通渭县| 兴宁市| 常山县| 南乐县| 茂名市|