精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

解析 Java 集合工具類:功能與實踐

開發 前端
集合是一個至關重要的概念,它猶如數據的魔法盒子,承載著各種元素的有序或無序組合。而集合工具類,則像是一把神奇的鑰匙,為我們開啟了高效處理和操作這些集合的大門。

在編程的廣袤領域中,集合是一個至關重要的概念,它猶如數據的魔法盒子,承載著各種元素的有序或無序組合。而集合工具類,則像是一把神奇的鑰匙,為我們開啟了高效處理和操作這些集合的大門。

一、詳解Java集合常用的方法

1. 集合判空

日常業務功能開發,為保證程序的健壯性,判空操作是必不可少的,筆者在日常審查代碼時候會看到很多開發會使用size方法進行判空,這種方案在常規集合容器下沒有任何問題,但是在某些特殊場景下,這個判空就可能存在性能問題:

if (list.size() == 0) {
            //do something
        }

最典型的就是ConcurrentLinkedQueue,打開其內部源碼即可看到,該容器獲取元素數時是從頭節點開始遍歷獲取的:

public int size() {
        int count = 0;
        //從頭節點開始遍歷累加count
        for (Node<E> p = first(); p != null; p = succ(p))
            if (p.item != null)
                // Collection.size() spec says to max out
                if (++count == Integer.MAX_VALUE)
                    break;
        return count;
    }

所以一般情況下,我們更建議使用isEmpty,該方法無論從語義還是實現上,都避免了掃描容器的開銷,是筆者比較推薦的一種判空方式:

public boolean isEmpty() {
        return size == 0;
    }

2. 列表集合轉Map

集合轉Map時可以直接使用java8版本的流編程,對應代碼示例如下:

ArrayList<Person> list = new ArrayList<>();
        list.add(new Person("jack", 18));
        list.add(new Person("rose", 16));
        //用流編程進行轉換
        list.stream().collect(Collectors.toMap(Person::getName, Person::getAge));

對應的我們也給出輸出結果:

16:17:35.383 [main] INFO com.sharkChili.Main - [Person(name=jack, age=18), Person(name=rose, age=16)]

需要注意一點,我們使用的時候盡可能保證value非空,要知道toMap底層用到了HashMap的方法,該方法中如果判斷value為空會拋出空指針異常:

@Override
    public V merge(K key, V value,
                   BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
         //如果value為空則拋出空指針異常          
        if (value == null)
            throw new NullPointerException();
  //......

}

3. 集合遍歷時移除元素(重點)

不建議使用for循環等方式進行remove,會拋出ConcurrentModificationException ,這就是單線程狀態下產生的 fail-fast 機制。

fail-fast 機制,即快速失敗機制,是java集合(Collection)中的一種錯誤檢測機制。當在迭代集合的過程中該集合在結構上發生改變的時候,就有可能會發生fail-fast,即拋出ConcurrentModificationException異常。fail-fast機制并不保證在不同步的修改下一定會拋出異常,它只是盡最大努力去拋出,所以這種機制一般僅用于檢測bug。

所以我們建議jdk8情況下使用這種方式進行動態移除,即使用removeIf方法,該方法已經為我們做好了封裝無論從使用還是語義上,這種寫法更加友好:

List<Integer> list = new ArrayList<>();
        for (int i = 1; i <= 10; ++i) {
            list.add(i);
        }
        //移除元素為5的
        list.removeIf(integer -> integer == 5);

這一點,我們從底層的源碼就可以知道,它為我們做好了:

  • 獲取迭代器
  • 遍歷元素
  • 基于迭代器安全刪除元素

對應我們給出這段源碼實現,該代碼位于Collection下:

default boolean removeIf(Predicate<? super E> filter) {
        Objects.requireNonNull(filter);
        boolean removed = false;
        //獲取迭代器
        final Iterator<E> each = iterator();
        //檢查是否有下一個元素
        while (each.hasNext()) {
        //如果斷言(即我們外部傳入的判斷條件)返回true,將元素刪除
            if (filter.test(each.next())) {
                each.remove();
                removed = true;
            }
        }
        return removed;
    }

4. 集合去重

集合去重可以利用 Set 元素唯一的特性且通過O(1)級別的元素定位,可以快速對一個集合進行去重操作,避免使用 List 的 contains() 進行掃描元素的性能開銷:

如下代碼所示,list去重需要調用contains,要遍歷數組,而set底層用hash計算,如果散列良好情況下判重只需要O(1)

int size = 10_0000;
        //List元素去重
        List<Integer> resultList = new ArrayList<>(size);
        long start = System.currentTimeMillis();
        for (int i = 0; i < size; i++) {
            if (!resultList.contains(i)) {
                resultList.add(i);
            }
        }
        long end = System.currentTimeMillis();
        System.out.println("List去重:" + (end - start));

        //set集合去重
        start = System.currentTimeMillis();
        HashSet<Integer> set = new HashSet<>();
        for (int i = 0; i < size; i++) {
            set.add(i);
        }
        end = System.currentTimeMillis();
        System.out.println("HashSet去重:" + (end - start));

對應我們也給出輸出結果來比對一下兩個集合之間的性能差異:

List去重:4353
HashSet去重:8

5. 集合轉數組

使用集合轉數組的方法,一般使用的是集合的 toArray(T[] array)這個方法,我們只需傳入數組首元素引用地址即可:

