精品欧美一区二区三区在线观看 _久久久久国色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技術棧公眾號

欧美va天堂va视频va在线| 久久精品久久久精品美女| 精品国产青草久久久久福利| 亚洲人成无码网站久久99热国产 | 91久久精品国产91性色tv| 日韩精品伦理第一区| 91欧美日韩麻豆精品| 亚洲精华国产欧美| 中文字幕av日韩| 日本精品一二三区| 深夜视频一区二区| 亚洲美女免费在线| 青青草成人激情在线| www.天堂在线| 青青草国产精品97视觉盛宴| 色中色综合影院手机版在线观看| 国产人妻一区二区| 澳门成人av| 欧美午夜寂寞影院| 成年人网站免费视频| 麻豆网站在线看| 久久久亚洲午夜电影| 不卡视频一区二区三区| 中文字幕观看在线| 亚洲一区二区免费看| 久久视频免费在线播放| 国产又爽又黄无码无遮挡在线观看| 亚洲黑人在线| 91成人国产精品| 一二三四视频社区在线| 99在线视频观看| 国产精品视频观看| 欧美日韩精品一区| 日日夜夜精品免费| 国产精品一区二区男女羞羞无遮挡 | 91久久精品视频| 亚洲婷婷综合网| 亚洲国产精品第一区二区三区 | 国产日韩三区| 亚洲av无码专区在线| 久久爱www久久做| 国产精品久久久久77777| 成人毛片18女人毛片| 1024日韩| 国产综合在线看| 国产亚洲精品久久久久久无几年桃| 91综合久久| 在线日韩第一页| 级毛片内射视频| 伊人久久大香线蕉| 精品亚洲aⅴ在线观看| 日韩精品一区二区三区高清免费| 亚洲码欧美码一区二区三区| 欧美一二三区在线| 性鲍视频在线观看| 韩国三级大全久久网站| 91精品国产麻豆国产自产在线 | 400部精品国偷自产在线观看| 69av在线| 国产精品久久久久aaaa| 亚洲综合首页| 制服丝袜在线播放| 一区二区三区免费观看| 99er在线视频| 春色校园综合激情亚洲| 欧美日韩国产影院| 99热成人精品热久久66| 日韩久久一区二区三区| 欧美日韩成人一区| 国内av一区二区| 久久精品一级| 精品国产欧美一区二区| 双性尿奴穿贞c带憋尿| 九九综合九九| 神马久久桃色视频| 极品盗摄国产盗摄合集| 伊人久久成人| 日本国产一区二区三区| 最近中文字幕av| 狠狠色2019综合网| 国产欧美欧洲| 精华区一区二区三区| 日韩毛片视频在线看| 无码人妻精品一区二区蜜桃网站| av最新在线| 欧洲av一区二区嗯嗯嗯啊| 久久久久xxxx| 黄色网一区二区| 在线成人免费网站| 久久久久久久久久久97| 久久久夜精品| 亚洲自拍av在线| 手机福利小视频在线播放| 国产精品视频九色porn| 777av视频| 另类一区二区| 亚洲精品www久久久| 日本伦理一区二区三区| 激情综合亚洲| 国产精品美女主播| 国产刺激高潮av| 国产欧美精品一区| 人妻无码久久一区二区三区免费| 99精品国自产在线| 精品福利二区三区| 国产黄a三级三级| 国产欧美激情| 亚洲一区二区免费| 国产视频第一区| 亚洲国产精品一区二区久久| 婷婷六月天在线| 亚洲精品一区二区三区在线| 国产亚洲精品久久久久久牛牛| 中文字幕在线有码| 日本欧美韩国一区三区| 国产一区二区三区免费不卡| 免费av在线网站| 色婷婷亚洲精品| 黄色性视频网站| 99热国内精品| 欧美在线视频免费| 日本黄色一区二区三区| 综合久久综合久久| 亚洲欧美自偷自拍另类| 亚洲三级网页| 久久久久久国产免费| 国产绳艺sm调教室论坛| 中文在线免费一区三区高中清不卡| koreanbj精品视频一区| 日韩视频1区| 久久久成人精品| 自拍偷拍色综合| 久久久久国产成人精品亚洲午夜 | 国产网红在线观看| 欧美一区二区视频在线观看2022| 免费观看a级片| 亚洲一区欧美激情| 精品视频第一区| 免费电影网站在线视频观看福利| 91麻豆精品国产| 成人18视频免费69| 久久成人av少妇免费| 午夜精品视频在线观看一区二区 | 一本色道久久加勒比精品| 欧类av怡春院| 99精品欧美| 精品久久久久久亚洲| 2020日本在线视频中文字幕| 欧美成人女星排名| 久久亚洲AV无码| 国产成人av电影免费在线观看| 天天干天天色天天爽| 91精品国产色综合久久不卡粉嫩| 在线亚洲男人天堂| 一级片在线免费观看视频| 国产精品情趣视频| 中文字幕中文在线| 一区二区在线影院| 91亚洲精品丁香在线观看| 黄网站app在线观看| 欧美丰满嫩嫩电影| 免费毛片在线播放免费| 成人综合婷婷国产精品久久蜜臀 | 人人干在线观看| 精品在线一区二区| 久久久久久久久影视| 伊人久久噜噜噜躁狠狠躁| 久久久久久久一| 婷婷综合激情网| 在线免费精品视频| 欧美xxxooo| 国产成人精品1024| 91视频 -- 69xx| 教室别恋欧美无删减版| 国产欧美韩国高清| 四虎av在线| 日韩高清免费观看| 成人黄色片在线观看| 亚洲手机成人高清视频| 日本50路肥熟bbw| 免费日韩一区二区| 在线观看日本一区| 国产精品nxnn| 国产成一区二区| 成人看av片| 日韩av影视在线| 欧美在线视频精品| 一区二区三区.www| 醉酒壮男gay强迫野外xx| 日本亚洲视频在线| bt天堂新版中文在线地址| 夜色77av精品影院| 91九色国产社区在线观看| www视频在线观看| 一区二区欧美激情| 秋霞欧美在线观看| 欧美三级在线视频| 日操夜操天天操| 国产精品久久精品日日| 一区二区免费在线观看视频| 美女精品自拍一二三四| 欧美视频在线观看视频| 日韩系列欧美系列| 久久av一区二区三区漫画| 日韩免费大片| 欧美中文在线观看| 亚洲精品天堂| 中文字幕精品国产| 图片区 小说区 区 亚洲五月| 在线成人午夜影院| 在线观看日本视频| 亚洲一区在线视频| 看黄色录像一级片| 国产亚洲一区二区三区在线观看| 人妻激情偷乱视频一区二区三区| 日韩av二区在线播放| 中文字幕无码精品亚洲资源网久久| 精品无人区麻豆乱码久久久| 国产亚洲欧美一区二区三区| 国产精品日韩精品在线播放| 国产精品久久久久久搜索 | 香蕉视频网站在线| 欧美一区二区三区免费| 中文字幕观看视频| 日本道色综合久久| 日韩在线视频免费播放| 亚洲国产sm捆绑调教视频| 成人在线观看小视频| 国产精品网站在线观看| 波多野结衣办公室33分钟| 成人18视频在线播放| 1314成人网| 国产乱码精品一品二品| 亚洲36d大奶网| 日韩av成人高清| 国产亚洲欧美在线视频| 亚洲国产导航| 国产精品久久久久久久久电影网| 中文字幕免费一区二区三区| 亚洲午夜精品一区二区 | 懂色av中文一区二区三区 | 99久久免费视频.com| 欧美午夜精品一区二区| 国产一区二区精品久久91| 午夜免费看毛片| 美国一区二区三区在线播放| 亚洲欧美激情网| 青青青伊人色综合久久| 一区二区三区视频在线观看免费| 日本大胆欧美人术艺术动态| 99热手机在线| 男女男精品视频网| 天堂在线一区二区三区| 国产一区久久久| 三级网站免费看| 丁香一区二区三区| 欧美激情一区二区三区p站| 国产91精品免费| fc2成人免费视频| 91亚洲精品一区二区乱码| 国产黄色网址在线观看| 久久久久久久久伊人| 91社区视频在线观看| 亚洲欧洲精品一区二区三区| www.5588.com毛片| 亚洲一区在线观看免费| 亚洲国产成人精品激情在线| 色综合视频在线观看| 中文字字幕在线观看| 正在播放亚洲一区| 亚洲精品久久久久avwww潮水| 亚洲精品在线免费播放| 欧美成人免费| 日韩中文字幕不卡视频| 影音先锋在线视频| 91av在线网站| 日本欧美在线| 国产欧美一区二区三区不卡高清| 台湾佬综合网| 一区不卡字幕| 在线精品亚洲| 中文字幕视频在线免费观看| 国产剧情一区二区三区| 亚洲av成人精品一区二区三区 | 蜜桃av免费观看| 亚洲精品欧美激情| 日本中文字幕网| 欧美日韩精品欧美日韩精品| 精品人妻一区二区三区浪潮在线 | 日本xxxx裸体xxxx| 国产精品美女久久久久高潮| 欧美日韩在线观看免费| 日韩欧美高清视频| 国产伦精品一区二区三区视频痴汉| 欧美成人一级视频| 国家队第一季免费高清在线观看 | 国产高清视频色在线www| 国产成人avxxxxx在线看| 国产精品欧美一区二区三区不卡 | 日韩欧美中文视频| 91麻豆精品视频| 男人操女人的视频网站| 色偷偷88欧美精品久久久| 国内精品久久久久久久久久| 亚洲无线码在线一区观看| 欧美v亚洲v| 91精品国产自产在线观看永久| 人人精品亚洲| 日韩欧美视频免费在线观看| 奇米精品一区二区三区在线观看一| 男女性杂交内射妇女bbwxz| 国产精品久久久久久久久免费丝袜| 日本一级黄色录像| 91精品国产综合久久久久久| 国产在线中文字幕| 久久久久久久电影一区| 未满十八勿进黄网站一区不卡| 精品亚洲第一| 亚洲黄色天堂| 久久黄色一级视频| 亚洲欧洲三级电影| 中文字幕av片| 国产亚洲视频在线| 小h片在线观看| 国产精品一级久久久| 91精品啪在线观看国产18| av丝袜天堂网| 久久综合色8888| 日本三级中文字幕| 精品黑人一区二区三区久久| 国产一二区在线观看| 国产在线日韩在线| 日韩一区电影| 亚洲77777| 国产欧美日本一区二区三区| 无码人妻熟妇av又粗又大| 日韩电影网在线| gratisvideos另类灌满| 产国精品偷在线| 午夜久久久久| 亚洲av无一区二区三区久久| 中文字幕一区二区三区手机版 | 国产91精品一区二区麻豆亚洲| 欧美人与禽zoz0善交| 色天天综合久久久久综合片| 色视频免费在线观看| 97色在线视频| 日韩av午夜| 日本在线观看a| 久久精品一区二区三区不卡| 亚洲精品视频在线观看免费视频| 亚洲国产精品人人爽夜夜爽| 超碰中文在线| 久久偷窥视频| 美女网站久久| 99在线视频免费| 欧美精品123区| 黄a在线观看| 国产精品9999久久久久仙踪林| 亚洲国产婷婷| 亚洲熟妇无码av| 欧美主播一区二区三区| 日本电影全部在线观看网站视频| 成人免费午夜电影| 亚洲网站视频| 风间由美一二三区av片| 色噜噜狠狠色综合中国| 91在线高清| 亚洲综合在线做性| 国产精品亚洲产品| 亚洲日本精品视频| 欧美放荡的少妇| 国产后进白嫩翘臀在线观看视频| 精品一区二区三区免费毛片| 首页国产欧美日韩丝袜| 天堂а√在线中文在线鲁大师| 日韩欧美一区在线观看| 麻豆蜜桃在线观看| 亚洲成人动漫在线观看| 免费精品在线视频| 日韩亚洲欧美一区二区三区| 欧美激情20| 一区不卡字幕| www.亚洲精品| 一本色道久久综合精品婷婷| 欧美激情xxxxx| 国产精品三级| 午夜免费视频网站| 日韩欧美亚洲综合| www免费视频观看在线| 韩国精品一区二区三区六区色诱| 青青草精品视频| 国产精彩视频在线| 中日韩美女免费视频网站在线观看| 久久久久久亚洲精品美女| 日韩小视频在线播放| 中文字幕一区二区三区乱码在线| 手机av免费在线观看| 国产欧美在线播放| 国产亚洲一级|