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

小小ArrayList,居然這么多坑?!

開發 前端
Arrays.asList 得到的是 Arrays 的內部類 ArrayList,List.subList 得到的是 ArrayList 的內部類 SubList,不能把這兩個內部類轉換為 ArrayList 使用。

今天,我來和你說說 List 列表操作有哪些坑。

Java 的集合類包括 Map 和 Collection 兩大類。Collection 包括 List、Set 和 Queue 三個小類,其中 List 列表集合是最重要也是所有業務代碼都會用到的。今天,我們就從把數組轉換為 List 集合、對 List 進行切片操作、List 搜索的性能問題等幾個方面著手,來聊聊其中最可能遇到的一些坑。

使用 Arrays.asList 把數據轉換為 List 的三個坑

Java 8 中 Stream 流式處理的各種功能,大大減少了集合類各種操作(投影、過濾、轉換)的代碼量。所以,在業務開發中,我們常常會把原始的數組轉換為 List 類數據結構,來繼續展開各種 Stream 操作。

你可能也想到了,使用 Arrays.asList 方法可以把數組一鍵轉換為 List,但其實沒這么簡單。接下來,就讓我們看看其中的緣由,以及使用 Arrays.asList 把數組轉換為 List 的幾個坑。

在如下代碼中,我們初始化三個數字的 int[]數組,然后使用 Arrays.asList 把數組轉換為 List:

int[] arr = {1, 2, 3};
List list = Arrays.asList(arr);
log.info("list:{} size:{} class:{}", list, list.size(), list.get(0).getClass());

但,這樣初始化的 List 并不是我們期望的包含 3 個數字的 List。通過日志可以發現,這個 List 包含的其實是一個 int 數組,整個 List 的元素個數是 1,元素類型是整數數組。