List<String> list = Arrays.asList("a", "b", "c", "d", "e", "f", "g", "h");

        //集合轉數組
        String[] array = list.toArray(new String[0]);

這一點我們查看Arrays的toArray實現詳情就知道,該方法會獲取當前需要轉為數組的列表大小,然后從列表首元素地址開始將元素我們傳入的數組引用空間中:

public <T> T[] toArray(T[] a) {
 //獲取列表元素大小
            int size = size();
            //......
            //基于傳入元素地址將元素復制到傳入的數組地址空間中                         
            System.arraycopy(this.a, 0, a, 0, size);
           //......
            return a;
        }

6. 數組轉集合

使用工具類 Arrays.asList() 把數組轉換成集合時,轉成的集合是Arrays工具類內部的ArrayList:

Integer[] nums = new Integer[10];
        for (int i = 0; i < 10; i++) {
            nums[i] = i;
        }
 List<Integer> myList = Arrays.asList(nums);

需要注意的是AbstractList不能使用其修改集合相關的方法,它是一個只讀的容器, 它并沒有重寫 add/remove/clear 方法,所以會拋出 UnsupportedOperationException 異常,這一點我們查看AbstractList源碼即可知曉這一點:

public void add(int index, E element) {
        throw new UnsupportedOperationException();
    }

  
    public E remove(int index) {
        throw new UnsupportedOperationException();
    }

二、詳解Java集合工具類

1. 常見集合排序操作API

Java內置了很多使用的集合操作的api,這里我們不妨列一下方法清單,讀者可以基于注釋熟悉一下這些API的使用:

void reverse(List list)//反轉
void shuffle(List list)//隨機排序
void sort(List list)//按自然排序的升序排序
void sort(List list, Comparator c)//定制排序,由Comparator控制排序邏輯
void swap(List list, int i , int j)//交換兩個索引位置的元素
void rotate(List list, int distance)//旋轉。當distance為正數時,將list后distance個元素整體移到前面。當distance為負數時,將 list的前distance個元素整體移到后面

2. 集合排序

升序排序我們只需將列表傳入sort方法,其底層排序的工作機制稍微會做介紹,這里我們先熟悉一下使用方法:

//隨機生成長度為10的列表
        List<Integer> list = RandomUtil.randomEleList(IntStream.range(0, 100).boxed().collect(Collectors.toList()), 10);
        //打印排序前的列表
        System.out.println(list);
        System.out.println("Collections  升序排序:");
        //排序并打印排序后的結果
        Collections.sort(list);
        System.out.println(list);

對應的輸出結果如下:

[16, 84, 72, 18, 42, 93, 55, 28, 47, 14]
Collections  升序排序:
[14, 16, 18, 28, 42, 47, 55, 72, 84, 93]

sort方法同樣是支持倒敘的排序的,對應的我們給出倒敘的比較器作為參數即可:

//隨機生成長度為10的列表
        List<Integer> list = RandomUtil.randomEleList(IntStream.range(0, 100).boxed().collect(Collectors.toList()), 10);
        //打印排序前的列表
        System.out.println(list);
        //倒敘排序并打印
        System.out.println("Collections  倒敘排序:");
        Collections.sort(list, Comparator.reverseOrder());
        System.out.println(list);

對應的我們也給出輸出結果:

[65, 84, 40, 27, 11, 24, 90, 54, 57, 6]
Collections  倒敘排序:
[90, 84, 65, 57, 54, 40, 27, 24, 11, 6]

3. 列表翻轉

reverse方法就是將我們元素內部按照倒敘反轉一下,對應我們給出代碼示例:

//隨機生成長度為10的列表
        List<Integer> list = RandomUtil.randomEleList(IntStream.range(0, 100).boxed().collect(Collectors.toList()), 10);
        //打印排序前的列表
        System.out.println(list);
        //將列表元素翻轉一圈并打印
        System.out.println("Collections  翻轉:");
        Collections.reverse(list);
        System.out.println(list);

可以看到,翻轉后的數值按照列表倒敘進行排列了:

[17, 84, 53, 20, 70, 29, 15, 61, 63, 82]
Collections  翻轉:
[82, 63, 61, 15, 29, 70, 20, 53, 84, 17]

4. 列表隨機排列

shuffle顧名思義即洗牌的意思,它會將列表內部元素順序打亂

//隨機生成長度為10的列表
        List<Integer> list = RandomUtil.randomEleList(IntStream.range(0, 100).boxed().collect(Collectors.toList()), 10);
        //打印排序前的列表
        System.out.println(list);
        //針對列表進行隨機排序
        System.out.println("Collections  隨機排序:");
        Collections.shuffle(list);
        System.out.println(list);

輸出結果:

[64, 24, 63, 94, 41, 76, 60, 69, 43, 27]
Collections  隨機排序:
[63, 64, 27, 41, 60, 24, 94, 69, 43, 76]

5. 列表整體移動

rotate算是比較少用的api,讀者可以簡單了解一下,這個方法會將列表中所有元素斗向前移動,對于列表末尾的元素會移動到列表首部,具體算法筆者會在后面的源碼講解進行分析,這里我們了解一下其使用效果:

//隨機生成長度為10的列表
        List<Integer> list = RandomUtil.randomEleList(IntStream.range(0, 100).boxed().collect(Collectors.toList()), 10);
        //打印排序前的列表
        System.out.println(list);
        //將列表元素全部向前移動一步
        System.out.println("Collections 所有元素向前移動一步:");
        Collections.rotate(list, 1);
        System.out.println(list);

輸出結果:

[4, 80, 35, 52, 28, 79, 17, 61, 33, 11]
Collections 所有元素向前移動一步:
[11, 4, 80, 35, 52, 28, 79, 17, 61, 33]

6. 兩數交換

swap可以指定兩數索引位置元素交換,如下代碼,我們將索引0和索引1位置的元素進行交換:

//隨機生成長度為10的列表
        List<Integer> list = RandomUtil.randomEleList(IntStream.range(0, 100).boxed().collect(Collectors.toList()), 10);
        //打印排序前的列表
        System.out.println(list);
        //打印兩數交換后的列表
        System.out.println("Collections  交換兩個索引位置元素:");
        Collections.swap(list, 0, 1);
        System.out.println(list);

對應輸出結果如下:

[24, 8, 91, 59, 34, 13, 78, 44, 84, 86]
Collections  交換兩個索引位置元素:
[8, 24, 91, 59, 34, 13, 78, 44, 84, 86]

三、詳解Java集合工具類算法底層實現

1. Collections.sort底層實現

查看sort方法底層實現可以看出,除非開發顯式配置歸并排序才會調用legacyMergeSort進行歸并排序,否則一律使用TimSort進行列表排序:

public static <T> void sort(T[] a, Comparator<? super T> c) {
        if (c == null) {
            sort(a);
        } else {
         //如果配置指定要求才使用歸并排序
            if (LegacyMergeSort.userRequested)
                legacyMergeSort(a, c);
            else
             //默認使用TimSort排序
                TimSort.sort(a, 0, a.length, c, null, 0, 0);
        }
    }

而TimSort的sort方法就是排序核心的實現,TimSort是自適應的、混合的、穩定的排序算法。是基于歸并和二分插入排序優點結合的排序算法。復雜度最壞的情況下只有O(nlogn),最壞的情況下,空間復雜度為O(n/2)。

這個方法在基數閾值的選取和排序的實現細節都做了機制都做了相對極致的優化,當列表元素小于32的情況下,TimSort會直接通過二分插入排序直接完成排序操作。

二分插入排序法是插入排序法的升級版本,如下所示,我們都知道插入排序后左邊的元素都是有序的,如果使用常規二分排序,那么最壞情況下插入時間是O(n),所以我們基于左邊有序這個特點改用二分插入的方式完成排序優化了這個問題。

當右邊元素進行插入時,不斷在左邊進行二分運算定位到mid元素:

  • 如果mid索引對應的元素小于插入元素,說明left索引元素值太小,需要向右移動找到下一個折中值。
  • 如果mid索引元素值大于待插入的元素值,說明right坐標對應的元素值太大,需要讓right坐標向左移動找到小一點的中間值。

通過這樣的二分運算最終會找到一個小于或者等于待入元素坐標left作為插入索引并將元素插入,然后其余元素全部向后移動一位:

對此我們也給出TimSort排序的前半部分實現,可以看到這段代碼在進行二分排序前會先定位開頭有序的最小區間initRunLen ,如下圖所示,這個數組索引3之前的元素都是正向元素的,所以排序是從索引4開始:

對應的我們也給出這段代碼的整體實現:

static <T> void sort(T[] a, int lo, int hi, Comparator<? super T> c,
                         T[] work, int workBase, int workLen) {
     int nRemaining  = hi - lo; //計算出待排序的范圍
        if (nRemaining < 2)
            return;  // Arrays of size 0 and 1 are always sorted

 // 若小于32則直接調用`binarySort`
        if (nRemaining < MIN_MERGE) {
        //計算出lo 到 hi 范圍找出有序的長度,若是降序則轉為升序后返回
            int initRunLen = countRunAndMakeAscending(a, lo, hi, c);
            //使用二分插入法將hi以內未排序的元素插入到數組中
            binarySort(a, lo, hi, lo + initRunLen, c);
            //完成后直接返回
            return;
        }

 //......


}

countRunAndMakeAscending代碼的實現,該方法本質上就是從頭開始比對元素:

  • 如果一開始runHi 元素大于其后一個元素,則正序方式先前遍歷,runHi 不斷前行,找到正向有序的最小區間。
  • 如果一開始runHi 元素小于后一個元素,則按照倒敘方式進行編譯,runHi 不斷前行,找到逆序的最小區間。
private static <T> int countRunAndMakeAscending(T[] a, int lo, int hi,
                                                    Comparator<? super T> c) {
        assert lo < hi;
        // 待比較的值從lo+1 開始
        int runHi = lo + 1;
        if (runHi == hi)
            return 1;

        // 第一次比較若小于0就進入循環,找到最小范圍的降序子數組,循環結束后翻轉為升序
        if (c.compare(a[runHi++], a[lo]) < 0) { // Descending
            while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) < 0)
                runHi++;
                //循環結束后翻轉為升序
            reverseRange(a, lo, runHi);
        } else {                              
        //反之就尋找升序子數組
            while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) >= 0)
                runHi++;
        }

 //runHi - lo即我們本次找到的有序子數組的長度
        return runHi - lo;
    }

然后我們再介紹binarySort,如上文所說不斷通過二分運算比對mid和插入元素的值,然后進行插入,這里筆者特殊說明一下binarySort對于二分插入排序的優化細節,從代碼中可以看到,當二分插入排序定位到合適的位置之后,會判斷這個位置和插入元素之間的距離,如果兩者距離小于2,則直接通過簡單的元素交換:

反之,如果待插入的位置和插入元素索引位置大于2,則找到left及其前方元素批量先前移動一格,然后騰出一塊空間將元素插入:

對應的我們給出binarySort的代碼實現細節:

private static <T> void binarySort(T[] a, int lo, int hi, int start,
                                       Comparator<? super T> c) {
        //......
        for ( ; start < hi; start++) {
            T pivot = a[start];

            // 二分搜索范圍設置為[lo,start)
            int left = lo;
            int right = start;
            assert left <= right;
         
         //通過二分法,找到合適插入位置
            while (left < right) {
            //通過位運算高效實現/2得到一個中間索引mid
                int mid = (left + right) >>> 1;
                //如果中間值大于插入元素,則right設置為mid,視圖找到小一點的mid
                if (c.compare(pivot, a[mid]) < 0)
                    right = mid;
                else
                //如果中間值小于插入元素,則left等于mid找到一個大一點的mid
                    left = mid + 1;
            }
            assert left == right;

          
            int n = start - left;  // 計算需要移動的步數
            // 這里正是設計者的精華所在,可以看到如果只要移動1-2步,直接交換即可,若大于兩步則直接指定數組范圍進行批量拷貝
            switch (n) {
                case 2:  a[left + 2] = a[left + 1];
                case 1:  a[left + 1] = a[left];
                         break;
                default: System.arraycopy(a, left, a, left + 1, n);
            }
            a[left] = pivot;
        }
    }

當元素大于32的時候,TimSort排序算法就會進行更近一步的設計,即針對當前數組生成無數個子單元進行二分插入排序,然后基于每個有序的子單元進行歸并從而得到不斷歸并得到一個有序集合:

對應的我們給出TimSort后續代碼,整體邏輯與筆者說明一致,建議讀者結合筆者說明和注釋理解:

TimSort<T> ts = new TimSort<>(a, c, work, workBase, workLen);
        int minRun = minRunLength(nRemaining);
        do {
            // 計算出最大的有序范圍的索引
            int runLen = countRunAndMakeAscending(a, lo, hi, c);

            //若小于minRun,則說明進行排序的數組太小,需要指定一個范圍排序一下
            if (runLen < minRun) {
            //nRemaining 為當前待排序的范圍大小,minRun 為計算出來至少要排序的范圍。若nRemaining 小于minRun ,則取nRemaining ,意味需要排序的范圍就剩幾個了直接用這幾個值排個序就好了。反之則取minRun 進行二分插入排序
                int force = nRemaining <= minRun ? nRemaining : minRun;
                binarySort(a, lo, lo + force, lo + runLen, c);
                //完成后force的值就代表當前經歷排序的元素個數,存到runLen中,作為后續合并的依據
                runLen = force;
            }

            // 將lo到runLen的值存到棧中,后續歸并會用到
            ts.pushRun(lo, runLen);
            //將當前排序的范圍數組歸并到已排序的數組中
            ts.mergeCollapse();

            // 起始位置加到runLen之后
            lo += runLen;
            //待排序的值減去已排序的長度
            nRemaining -= runLen;
        } while (nRemaining != 0);

        // Merge all remaining runs to complete sort
        assert lo == hi;
        ts.mergeForceCollapse();
        assert ts.stackSize == 1;

由于這篇文章主要描述Java集合工具類的使用,所以就不展開細講了。

2. rotate列表旋轉算法的實現

rotate旋轉算法底層也有很多的巧妙設計,步入其源碼可以看到:

  • 如果是RandomAccess即具備隨機訪問特性的數組或者數組大小小于100時使用rotate1方法進行旋轉
  • 反之說明該列表是不具備隨機范文的鏈表則調用rotate2進行元素旋轉

對應的我們給出代碼的頂層實現:

public static void rotate(List<?> list, int distance) {
  //如果是具備隨機訪問特性或者元素小于100則調用rotate1
        if (list instanceof RandomAccess || list.size() < ROTATE_THRESHOLD)
            rotate1(list, distance);
        else //反之調用rotate2
            rotate2(list, distance);
    }

我們先來說說rotate1方法的實現,邏輯比較簡單,計算出移動的步數之后通過list的set方法將元素設置到移動的位置上,通過set方法得到該位置上原有的元素,再將該元素移動到旋轉后的的索引上:

對應的我們給出這段實現的源碼,讀者可結合說明了解核心流程:

private static <T> void rotate1(List<T> list, int distance) {
        int size = list.size();
        if (size == 0)
            return;
            //計算移動的步數
        distance = distance % size;
        //若為負數則加上數組大小 即可 (向左走n步)==(向右走數組大小+n步)
        if (distance < 0)
            distance += size;
        if (distance == 0)
            return;

 //移動
        for (int cycleStart = 0, nMoved = 0; nMoved != size; cycleStart++) {
            T displaced = list.get(cycleStart);
            int i = cycleStart;
            
            do {
                i += distance;
                //若大于數組大小則減去數組大小得出最終要走的步
                if (i >= size)
                    i -= size;
                   //賦值并返回舊元素進行下一次do while旋轉
                displaced = list.set(i, displaced);
                nMoved ++;
            } while (i != cycleStart);
        }
    }