12:50:39.445 [main] INFO AsListApplication - list:[[I@1c53fd30] size:1 class:class [I

其原因是,只能是把 int 裝箱為 Integer,不可能把 int 數組裝箱為 Integer 數組。我們知道,Arrays.asList 方法傳入的是一個泛型 T 類型可變參數,最終 int 數組整體作為了一個對象成為了泛型類型 T:

public static <T> List<T> asList(T... a) {
    return new ArrayList<>(a);
}

直接遍歷這樣的 List 必然會出現 Bug,修復方式有兩種,如果使用 Java8 以上版本可以使用 Arrays.stream 方法來轉換,否則可以把 int 數組聲明為包裝類型 Integer 數組:

int[] arr1 = {1, 2, 3};
List list1 = Arrays.stream(arr1).boxed().collect(Collectors.toList());
log.info("list:{} size:{} class:{}", list1, list1.size(), list1.get(0).getClass());

Integer[] arr2 = {1, 2, 3};
List list2 = Arrays.asList(arr2);
log.info("list:{} size:{} class:{}", list2, list2.size(), list2.get(0).getClass());

修復后的代碼得到如下日志,可以看到 List 具有三個元素,元素類型是 Integer:

13:10:57.373 [main] INFO AsListApplication - list:[1, 2, 3] size:3 class:class java.lang.Integer

可以看到第一個坑是,不能直接使用 Arrays.asList 來轉換基本類型數組。 那么,我們獲得了正確的 List,是不是就可以像普通的 List 那樣使用了呢?我們繼續往下看。

把三個字符串 1、2、3 構成的字符串數組,使用 Arrays.asList 轉換為 List 后,將原始字符串數組的第二個字符修改為 4,然后為 List 增加一個字符串 5,最后數組和 List 會是怎樣呢?

String[] arr = {"1", "2", "3"};
List list = Arrays.asList(arr);
arr[1] = "4";
try {
    list.add("5");
} catch (Exception ex) {
    ex.printStackTrace();
}

log.info("arr:{} list:{}", Arrays.toString(arr), list);

可以看到,日志里有一個 UnsupportedOperationException,為 List 新增字符串 5 的操作失敗了,而且把原始數組的第二個元素從 2 修改為 4 后,asList 獲得的 List 中的第二個元素也被修改為 4 了:

java.lang.UnsupportedOperationException
  at java.util.AbstractList.add(AbstractList.java:148)
  at java.util.AbstractList.add(AbstractList.java:108)
  at AsListApplication.wrong2(AsListApplication.java:41)
  at AsListApplication.main(AsListApplication.java:15)
13:15:34.699 [main] INFO AsListApplication - arr:[1, 4, 3] list:[1, 4, 3]

這里,又引出了兩個坑。

第二個坑,Arrays.asList 返回的 List 不支持增刪操作。 Arrays.asList 返回的 List 并不是我們期望的 java.util.ArrayList,而是 Arrays 的內部類 ArrayList。ArrayList 內部類繼承自 AbstractList 類,并沒有覆寫父類的 add 方法,而父類中 add 方法的實現,就是拋出 UnsupportedOperationException。相關源碼如下所示:

public static <T> List<T> asList(T... a) {
    return new ArrayList<>(a);
}

private static class ArrayList<E> extends AbstractList<E> implements RandomAccess, java.io.Serializable {
    private final E[] a;
    ArrayList(E[] array) {
        a = Objects.requireNonNull(array);
    }
...
    @Override
    public E set(int index, E element) {
        E oldValue = a[index];
        a[index] = element;
        return oldValue;
    }
    ...
}

public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {

...

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

第三個坑,對原始數組的修改會影響到我們獲得的那個 List。 看一下 ArrayList 的實現,可以發現 ArrayList 其實是直接使用了原始的數組。所以,我們要特別小心,把通過 Arrays.asList 獲得的 List 交給其他方法處理,很容易因為共享了數組,相互修改產生 Bug。

修復方式比較簡單,重新 new 一個 ArrayList 初始化 Arrays.asList 返回的 List 即可:

String[] arr = {"1", "2", "3"};
List list = new ArrayList(Arrays.asList(arr));
arr[1] = "4";

try {
    list.add("5");
} catch (Exception ex) {
    ex.printStackTrace();
}

log.info("arr:{} list:{}", Arrays.toString(arr), list);

修改后的代碼實現了原始數組和 List 的“解耦”,不再相互影響。同時,因為操作的是真正的 ArrayList,add 也不再出錯:

13:34:50.829 [main] INFO AsListApplication - arr:[1, 4, 3] list:[1, 2, 3, 5]

使用 List.subList 進行切片操作居然會導致 OOM?

業務開發時常常要對 List 做切片處理,即取出其中部分元素構成一個新的 List,我們通常會想到使用 List.subList 方法。但,和 Arrays.asList 的問題類似,List.subList 返回的子 List 不是一個普通的 ArrayList。這個子 List 可以認為是原始 List 的視圖,會和原始 List 相互影響。如果不注意,很可能會因此產生 OOM 問題。接下來,我們就一起分析下其中的坑。

如下代碼所示,定義一個名為 data 的靜態 List 來存放 Integer 的 List,也就是說 data 的成員本身是包含了多個數字的 List。循環 1000 次,每次都從一個具有 10 萬個 Integer 的 List 中,使用 subList 方法獲得一個只包含一個數字的子 List,并把這個子 List 加入 data 變量:

private static List<List<Integer>> data = new ArrayList<>();

private static void oom() {
    for (int i = 0; i < 1000; i++) {
        List<Integer> rawList = IntStream.rangeClosed(1, 100000).boxed().collect(Collectors.toList());
        data.add(rawList.subList(0, 1));
    }
}

你可能會覺得,這個 data 變量里面最終保存的只是 1000 個具有 1 個元素的 List,不會占用很大空間,但程序運行不久就出現了 OOM:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
  at java.util.Arrays.copyOf(Arrays.java:3181)
  at java.util.ArrayList.grow(ArrayList.java:265)

出現 OOM 的原因是,循環中的 1000 個具有 10 萬個元素的 List 始終得不到回收,因為它始終被 subList 方法返回的 List 強引用。那么,返回的子 List 為什么會強引用原始的 List,它們又有什么關系呢?我們再繼續做實驗觀察一下這個子 List 的特性。

首先初始化一個包含數字 1 到 10 的 ArrayList,然后通過調用 subList 方法取出 2、3、4;隨后刪除這個 SubList 中的元素數字 3,并打印原始的 ArrayList;最后為原始的 ArrayList 增加一個元素數字 0,遍歷 SubList 輸出所有元素:

List<Integer> list = IntStream.rangeClosed(1, 10).boxed().collect(Collectors.toList());
List<Integer> subList = list.subList(1, 4);
System.out.println(subList);
subList.remove(1);
System.out.println(list);
list.add(0);
try {
    subList.forEach(System.out::println);
} catch (Exception ex) {
    ex.printStackTrace();
}

代碼運行后得到如下輸出:

[2, 3, 4]
[1, 2, 4, 5, 6, 7, 8, 9, 10]
java.util.ConcurrentModificationException
  at java.util.ArrayList$SubList.checkForComodification(ArrayList.java:1239)
  at java.util.ArrayList$SubList.listIterator(ArrayList.java:1099)
  at java.util.AbstractList.listIterator(AbstractList.java:299)
  at java.util.ArrayList$SubList.iterator(ArrayList.java:1095)
  at java.lang.Iterable.forEach(Iterable.java:74)

可以看到兩個現象:

原始 List 中數字 3 被刪除了,說明刪除子 List 中的元素影響到了原始 List;

嘗試為原始 List 增加數字 0 之后再遍歷子 List,會出現 ConcurrentModificationException。

我們分析下 ArrayList 的源碼,看看為什么會是這樣。

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable {

    protected transient int modCount = 0;

  private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

  public void add(int index, E element) {
    rangeCheckForAdd(index);
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    elementData[index] = element;
    size++;
  }

  public List<E> subList(int fromIndex, int toIndex) {
    subListRangeCheck(fromIndex, toIndex, size);
    return new SubList(this, offset, fromIndex, toIndex);
  }

  private class SubList extends AbstractList<E> implements RandomAccess {

    private final AbstractList<E> parent;
    private final int parentOffset;
    private final int offset;
    int size;

    SubList(AbstractList<E> parent,
          int offset, int fromIndex, int toIndex) {
        this.parent = parent;
        this.parentOffset = fromIndex;
        this.offset = offset + fromIndex;
        this.size = toIndex - fromIndex;
        this.modCount = ArrayList.this.modCount;
    }

        public E set(int index, E element) {
            rangeCheck(index);
            checkForComodification();
            return l.set(index+offset, element);
        }

    public ListIterator<E> listIterator(final int index) {
                checkForComodification();
                ...
    }

    private void checkForComodification() {
        if (ArrayList.this.modCount != this.modCount)
            throw new ConcurrentModificationException();
    }
    ...
  }
}

第一,ArrayList 維護了一個叫作 modCount 的字段,表示集合結構性修改的次數。所謂結構性修改,指的是影響 List 大小的修改,所以 add 操作必然會改變 modCount 的值。

第二,分析第 21 到 24 行的 subList 方法可以看到,獲得的 List 其實是內部類 SubList,并不是普通的 ArrayList,在初始化的時候傳入了 this。

第三,分析第 26 到 39 行代碼可以發現,這個 SubList 中的 parent 字段就是原始的 List。SubList 初始化的時候,并沒有把原始 List 中的元素復制到獨立的變量中保存。我們可以認為 SubList 是原始 List 的視圖,并不是獨立的 List。雙方對元素的修改會相互影響,而且 SubList 強引用了原始的 List,所以大量保存這樣的 SubList 會導致 OOM。

第四,分析第 47 到 55 行代碼可以發現,遍歷 SubList 的時候會先獲得迭代器,比較原始 ArrayList modCount 的值和 SubList 當前 modCount 的值。獲得了 SubList 后,我們為原始 List 新增了一個元素修改了其 modCount,所以判等失敗拋出 ConcurrentModificationException 異常。

既然 SubList 相當于原始 List 的視圖,那么避免相互影響的修復方式有兩種:

一種是,不直接使用 subList 方法返回的 SubList,而是重新使用 new ArrayList,在構造方法傳入 SubList,來構建一個獨立的 ArrayList;

另一種是,對于 Java 8 使用 Stream 的 skip 和 limit API 來跳過流中的元素,以及限制流中元素的個數,同樣可以達到 SubList 切片的目的。

//方式一:
List<Integer> subList = new ArrayList<>(list.subList(1, 4));

//方式二:
List<Integer> subList = list.stream().skip(1).limit(3).collect(Collectors.toList());

修復后代碼輸出如下:

[2, 3, 4]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
2
4

可以看到,刪除 SubList 的元素不再影響原始 List,而對原始 List 的修改也不會再出現 List 迭代異常。

一定要讓合適的數據結構做合適的事情

在使用 List 集合類的時候,不注意使用場景也會遇見兩個常見誤區。

第一個誤區是,使用數據結構不考慮平衡時間和空間。

首先,定義一個只有一個 int 類型訂單號字段的 Order 類:

@Data
@NoArgsConstructor
@AllArgsConstructor
static class Order {
    private int orderId;
}

然后,定義一個包含 elementCount 和 loopCount 兩個參數的 listSearch 方法,初始化一個具有 elementCount 個訂單對象的 ArrayList,循環 loopCount 次搜索這個 ArrayList,每次隨機搜索一個訂單號:

private static Object listSearch(int elementCount, int loopCount) {

    List<Order> list = IntStream.rangeClosed(1, elementCount).mapToObj(i -> new Order(i)).collect(Collectors.toList());

    IntStream.rangeClosed(1, loopCount).forEach(i -> {
        int search = ThreadLocalRandom.current().nextInt(elementCount);
        Order result = list.stream().filter(order -> order.getOrderId() == search).findFirst().orElse(null);
        Assert.assertTrue(result != null && result.getOrderId() == search);
    });
    return list;
}

隨后,定義另一個 mapSearch 方法,從一個具有 elementCount 個元素的 Map 中循環 loopCount 次查找隨機訂單號。Map 的 Key 是訂單號,Value 是訂單對象:

private static Object mapSearch(int elementCount, int loopCount) {

    Map<Integer, Order> map = IntStream.rangeClosed(1, elementCount).boxed().collect(Collectors.toMap(Function.identity(), i -> new Order(i)));

    IntStream.rangeClosed(1, loopCount).forEach(i -> {
        int search = ThreadLocalRandom.current().nextInt(elementCount);
        Order result = map.get(search);
        Assert.assertTrue(result != null && result.getOrderId() == search);
    });
    return map;
}

我們知道,搜索 ArrayList 的時間復雜度是 O(n),而 HashMap 的 get 操作的時間復雜度是 O(1)。所以,要對大 List 進行單值搜索的話,可以考慮使用 HashMap,其中 Key 是要搜索的值,Value 是原始對象,會比使用 ArrayList 有非常明顯的性能優勢。

如下代碼所示,對 100 萬個元素的 ArrayList 和 HashMap,分別調用 listSearch 和 mapSearch 方法進行 1000 次搜索:

int elementCount = 1000000;

int loopCount = 1000;

StopWatch stopWatch = new StopWatch();
stopWatch.start("listSearch");

Object list = listSearch(elementCount, loopCount);
System.out.println(ObjectSizeCalculator.getObjectSize(list));
stopWatch.stop();
stopWatch.start("mapSearch");

Object map = mapSearch(elementCount, loopCount);
stopWatch.stop();
System.out.println(ObjectSizeCalculator.getObjectSize(map));
System.out.println(stopWatch.prettyPrint());

通過運行結果可以看到,僅僅是 1000 次搜索,listSearch 方法耗時 3.3 秒,而 mapSearch 耗時僅僅 108 毫秒。

即使我們要搜索的不是單值而是條件區間,也可以嘗試使用 HashMap 來進行“搜索性能優化”。如果你的條件區間是固定的話,可以提前把 HashMap 按照條件區間進行分組,Key 就是不同的區間。

的確,如果業務代碼中有頻繁的大 ArrayList 搜索,使用 HashMap 性能會好很多。類似,如果要對大 ArrayList 進行去重操作,也不建議使用 contains 方法,而是可以考慮使用 HashSet 進行去重。說到這里,還有一個問題,使用 HashMap 是否會犧牲空間呢?

為此,我們使用 ObjectSizeCalculator 工具打印 ArrayList 和 HashMap 的內存占用,可以看到 ArrayList 占用內存 21M,而 HashMap 占用的內存達到了 72M,是 List 的三倍多。進一步使用 MAT 工具分析堆可以再次證明,ArrayList 在內存占用上性價比很高,77% 是實際的數據(如第 1 個圖所示,16000000/20861992),而 HashMap 的“含金量”只有 22%。

所以,在應用內存吃緊的情況下,我們需要考慮是否值得使用更多的內存消耗來換取更高的性能。這里我們看到的是平衡的藝術,空間換時間,還是時間換空間,只考慮任何一個方面都是不對的。

第二個誤區是,過于迷信教科書的大 O 時間復雜度。

數據結構中要實現一個列表,有基于連續存儲的數組和基于指針串聯的鏈表兩種方式。在 Java 中,有代表性的實現是 ArrayList 和 LinkedList,前者背后的數據結構是數組,后者則是(雙向)鏈表。

在選擇數據結構的時候,我們通常會考慮每種數據結構不同操作的時間復雜度,以及使用場景兩個因素。查看這里,你可以看到數組和鏈表大 O 時間復雜度的顯著差異:

對于數組,隨機元素訪問的時間復雜度是 O(1),元素插入操作是 O(n);

對于鏈表,隨機元素訪問的時間復雜度是 O(n),元素插入操作是 O(1)。

那么,在大量的元素插入、很少的隨機訪問的業務場景下,是不是就應該使用 LinkedList 呢?接下來,我們寫一段代碼測試下兩者隨機訪問和插入的性能吧。

定義四個參數一致的方法,分別對元素個數為 elementCount 的 LinkedList 和 ArrayList,循環 loopCount 次,進行隨機訪問和增加元素到隨機位置的操作:

//LinkedList訪問
private static void linkedListGet(int elementCount, int loopCount) {
    List<Integer> list = IntStream.rangeClosed(1, elementCount).boxed().collect(Collectors.toCollection(LinkedList::new));
    IntStream.rangeClosed(1, loopCount).forEach(i -> list.get(ThreadLocalRandom.current().nextInt(elementCount)));
}

//ArrayList訪問
private static void arrayListGet(int elementCount, int loopCount) {
    List<Integer> list = IntStream.rangeClosed(1, elementCount).boxed().collect(Collectors.toCollection(ArrayList::new));
    IntStream.rangeClosed(1, loopCount).forEach(i -> list.get(ThreadLocalRandom.current().nextInt(elementCount)));
}

//LinkedList插入
private static void linkedListAdd(int elementCount, int loopCount) {
    List<Integer> list = IntStream.rangeClosed(1, elementCount).boxed().collect(Collectors.toCollection(LinkedList::new));
    IntStream.rangeClosed(1, loopCount).forEach(i -> list.add(ThreadLocalRandom.current().nextInt(elementCount),1));
}

//ArrayList插入
private static void arrayListAdd(int elementCount, int loopCount) {
    List<Integer> list = IntStream.rangeClosed(1, elementCount).boxed().collect(Collectors.toCollection(ArrayList::new));
    IntStream.rangeClosed(1, loopCount).forEach(i -> list.add(ThreadLocalRandom.current().nextInt(elementCount),1));
}

測試代碼如下,10 萬個元素,循環 10 萬次:

int elementCount = 100000;

int loopCount = 100000;

StopWatch stopWatch = new StopWatch();
stopWatch.start("linkedListGet");
linkedListGet(elementCount, loopCount);
stopWatch.stop();

stopWatch.start("arrayListGet");
arrayListGet(elementCount, loopCount);
stopWatch.stop();
System.out.println(stopWatch.prettyPrint());

StopWatch stopWatch2 = new StopWatch();
stopWatch2.start("linkedListAdd");
linkedListAdd(elementCount, loopCount);
stopWatch2.stop();

stopWatch2.start("arrayListAdd");
arrayListAdd(elementCount, loopCount);
stopWatch2.stop();

System.out.println(stopWatch2.prettyPrint());

運行結果可能會讓你大跌眼鏡。在隨機訪問方面,我們看到了 ArrayList 的絕對優勢,耗時只有 11 毫秒,而 LinkedList 耗時 6.6 秒,這符合上面我們所說的時間復雜度;但,隨機插入操作居然也是 LinkedList 落敗,耗時 9.3 秒,ArrayList 只要 1.5 秒:

---------------------------------------------
ns         %     Task name
---------------------------------------------
6604199591  100%  linkedListGet
011494583  000%  arrayListGet

StopWatch '': running time = 10729378832 ns
---------------------------------------------
ns         %     Task name
---------------------------------------------
9253355484  086%  linkedListAdd
1476023348  014%  arrayListAdd

翻看 LinkedList 源碼發現,插入操作的時間復雜度是 O(1) 的前提是,你已經有了那個要插入節點的指針。但,在實現的時候,我們需要先通過循環獲取到那個節點的 Node,然后再執行插入操作。前者也是有開銷的,不可能只考慮插入操作本身的代價:

public void add(int index, E element) {

    checkPositionIndex(index);
    if (index == size)
        linkLast(element);
    else
        linkBefore(element, node(index));
}

Node<E> node(int index) {
    // assert isElementIndex(index);
    if (index < (size >> 1)) {
        Node<E> x = first;
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {
        Node<E> x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}

所以,對于插入操作,LinkedList 的時間復雜度其實也是 O(n)。繼續做更多實驗的話你會發現,在各種常用場景下,LinkedList 幾乎都不能在性能上勝出 ArrayList。

諷刺的是,LinkedList 的作者約書亞 · 布洛克(Josh Bloch),在其推特上回復別人時說,雖然 LinkedList 是我寫的但我從來不用,有誰會真的用嗎?

圖片圖片

這告訴我們,任何東西理論上和實際上是有差距的,請勿迷信教科書的理論,最好在下定論之前實際測試一下。拋開算法層面不談,由于 CPU 緩存、內存連續性等問題,鏈表這種數據結構的實現方式對性能并不友好,即使在它最擅長的場景都不一定可以發揮威力。

小結

今天,我分享了若干和 List 列表相關的錯誤案例,基本都是由“想當然”導致的。

第一,想當然認為,Arrays.asList 和 List.subList 得到的 List 是普通的、獨立的 ArrayList,在使用時出現各種奇怪的問題。

Arrays.asList 得到的是 Arrays 的內部類 ArrayList,List.subList 得到的是 ArrayList 的內部類 SubList,不能把這兩個內部類轉換為 ArrayList 使用。

Arrays.asList 直接使用了原始數組,可以認為是共享“存儲”,而且不支持增刪元素;List.subList 直接引用了原始的 List,也可以認為是共享“存儲”,而且對原始 List 直接進行結構性修改會導致 SubList 出現異常。

對 Arrays.asList 和 List.subList 容易忽略的是,新的 List 持有了原始數據的引用,可能會導致原始數據也無法 GC 的問題,最終導致 OOM。

第二,想當然認為,Arrays.asList 一定可以把所有數組轉換為正確的 List。當傳入基本類型數組的時候,List 的元素是數組本身,而不是數組中的元素。

第三,想當然認為,內存中任何集合的搜索都是很快的,結果在搜索超大 ArrayList 的時候遇到性能問題。我們考慮利用 HashMap 哈希表隨機查找的時間復雜度為 O(1) 這個特性來優化性能,不過也要考慮 HashMap 存儲空間上的代價,要平衡時間和空間。

第四,想當然認為,鏈表適合元素增刪的場景,選用 LinkedList 作為數據結構。在真實場景中讀寫增刪一般是平衡的,而且增刪不可能只是對頭尾對象進行操作,可能在 90% 的情況下都得不到性能增益,建議使用之前通過性能測試評估一下。

責任編輯:武曉燕 來源: JAVA日知錄
相關推薦

2021-09-28 12:25:30

數據庫

2021-08-06 16:57:39

存儲Redis數據類型

2020-06-01 08:04:18

三目運算符代碼

2025-04-18 09:31:19

2025-07-23 10:13:57

2017-08-11 14:21:33

軟件開發前端框架

2023-07-17 08:21:52

漏洞版本項目

2022-06-14 10:48:55

排查故障

2024-07-12 09:35:38

前端工具檢驗

2018-06-26 15:00:24

Docker安全風險

2023-11-13 08:49:54

2024-02-20 08:09:51

Java 8DateUtilsDate工具類

2022-07-06 11:47:27

JAVAfor循環

2023-09-11 11:53:51

物聯網協議物聯網

2021-06-10 09:00:33

單例模式數據庫

2017-12-21 19:38:50

潤乾中間表

2022-07-26 23:43:29

編程語言開發Java

2021-01-14 05:08:44

編譯鏈接

2021-01-29 08:52:10

App微信移動應用

2023-05-31 07:57:12

筆記本電腦信譽度
點贊
收藏

51CTO技術棧公眾號

国产在线视频一区二区三区| 国产精品密蕾丝视频下载| 亚洲日本一区二区| 精品1区2区| 国产91av在线播放| 一区二区三区网站| 亚洲精品视频免费| 手机在线免费毛片| 91av亚洲| 亚洲免费大片在线观看| 欧美高清性xxxxhd| 国产精品久久久久久在线| 亚洲日本黄色| 日韩在线免费av| 无码人妻aⅴ一区二区三区| 欧美视频第一| 日韩欧美在线一区| 久久国产精品免费观看| 黄色毛片在线看| 成人污视频在线观看| 国产精品视频精品| 美日韩一二三区| 一区福利视频| 美日韩精品免费视频| xxxx日本黄色| 亚洲精品一级二级三级| 欧美www视频| 国产成人美女视频| 久久久一本精品| 姬川优奈aav一区二区| 亚洲国产精品女人| 最新97超碰在线| 国产区在线观看成人精品 | 成人午夜精品久久久久久久蜜臀| 成人在线播放视频| 久久综合五月天婷婷伊人| 成人91免费视频| 国产三级伦理片| 蜜桃视频一区二区三区在线观看| 欧美亚洲第一区| 国产无套粉嫩白浆内谢| 欧美激情视频一区二区三区在线播放 | 久久精品不卡| 伊人久久大香线蕉av一区二区| 中文在线永久免费观看| 福利在线一区| 精品第一国产综合精品aⅴ| 999热精品视频| 成人综合日日夜夜| 777午夜精品免费视频| 日韩av在线中文| 亚洲精品成a人ⅴ香蕉片| 欧美午夜理伦三级在线观看| 一级在线免费视频| 欧美成人一二区| 欧美二区乱c少妇| 韩国三级与黑人| 亚州一区二区| 精品对白一区国产伦| 亚洲国产精品自拍视频| 精品女人视频| 亚洲人成人99网站| 超碰人人干人人| 国产精品88久久久久久| 久久婷婷国产麻豆91天堂| 日韩a级片在线观看| 欧美福利视频| 97在线视频一区| 国产女主播喷水视频在线观看| 日韩不卡免费视频| 国产精品一区二区久久精品| 国产精品久久免费| 丁香天五香天堂综合| 精品一区2区三区| 国产女主播在线直播| 国产精品久久久久永久免费观看 | 国产精品久久久久免费a∨| 国产精华一区| 色窝窝无码一区二区三区| 97久久精品人人爽人人爽蜜臀| 久久riav二区三区| gogogo高清在线观看免费完整版| 中文字幕一区二区三区视频| 九九久久九九久久| 成人美女黄网站| 欧美另类videos死尸| 精品人妻一区二区免费| 伊人成综合网伊人222| 日韩最新免费不卡| 日本精品人妻无码77777| 国产精品免费看| 成人黄色生活片| 少妇人妻精品一区二区三区| 国产欧美日韩在线| 天堂8在线天堂资源bt| 欧美一级大黄| 欧美成人福利视频| 国产极品视频在线观看| 国产精品vip| 国产精品入口尤物| 色呦呦视频在线| 国产精品久久久久久久久图文区| 免费看毛片的网址| 人人精品久久| 亚洲男人的天堂在线播放| 翔田千里88av中文字幕| 香蕉久久夜色精品国产| 91在线|亚洲| 国产福利电影在线| 亚洲国产日韩a在线播放性色| 91看片在线免费观看| 久久午夜影院| 久久久国产一区二区| 国产在线观看黄色| 成人性生交大片| 中文字幕在线中文字幕日亚韩一区| 国产高清中文字幕在线| 3d成人动漫网站| www亚洲色图| 亚洲男人影院| 精品欧美一区二区久久久伦 | 国产精品毛片久久久| 久久精品中文字幕一区| 免费av中文字幕| 99久久国产综合色|国产精品| 欧美精品久久96人妻无码| 不卡亚洲精品| 国产一区二区av| 精品人妻无码一区二区性色| 成人网在线播放| 日本精品久久久久久久久久| 国产精一区二区| www.日韩欧美| 亚洲视频在线观看免费视频| 久久精品一区二区三区四区 | 蜜芽一区二区三区| 日本一区精品| 免费电影日韩网站| 精品丝袜一区二区三区| 欧美三级韩国三级日本三斤在线观看| 成人免费精品视频| 妞干网视频在线观看| 97品白浆高清久久久久久| 久久777国产线看观看精品| 国产精品无码久久av| 国产精品久99| 手机在线视频一区| 亚洲日韩中文字幕一区| 国产亚洲欧美视频| 波多野结衣电影在线播放| 久久夜色精品国产欧美乱极品| 国产 日韩 亚洲 欧美| 国产日韩三级| 欧美亚洲视频在线看网址| 国产自产一区二区| 香蕉影视欧美成人| bl动漫在线观看| 亚洲日本激情| 日本不卡一区| 岛国精品在线| 日韩中文字幕在线播放| 91精品国自产| 亚洲精品成人a在线观看| 女人扒开双腿让男人捅| 在线观看不卡| 欧美国产一区二区在线| 午夜激情成人网| 久久精品男人天堂| 国产黄色免费大片| 亚洲成人精品一区二区| 色婷婷在线影院| 捆绑调教一区二区三区| 无码毛片aaa在线| 极品国产人妖chinesets亚洲人妖 激情亚洲另类图片区小说区 | 国产在线观看免费| 欧美性大战久久| 久久久久久久久久97| 国产经典欧美精品| 国产极品粉嫩福利姬萌白酱| 不卡中文字幕| 91精品国产综合久久久久久丝袜| 国产盗摄一区二区| 亚洲精品永久免费| 91久久精品无码一区二区| 亚洲综合激情另类小说区| 一区二区视频观看| 免费成人在线观看视频| 女人帮男人橹视频播放| 精品国产不卡| 国产91亚洲精品一区二区三区| 成人动漫一区| 欧美精品免费看| 欧美孕妇性xxxⅹ精品hd| 7777精品伊人久久久大香线蕉的| 精品无码人妻一区二区三区| 国产日韩欧美高清| 麻豆免费在线观看视频| 日韩电影一二三区| 日韩黄色短视频| 久久电影院7| 久久久久成人精品免费播放动漫| 中文幕av一区二区三区佐山爱| 韩日欧美一区二区| 久久综合网导航| 精品一区二区亚洲| 精品久久国产视频| 在线观看国产91| 日韩av在线播| 亚洲三级免费电影| 蜜乳av中文字幕| 成人免费高清在线观看| 中文字幕线观看| 久久动漫亚洲| 大伊香蕉精品视频在线| 91日韩视频| 欧美日韩电影一区二区| 777久久精品| 96精品久久久久中文字幕| av高清不卡| 国内外成人免费激情在线视频| 免费在线观看黄色| 亚洲午夜久久久久久久| 性xxxx视频播放免费| 日韩精品一区在线| 国产免费av观看| 欧美片在线播放| 午夜视频网站在线观看| 日韩欧美综合在线视频| 久久精品国产亚洲av麻豆色欲| ●精品国产综合乱码久久久久| 日韩中文字幕有码| 99re亚洲国产精品| 国产综合内射日韩久| 国产成人无遮挡在线视频| 激情文学亚洲色图| 麻豆成人免费电影| 亚洲一级片免费| 青青国产91久久久久久| 国产视频在线视频| 久久激情婷婷| 毛片一区二区三区四区| 亚洲免费综合| 日韩精品免费播放| 日韩中文字幕1| 久久国产亚洲精品无码| 一区二区毛片| 成人免费在线小视频| 国产日韩欧美三级| 成年人免费在线播放| 国产精品美女| 人人干人人视频| 日韩高清在线观看| 亚洲无吗一区二区三区| 奇米777欧美一区二区| 中日韩av在线播放| 国产美女视频一区| 中国男女全黄大片| av成人动漫在线观看| 欧美 变态 另类 人妖| 久久久国际精品| 免费黄色片网站| 亚洲欧洲av在线| 久久丫精品久久丫| 欧美日韩精品国产| 在线免费观看av网址| 欧美日韩色一区| 国产按摩一区二区三区| 精品国产一区二区三区忘忧草 | 久久久国产精华液999999| 亚洲综合专区| 给我免费播放片在线观看| 久久国产成人| 911av视频| 成人午夜伦理影院| 久久美女免费视频| 国产精品久久久久影院| 国产无遮挡又黄又爽又色| 精品欧美激情精品一区| 中文字幕你懂的| 日韩视频在线永久播放| 亚洲 欧美 自拍偷拍| 中文字幕国产精品久久| 日韩三级免费| 国产成人精品久久二区二区91| 久久三级毛片| 精品国产乱码久久久久久88av| 成人在线亚洲| 国产一二三在线视频| 日本怡春院一区二区| 手机av在线网站| 26uuu精品一区二区三区四区在线 26uuu精品一区二区在线观看 | 亚洲日本一区二区三区在线| 欧美日韩三区四区| 午夜精品国产| 亚洲成人天堂网| 99久久99久久久精品齐齐| 开心激情五月网| 黄网站色欧美视频| 国产黄色美女视频| 中文一区二区视频| 成人国产电影在线观看| 成人黄色影片在线| 亚洲欧洲av| 精品国偷自产一区二区三区| 麻豆精品国产传媒mv男同| 精品人妻一区二区三区日产| 自拍偷拍亚洲激情| 免费的毛片视频| 亚洲成年人在线播放| 免费在线看黄网站| 日本一区二区三区在线播放| 91夜夜蜜桃臀一区二区三区| 国产成年人在线观看| 天堂久久久久va久久久久| 香港三日本8a三级少妇三级99| 亚洲欧洲www| 最近中文字幕在线观看视频| 日韩av中文字幕在线免费观看 | 欧美专区第一页| 澳门久久精品| 一区中文字幕在线观看| 日韩电影在线观看电影| 中文字幕丰满乱子伦无码专区| 亚洲精品乱码久久久久| 亚洲在线精品视频| 国产一区二区三区精品久久久| 欧亚av在线| 好吊色欧美一区二区三区四区| 欧美婷婷在线| 91精产国品一二三| 亚洲人一二三区| 亚洲一区在线观| 中文字幕日韩综合av| 88xx成人网| 亚洲精品久久久久久一区二区| 久久久精品性| 制服 丝袜 综合 日韩 欧美| 欧美性xxxxxxx| 色天堂在线视频| 欧美最猛性xxxx| 久久av电影| 欧美精品成人网| 国产亚洲欧美一级| 波多野结衣家庭主妇| 亚洲三级免费看| 精品成人av| 亚洲国产一区二区三区在线播| 日本女人一区二区三区| 久久精品无码一区| 欧美在线一区二区三区| 波多野结衣在线影院| 国产精品综合网站| 婷婷激情图片久久| 精品人妻一区二区三| 亚洲午夜激情网站| 日韩一级免费视频| 欧洲成人在线视频| 精品成人影院| 亚洲va在线va天堂va偷拍| 中文字幕佐山爱一区二区免费| 99国产精品99| 欧美黑人狂野猛交老妇| 久久99精品久久久久久欧洲站| 少妇人妻在线视频| 久久久精品黄色| 国产精品无码天天爽视频| 欧美黑人xxxx| 女人av一区| 一级做a免费视频| 亚洲亚洲人成综合网络| 日韩大胆视频| 国产一区二区在线免费视频| 在线中文一区| 国产伦精品一区二区三区妓女| 在线亚洲+欧美+日本专区| 男人的天堂在线视频免费观看 | 久久精品无码一区二区三区毛片| 亚洲一区二区三区爽爽爽爽爽| 涩涩视频在线观看免费| 国产日韩av在线| 亚洲午夜黄色| 久久婷婷五月综合| 精品国内片67194| 影视一区二区三区| 亚洲黄色网址在线观看| 99久久er热在这里只有精品66| 91黄色在线视频| 午夜精品一区二区三区在线播放| 波多野结衣在线播放一区| 欧美69精品久久久久久不卡 | 国产又大又粗又长| 国内偷自视频区视频综合| 久久在线视频免费观看| 娇妻高潮浓精白浆xxⅹ| 欧美亚洲综合另类| xxxx在线视频| 在线精品日韩| 久久日一线二线三线suv| 国产女人18毛片水18精| 国产成人精品久久|