走到rotate2這個函數則說明這個數組為不具備隨機訪問性的鏈表,為了保證性能,該方法會通過計算的方式得到計算出一個批量移動的區間,然后基于這兩個整體進行批量的移動。

例如我們現在有一個鏈表,內部包含0-100一共101個元素,元素值為0~100,剛剛好可以執行rotate2方法,假設我們希望全體向前移動一步,rotate2算法會通過-distance % size得到100,即[0,100]區間是只需先前移動的區間,而[101]是需要移動到列表前面的區間,rotate2的執行步驟為:

  • 將區間1翻轉,得到99~0。
  • 將區間2翻轉,得到100,此時列表排列為99~0、100。
  • 最后將整個列表進行一次翻轉,將100移動到列表最前面的同時,也將只需先前移動一格的區間放到100的后面:

對應的我們也給出rotate2的代碼實現,整體思路和筆者說明的一致,就是通過-distance % size計算得到只需先前移動和要翻轉到列表前面的兩個區間,然后執行:

  • 只需向前移動的區間1翻轉。
  • 移動到列表首部的區間2翻轉。
  • 整個列表翻轉將區間2提前。
private static void rotate2(List<?> list, int distance) {
        int size = list.size();
        if (size == 0)
            return;
        //[0,mid]只需向前移動distance步,(mid,list.size()-1]移動到列表前面    
        int mid =  -distance % size;
        if (mid < 0)
            mid += size;
        if (mid == 0)
            return;
  //翻轉[0,mid]
        reverse(list.subList(0, mid));
        //翻轉(mid,list.size()-1]
        reverse(list.subList(mid, size));
        //整體移動,將(mid,list.size()-1]翻轉到列表前方,同時保證[0,mid]有序
        reverse(list);
    }

四、詳解jdk常見搜索比對函數

1. 核心api概覽

jdk也為我提供了很多使用的搜索和比較統計函數,對應的函數列表如下:

int binarySearch(List list, Object key)//對List進行二分查找,返回索引,注意List必須是有序的
int max(Collection coll)//根據元素的自然順序,返回最大的元素。 類比int min(Collection coll)
int max(Collection coll, Comparator c)//根據定制排序,返回最大元素,排序規則由Comparatator類控制。類比int min(Collection coll, Comparator c)
void fill(List list, Object obj)//用指定的元素代替指定list中的所有元素
int frequency(Collection c, Object o)//統計元素出現次數
int indexOfSubList(List list, List target)//統計target在list中第一次出現的索引,找不到則返回-1,類比int lastIndexOfSubList(List source, list target)
boolean replaceAll(List list, Object oldVal, Object newVal)//用新元素替換舊元素

2. 使用示例

System.out.println("Collections  二分搜索法(注意數組并需有序):");
        Collections.sort(list);
        int idx = Collections.binarySearch(list, 1);
        System.out.println(idx);

        System.out.println("Collections  求最大值:");
        System.out.println(Collections.max(list));

        System.out.println("Collections  按自定義方式找最大值:");
        System.out.println(Collections.max(list, Comparator.reverseOrder()));

        System.out.println("Collections  用指定元素替代list中所有的元素:");
        Collections.fill(list, 5);
        System.out.println(list);


        System.out.println("Collections  統計頻次:");
        System.out.println(Collections.frequency(list, 5));


        System.out.println("Collections  返回target子集在list中第一次出現的位置:");
        List<Integer> list1 = Arrays.asList(1, 2, 3, 4, 5, 6);
        List<Integer> target = Arrays.asList(3, 4);
        System.out.println(Collections.indexOfSubList(list1, target));//返回target子集在list中第一次出現的位置

        System.out.println("Collections  replaceAll:");
        Collections.replaceAll(list, 5, 6);
        System.out.println(list);

3. 詳解洗牌算法

這里我們著重說明一下隨機洗牌算法的實現,邏輯比較簡單:

  • 如果列表具備隨機訪問或者size小于100,可以直接從size開始倒敘調用swap進行隨機元素交換。
  • 反之說明當前列表是大于100的鏈表,首先將這些元素存到一個具備隨機訪問的數組中,然后基于這個數組進行隨機swap交換,再存入鏈表中,所以性能表現會差一些。

對應的源碼如下:

public static void shuffle(List<?> list, Random rnd) {
        int size = list.size();
        //若小于SHUFFLE_THRESHOLD 或者是RandomAccess類則從高位索引與隨機一個低位索引交換值完成洗牌
        if (size < SHUFFLE_THRESHOLD || list instanceof RandomAccess) {
            for (int i=size; i>1; i--)
             //將i-1位置元素和隨機一個位置進行交換
                swap(list, i-1, rnd.nextInt(i));
        } else {
        //反之轉成數組,再遍歷數組的值存到list中
            Object arr[] = list.toArray();

          
            for (int i=size; i>1; i--)
            ////將i-1位置元素和隨機一個位置進行交換
                swap(arr, i-1, rnd.nextInt(i));

         //然后設置到鏈表中
            ListIterator it = list.listIterator();
            for (int i=0; i<arr.length; i++) {
                it.next();
                it.set(arr[i]);
            }
        }
    }

五、同步控制

1. 同步控制常見函數

注意,非必要不要使用這種API,效率極低

synchronizedCollection(Collection<T>  c) //返回指定 collection 支持的同步(線程安全的)collection。
synchronizedList(List<T> list)//返回指定列表支持的同步(線程安全的)List。
synchronizedMap(Map<K,V> m) //返回由指定映射支持的同步(線程安全的)Map。
synchronizedSet(Set<T> s) //返回指定 set 支持的同步(線程安全的)set。

2. 使用示例

可以看到筆者在下面貼出使用Collections.synchronizedList包裝后的list的add方法,鎖的粒度很大,在多線程操作情況下,性能非常差。

我們就以synchronizedList為例查看其add方法,可以看到其實現線程安全的方式很簡單,直接在工作代碼上synchronized ,在高并發情況下,很可能造成大量線程阻塞

public void add(int index, E element) {
            synchronized (mutex) {list.add(index, element);}
        }

示例代碼如下,我們分別開兩個線程,往數組中添加1000個數組,可以看到筆者注釋代碼中用了普通list,以及通過Collections.synchronizedList后的list,感興趣的讀者可以基于下面代碼測試是否線程安全

@Test
    public void ThreadSafe() {
        CountDownLatch latch = new CountDownLatch(1);
//        List<Integer> list = new ArrayList<>();

   
        List<Integer> list = Collections.synchronizedList(new ArrayList<>());
        ExecutorService threadPool = Executors.newFixedThreadPool(2);
        for (int i = 0; i < 2; i++) {
            threadPool.submit(() -> {
                try {
                    latch.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                for (int j = 0; j < 1000; j++) {

                    list.add(j);
                }
            });

        }

        latch.countDown();

        threadPool.shutdown();
        while (!threadPool.isTerminated()) {

        }

        System.out.println(list.size());
    }

輸出結果為2000,說明該方法確實實現了線程安全

2000

責任編輯:趙寧寧 來源: 寫代碼的SharkChili
相關推薦

2025-05-06 07:04:23

MyBatis技巧框架

2024-11-08 16:54:38

2011-03-25 11:39:13

Nagios 功能Nagios 結構

2009-06-17 15:13:57

Java網絡功能編程方法

2010-06-28 14:08:18

UML建模工具Visi

2022-11-01 07:54:18

Python工具類計數器

2021-12-23 14:57:25

Nginx服務器虛擬主機

2025-05-12 08:14:39

2021-07-21 09:15:57

Python工具編程語言

2009-09-04 09:15:41

Java集合類

2009-07-17 17:00:20

JavaFX功能Swing

2009-10-09 17:30:11

服務器測試方法

2020-06-27 09:01:53

Java包裝類編程語言

2020-08-01 16:01:19

廣域網SDN網絡技術

2024-11-08 17:15:49

2009-12-17 13:52:06

Fedora 11功能

2013-07-23 15:10:34

PowerShell

2010-07-05 13:19:36

2025-10-16 07:34:42

Spring工具類??API??

2020-07-08 07:56:08

Java工具類包裝類
點贊
收藏

51CTO技術棧公眾號

国产中文字幕在线免费观看| 国产99在线免费| 三级黄色免费观看| 在线视频亚洲欧美中文| 欧美视频在线看| 一区二区三视频| 隣の若妻さん波多野结衣| 久久在线91| 欧美黑人巨大xxx极品| 国产真实乱人偷精品人妻| 欧美在线一级| 精品久久久香蕉免费精品视频| 在线日韩av永久免费观看| 欧美少妇bbw| 激情欧美一区二区三区在线观看| 91chinesevideo永久地址| 亚洲色图27p| 色愁久久久久久| 日韩三级在线观看| 在线观看av日韩| 77thz桃花论族在线观看| 国产精品毛片无遮挡高清| 国产高清在线一区| 97视频免费在线| 日韩精品三区四区| 91精品成人久久| 欧美性猛交xxxxx少妇| 日本大胆欧美| 亚洲免费高清视频| 国产一卡二卡三卡四卡| 成人永久在线| 欧美日韩色一区| 成熟老妇女视频| 暧暧视频在线免费观看| 亚洲免费观看高清完整版在线观看熊| 欧美一区二区影视| 亚洲av激情无码专区在线播放| 国产乱人伦精品一区二区在线观看| 国产福利视频一区| 亚洲 欧美 成人| 欧美激情综合色综合啪啪| 日韩视频免费在线观看| 日韩av片在线| 成人久久综合| 在线日韩av观看| 香蕉视频久久久| 国产精品视频一区二区三区四蜜臂| 亚洲成色999久久网站| 日本xxxx免费| 北条麻妃一区二区三区在线| 日韩欧美一级二级三级久久久| 五月六月丁香婷婷| 久久99成人| 337p亚洲精品色噜噜| 天天干天天色天天干| 亚洲欧美专区| 欧美一级淫片007| 91视频免费入口| 久久久精品区| 精品国产乱码久久久久久夜甘婷婷 | 国产精品一二三在线| 真实新婚偷拍xxxxx| 青青草一区二区三区| 国产精品一二三视频| 国产精品一级视频| 国产福利一区二区三区| 国产精品乱码视频| 亚洲 小说区 图片区 都市| 久久久久久97三级| 亚洲高清视频一区| 精品51国产黑色丝袜高跟鞋| 亚洲最大成人网4388xx| 成人一对一视频| 小黄鸭精品aⅴ导航网站入口| 在线免费观看一区| 欧美视频国产视频| 超碰成人97| 亚洲毛片在线免费观看| xxxxx99| 欧美激情视频一区二区三区免费| 久久免费国产视频| 无码人妻av免费一区二区三区| 日本免费在线视频不卡一不卡二| 亚洲一区久久久| 亚州男人的天堂| 亚洲国产高清在线| 国产性生活免费视频| 日韩影片中文字幕| 欧美一区二区视频在线观看2022| 中文字幕永久免费| 国产日产一区| 欧美国产在线电影| 日韩欧美国产另类| 国产91色综合久久免费分享| 欧美福利一区二区三区| www.欧美日本韩国| 日本韩国一区二区| 永久看看免费大片| 日本不卡免费一区| 69视频在线播放| 国产精品九九九九| 久久综合资源网| 91大学生片黄在线观看| 欧美影视资讯| 亚洲第一视频网站| 男人的午夜天堂| 香蕉久久a毛片| 亚洲综合日韩在线| 国产黄色在线播放| 亚洲成人一区二区在线观看| 亚洲欧美日本一区二区| 少妇精品久久久一区二区| 欧美日韩成人在线播放| 在线播放一级片| 91麻豆高清视频| 国产片侵犯亲女视频播放| a屁视频一区二区三区四区| 日韩精品视频在线免费观看| 丝袜 亚洲 另类 欧美 重口| 男人的j进女人的j一区| 久久精品一二三区| √8天堂资源地址中文在线| 日韩欧美一级二级| 男人在线观看视频| 美女视频黄久久| 人偷久久久久久久偷女厕| www成人免费观看| 精品日韩99亚洲| 欧美日韩在线观看成人| 黄网站免费久久| 亚洲一区在线免费| 99久久综合国产精品二区| 亚洲精品网址在线观看| 国产成人无码精品| 波多野结衣在线一区| 99国产精品白浆在线观看免费| 国产欧美日韩电影| 久久五月天色综合| 国产尤物在线观看| 亚洲欧美中日韩| 一区二区在线免费看| 欧美国产一区二区三区激情无套| 国产成人精品亚洲精品| 久久经典视频| 在线免费av一区| 少妇太紧太爽又黄又硬又爽小说 | 亚洲在线中文字幕| 久久久久亚洲av无码麻豆| 91成人免费| 2020国产精品久久精品不卡| 成人区精品一区二区不卡| 欧美一级免费观看| 久久久久久久九九九九| 成人国产一区二区三区精品| 六月婷婷在线视频| 亚洲老女人视频免费| 国产精品69av| 欧美成人性生活视频| 欧美一区二区三区视频在线| 久久久久成人片免费观看蜜芽| 粉嫩在线一区二区三区视频| 人妻少妇精品无码专区二区| 天美av一区二区三区久久| 奇米4444一区二区三区| 国产高清视频在线| 欧美精品色一区二区三区| 日韩激情综合网| 国产成人一级电影| 黄色免费观看视频网站| 精品国产乱码久久久久久1区2匹| 国产主播欧美精品| 免费在线观看的电影网站| 日韩精品极品视频| 一区二区自拍偷拍| 亚洲精品高清在线观看| 国产麻豆剧传媒精品国产av| 日韩精品一卡二卡三卡四卡无卡| 一区二区av| gogo人体一区| 国产精品成人一区| 亚洲妇熟xxxx妇色黄| 日韩成人在线视频网站| 这里只有精品6| 亚洲一区在线观看免费观看电影高清| 亚洲一区二区在线免费| 免费看黄色91| 性高湖久久久久久久久aaaaa| 亚洲综合图色| 亚洲一区二区久久久久久久| 免费看男女www网站入口在线| 伊人亚洲福利一区二区三区| 精品久久久中文字幕人妻| 欧美性少妇18aaaa视频| www.av成人| 国产欧美日韩精品在线| 中文字幕在线播放一区二区| 视频在线观看一区| 日韩美女爱爱视频| 色综合天天爱| 精品无码久久久久久久动漫| 深夜福利亚洲| 日产精品久久久一区二区福利 | 欧美孕妇与黑人孕交| 黄色免费在线看| 亚洲片国产一区一级在线观看| 国内老熟妇对白xxxxhd| 色欧美日韩亚洲| 久久精品视频国产| 中文字幕一区三区| 级毛片内射视频| 99久久久无码国产精品| 五月六月丁香婷婷| 美女脱光内衣内裤视频久久网站| 国产亚洲综合视频| 丰满岳乱妇国产精品一区| 亚洲精品进入| 国产精品揄拍500视频| 欧美激情护士| 欧美精品福利在线| 乱人伦中文视频在线| 亚洲人成在线观看| 无码精品人妻一区二区| 欧美成人三级在线| 97精品人妻一区二区三区| 色偷偷成人一区二区三区91| 日韩三级免费看| 亚洲综合网站在线观看| 日韩三级在线观看视频| 国产精品网站在线| 日韩人妻无码精品综合区| 91在线云播放| 亚洲久久久久久| 成人av免费在线播放| 亚洲一区二区三区三州| 久久99日本精品| 一本色道久久亚洲综合精品蜜桃| 天堂在线一区二区| 成人在线观看a| 久久永久免费| 天堂社区在线视频| 久久亚洲图片| 福利在线一区二区三区| 久久天堂成人| 欧美黄色一级片视频| 久久精品成人| 色婷婷综合久久久久中文字幕 | 91av网站在线播放| 三级在线看中文字幕完整版| 午夜精品福利在线观看| 色在线视频观看| 欧美在线视频观看免费网站| 亚洲欧美小说色综合小说一区| 91高清视频免费| 欧美自拍电影| 国产精品一区av| 国产色99精品9i| 成人av网站观看| 国内精品免费| 欧美另类视频在线| 欧美精品尤物在线观看| 在线观看日韩羞羞视频| 中文字幕日韩欧美精品高清在线| 国产成人生活片| 99riav国产精品| 人人爽人人av| 国产一区二区三区免费| 性生交大片免费看l| 成人免费视频国产在线观看| aaaaa级少妇高潮大片免费看| 国产午夜精品久久久久久免费视| 久久久国产一级片| 一区二区三区四区中文字幕| 日本三级黄色大片| 一本一道综合狠狠老| 中文字幕一级片| 日韩欧美国产午夜精品| 亚洲人妻一区二区| 国产小视频国产精品| 午夜伦理在线| 久久久久一本一区二区青青蜜月| 亚洲黄色中文字幕| 成人久久久久爱| 任你躁在线精品免费| 亚洲综合欧美日韩| 亚洲人妖在线| 亚洲欧美在线精品| 成人不卡免费av| 刘亦菲国产毛片bd| 亚洲1区2区3区4区| 136福利视频导航| 精品国产百合女同互慰| 成年人免费在线视频| 欧美黄色免费网站| 日韩中文在线播放| 国产精品免费看一区二区三区| 精品日韩免费| 国产一级爱c视频| 久草精品在线观看| av直播在线观看| 亚洲综合偷拍欧美一区色| 少妇又紧又色又爽又刺激视频| 精品久久久久久最新网址| 99免在线观看免费视频高清| 97精品一区二区三区| 四虎精品一区二区免费| 欧美二区三区在线| 亚洲激情精品| 日韩成人av免费| 国产欧美精品日韩区二区麻豆天美| 欧美黄色免费在线观看| 欧美在线free| 亚洲欧洲精品视频| 久久久久久久一区二区三区| 欧美黄页在线免费观看| 蜜桃久久影院| 亚洲欧洲一区| 久久精品一二三四| 国产精品久久国产精麻豆99网站| 精品国产午夜福利| 精品噜噜噜噜久久久久久久久试看| 9191在线观看| 国产精品福利在线| 色狠狠久久av综合| 成人一对一视频| 成人av免费网站| 玖玖爱免费视频| 欧美一区二区视频在线观看2022 | 欧美成人黄色网址| 久久―日本道色综合久久| 日韩经典在线观看| 精品国产一区二区国模嫣然| 尤物在线网址| 91九色极品视频| 欧美在线影院| 天天色天天干天天色| 亚洲欧洲av在线| 97人妻人人澡人人爽人人精品| 色综合伊人色综合网| 91亚洲精品| 午夜一区二区三区| 欧美a级一区二区| 貂蝉被到爽流白浆在线观看| 欧美日韩亚洲综合在线 | 久久久久久久999| 97se亚洲国产一区二区三区| 男人添女人下部视频免费| 国产成人精品综合在线观看 | 一本大道av伊人久久综合| 深夜福利在线视频| 欧美最顶级的aⅴ艳星| 夜夜春成人影院| 国产av人人夜夜澡人人爽| 欧美国产一区二区| 97成人在线观看| 欧美国产日产韩国视频| 91久久精品无嫩草影院 | 黄在线观看免费网站ktv| 精品国产一区二区三区日日嗨| 日韩午夜免费视频| 久久精品成人av| 欧美日韩精品综合在线| 国产最新在线| 高清国产一区| 国产亚洲网站| 欧美人妻一区二区三区| 欧美日韩高清一区二区| 污污的网站在线免费观看| 国产日韩在线一区二区三区| 久久激情婷婷| 亚洲波多野结衣| 亚洲国产精品小视频| gogo亚洲高清大胆美女人体| 在线国产精品网| 北条麻妃国产九九精品视频| 秋霞av一区二区三区| 久久精品2019中文字幕| 高清精品xnxxcom| 日本免费观看网站| 一二三区精品视频| 日韩美女一级视频| 成人一区二区电影| 亚洲美女网站| 中文字幕91视频| 精品日韩成人av| 高清在线一区| 国产真人做爰毛片视频直播| 欧美国产精品中文字幕| 午夜精品久久久久久久91蜜桃| 热99久久精品| 欧美一区网站| 香蕉视频久久久| 欧美精品一区二区三区很污很色的| 日韩一级二级| 欧美久久在线观看| 国产精品色哟哟| 亚洲三区在线观看无套内射| 成人欧美一区二区三区在线湿哒哒 | 91尤物视频在线观看| 国产精选久久久| 国产成人综合精品|