溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Java集合詳解8:Java集合類細(xì)節(jié)精講,細(xì)節(jié)決定成敗

發(fā)布時(shí)間:2020-06-11 01:32:27 來源:網(wǎng)絡(luò) 閱讀:178 作者:黃小斜 欄目:編程語言

本系列文章將整理到我在GitHub上的《Java面試指南》倉庫,更多精彩內(nèi)容請(qǐng)到我的倉庫里查看

https://github.com/h3pl/Java-Tutorial

喜歡的話麻煩點(diǎn)下Star哈

文章首發(fā)于我的個(gè)人博客:

www.how2playlife.com

本文是微信公眾號(hào)【Java技術(shù)江湖】的《走進(jìn)JavaWeb技術(shù)世界》其中一篇,本文部分內(nèi)容來源于網(wǎng)絡(luò),為了把本文主題講得清晰透徹,也整合了很多我認(rèn)為不錯(cuò)的技術(shù)博客內(nèi)容,引用其中了一些比較好的博客文章,如有侵權(quán),請(qǐng)聯(lián)系作者。

該系列博文會(huì)告訴你如何從入門到進(jìn)階,從servlet到框架,從ssm再到SpringBoot,一步步地學(xué)習(xí)JavaWeb基礎(chǔ)知識(shí),并上手進(jìn)行實(shí)戰(zhàn),接著了解JavaWeb項(xiàng)目中經(jīng)常要使用的技術(shù)和組件,包括日志組件、Maven、Junit,等等內(nèi)容,以便讓你更完整地了解整個(gè)JavaWeb技術(shù)體系,形成自己的知識(shí)框架。為了更好地總結(jié)和檢驗(yàn)?zāi)愕膶W(xué)習(xí)成果,本系列文章也會(huì)提供每個(gè)知識(shí)點(diǎn)對(duì)應(yīng)的面試題以及參考答案。

如果對(duì)本系列文章有什么建議,或者是有什么疑問的話,也可以關(guān)注公眾號(hào)【Java技術(shù)江湖】聯(lián)系作者,歡迎你參與本系列博文的創(chuàng)作和修訂。

文末贈(zèng)送8000G的Java架構(gòu)師學(xué)習(xí)資料,需要的朋友可以到文末了解領(lǐng)取方式,資料包括Java基礎(chǔ)、進(jìn)階、項(xiàng)目和架構(gòu)師等免費(fèi)學(xué)習(xí)資料,更有數(shù)據(jù)庫、分布式、微服務(wù)等熱門技術(shù)學(xué)習(xí)視頻,內(nèi)容豐富,兼顧原理和實(shí)踐,另外也將贈(zèng)送作者原創(chuàng)的Java學(xué)習(xí)指南、Java程序員面試指南等干貨資源)
<!-- more -->

今天我們來探索一下Java集合類中的一些技術(shù)細(xì)節(jié)。主要是對(duì)一些比較容易被遺漏和誤解的知識(shí)點(diǎn)做一些講解和補(bǔ)充??赡懿蝗?,還請(qǐng)諒解。

初始容量

集合是我們?cè)贘ava編程中使用非常廣泛的,它就像大海,海納百川,像萬能容器,盛裝萬物,而且這個(gè)大海,萬能容器還可以無限變大(如果條件允許)。當(dāng)這個(gè)海、容器的量變得非常大的時(shí)候,它的初始容量就會(huì)顯得很重要了,因?yàn)橥诤!U(kuò)容是需要消耗大量的人力物力財(cái)力的。

同樣的道理,Collection的初始容量也顯得異常重要。所以:對(duì)于已知的情景,請(qǐng)為集合指定初始容量。

public static void main(String[] args) {
    StudentVO student = null;
    long begin1 = System.currentTimeMillis();
    List<StudentVO> list1 = new ArrayList<>();
    for(int i = 0 ; i < 1000000; i++){
        student = new StudentVO(i,"chenssy_"+i,i);
        list1.add(student);
    }
    long end1 = System.currentTimeMillis();
    System.out.println("list1 time:" + (end1 - begin1));

    long begin2 = System.currentTimeMillis();
    List<StudentVO> list2 = new ArrayList<>(1000000);
    for(int i = 0 ; i < 1000000; i++){
        student = new StudentVO(i,"chenssy_"+i,i);
        list2.add(student);
    }
    long end2 = System.currentTimeMillis();
    System.out.println("list2 time:" + (end2 - begin2));
}

上面代碼兩個(gè)list都是插入1000000條數(shù)據(jù),只不過list1沒有沒有申請(qǐng)初始化容量,而list2初始化容量1000000。那運(yùn)行結(jié)果如下:

list1 time:1638
list2 time:921

從上面的運(yùn)行結(jié)果我們可以看出list2的速度是list1的兩倍左右。在前面LZ就提過,ArrayList的擴(kuò)容機(jī)制是比較消耗資源的。我們先看ArrayList的add方法:

public boolean add(E e) {  
        ensureCapacity(size + 1);   
        elementData[size++] = e;  
        return true;  
    }  

public void ensureCapacity(int minCapacity) {  
    modCount++;         //修改計(jì)數(shù)器
    int oldCapacity = elementData.length;    
    //當(dāng)前需要的長度超過了數(shù)組長度,進(jìn)行擴(kuò)容處理
    if (minCapacity > oldCapacity) {  
        Object oldData[] = elementData;  
        //新的容量 = 舊容量 * 1.5 + 1
        int newCapacity = (oldCapacity * 3)/2 + 1;  
            if (newCapacity < minCapacity)  
                newCapacity = minCapacity;  
      //數(shù)組拷貝,生成新的數(shù)組 
      elementData = Arrays.copyOf(elementData, newCapacity);  
    }  
}

ArrayList每次新增一個(gè)元素,就會(huì)檢測ArrayList的當(dāng)前容量是否已經(jīng)到達(dá)臨界點(diǎn),如果到達(dá)臨界點(diǎn)則會(huì)擴(kuò)容1.5倍。然而ArrayList的擴(kuò)容以及數(shù)組的拷貝生成新的數(shù)組是相當(dāng)耗資源的。所以若我們事先已知集合的使用場景,知道集合的大概范圍,我們最好是指定初始化容量,這樣對(duì)資源的利用會(huì)更加好,尤其是大數(shù)據(jù)量的前提下,效率的提升和資源的利用會(huì)顯得更加具有優(yōu)勢。

asList的缺陷

在實(shí)際開發(fā)過程中我們經(jīng)常使用asList講數(shù)組轉(zhuǎn)換為List,這個(gè)方法使用起來非常方便,但是asList方法存在幾個(gè)缺陷:

避免使用基本數(shù)據(jù)類型數(shù)組轉(zhuǎn)換為列表

使用8個(gè)基本類型數(shù)組轉(zhuǎn)換為列表時(shí)會(huì)存在一個(gè)比較有味的缺陷。先看如下程序:

public static void main(String[] args) {
        int[] ints = {1,2,3,4,5};
        List list = Arrays.asList(ints);
        System.out.println("list'size:" + list.size());
    }
------------------------------------
outPut:
list'size:1

程序的運(yùn)行結(jié)果并沒有像我們預(yù)期的那樣是5而是逆天的1,這是什么情況?先看源碼:

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

asList接受的參數(shù)是一個(gè)泛型的變長參數(shù),我們知道基本數(shù)據(jù)類型是無法發(fā)型化的,也就是說8個(gè)基本類型是無法作為asList的參數(shù)的, 要想作為泛型參數(shù)就必須使用其所對(duì)應(yīng)的包裝類型。但是這個(gè)這個(gè)實(shí)例中為什么沒有出錯(cuò)呢?

因?yàn)樵搶?shí)例是將int類型的數(shù)組當(dāng)做其參數(shù),而在Java中數(shù)組是一個(gè)對(duì)象,它是可以泛型化的。所以該例子是不會(huì)產(chǎn)生錯(cuò)誤的。既然例子是將整個(gè)int類型的數(shù)組當(dāng)做泛型參數(shù),那么經(jīng)過asList轉(zhuǎn)換就只有一個(gè)int 的列表了。如下:

public static void main(String[] args) {
    int[] ints = {1,2,3,4,5};
    List list = Arrays.asList(ints);
    System.out.println("list 的類型:" + list.get(0).getClass());
    System.out.println("list.get(0) == ints:" + list.get(0).equals(ints));
}

outPut:
list 的類型:class [I
list.get(0) == ints:true
從這個(gè)運(yùn)行結(jié)果我們可以充分證明list里面的元素就是int數(shù)組。弄清楚這點(diǎn)了,那么修改方法也就一目了然了:將int 改變?yōu)镮nteger。

public static void main(String[] args) {
        Integer[] ints = {1,2,3,4,5};
        List list = Arrays.asList(ints);
        System.out.println("list'size:" + list.size());
        System.out.println("list.get(0) 的類型:" + list.get(0).getClass());
        System.out.println("list.get(0) == ints[0]:" + list.get(0).equals(ints[0]));
    }
----------------------------------------
outPut:
list'size:5
list.get(0) 的類型:class java.lang.Integer
list.get(0) == ints[0]:true

asList產(chǎn)生的列表不可操作

對(duì)于上面的實(shí)例我們?cè)僮鲆粋€(gè)小小的修改:

public static void main(String[] args) {
        Integer[] ints = {1,2,3,4,5};
        List list = Arrays.asList(ints);
        list.add(6);
    }

該實(shí)例就是講ints通過asList轉(zhuǎn)換為list 類別,然后再通過add方法加一個(gè)元素,這個(gè)實(shí)例簡單的不能再簡單了,但是運(yùn)行結(jié)果呢?打出我們所料:

Exception in thread "main" java.lang.UnsupportedOperationException
    at java.util.AbstractList.add(Unknown Source)
    at java.util.AbstractList.add(Unknown Source)
    at com.chenssy.test.arrayList.AsListTest.main(AsListTest.java:10)

運(yùn)行結(jié)果盡然拋出UnsupportedOperationException異常,該異常表示list不支持add方法。這就讓我們郁悶了,list怎么可能不支持add方法呢?難道jdk腦袋堵塞了?我們?cè)倏碼sList的源碼:

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

asList接受參數(shù)后,直接new 一個(gè)ArrayList,到這里看應(yīng)該是沒有錯(cuò)誤的啊?別急,再往下看:

private static class ArrayList<E> extends AbstractList<E>
    implements RandomAccess, java.io.Serializable{
        private static final long serialVersionUID = -2764017481108945198L;
        private final E[] a;

        ArrayList(E[] array) {
            if (array==null)
                throw new NullPointerException();
            a = array;
        }
        //.................
    }

這是ArrayList的源碼,從這里我們可以看出,此ArrayList不是java.util.ArrayList,他是Arrays的內(nèi)部類。

該內(nèi)部類提供了size、toArray、get、set、indexOf、contains方法,而像add、remove等改變list結(jié)果的方法從AbstractList父類繼承過來,同時(shí)這些方法也比較奇葩,它直接拋出UnsupportedOperationException異常:

public boolean add(E e) {
        add(size(), e);
        return true;
    }

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

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

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

通過這些代碼可以看出asList返回的列表只不過是一個(gè)披著list的外衣,它并沒有l(wèi)ist的基本特性(變長)。該list是一個(gè)長度不可變的列表,傳入?yún)?shù)的數(shù)組有多長,其返回的列表就只能是多長。所以::不要試圖改變asList返回的列表,否則你會(huì)自食苦果。

subList的缺陷

我們經(jīng)常使用subString方法來對(duì)String對(duì)象進(jìn)行分割處理,同時(shí)我們也可以使用subList、subMap、subSet來對(duì)List、Map、Set進(jìn)行分割處理,但是這個(gè)分割存在某些瑕疵。

subList返回僅僅只是一個(gè)視圖

首先我們先看如下實(shí)例:

public static void main(String[] args) {
List<Integer> list1 = new ArrayList<Integer>();
list1.add(1);
list1.add(2);

    //通過構(gòu)造函數(shù)新建一個(gè)包含list1的列表 list2
    List<Integer> list2 = new ArrayList<Integer>(list1);

    //通過subList生成一個(gè)與list1一樣的列表 list3
    List<Integer> list3 = list1.subList(0, list1.size());

    //修改list3
    list3.add(3);

    System.out.println("list1 == list2:" + list1.equals(list2));
    System.out.println("list1 == list3:" + list1.equals(list3));
}

這個(gè)例子非常簡單,無非就是通過構(gòu)造函數(shù)、subList重新生成一個(gè)與list1一樣的list,然后修改list3,最后比較list1 == list2?、list1 == list3?。

按照我們常規(guī)的思路應(yīng)該是這樣的:因?yàn)閘ist3通過add新增了一個(gè)元素,那么它肯定與list1不等,而list2是通過list1構(gòu)造出來的,所以應(yīng)該相等,所以結(jié)果應(yīng)該是:

list1 == list2:true
list1 == list3: false

首先我們先不論結(jié)果的正確與否,我們先看subList的源碼:

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

subListRangeCheck方式是判斷fromIndex、toIndex是否合法,如果合法就直接返回一個(gè)subList對(duì)象,注意在產(chǎn)生該new該對(duì)象的時(shí)候傳遞了一個(gè)參數(shù) this ,該參數(shù)非常重要,因?yàn)樗碇糽ist。

/**

  • 繼承AbstractList類,實(shí)現(xiàn)RandomAccess接口
    */
    private class SubList extends AbstractList<E> implements RandomAccess {
    private final AbstractList<E> parent; //列表
    private final int parentOffset;
    private final int offset;
    int size;

    //構(gòu)造函數(shù)
    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;
    }
    
    //set方法
    public E set(int index, E e) {
        rangeCheck(index);
        checkForComodification();
        E oldValue = ArrayList.this.elementData(offset + index);
        ArrayList.this.elementData[offset + index] = e;
        return oldValue;
    }
    
    //get方法
    public E get(int index) {
        rangeCheck(index);
        checkForComodification();
        return ArrayList.this.elementData(offset + index);
    }
    
    //add方法
    public void add(int index, E e) {
        rangeCheckForAdd(index);
        checkForComodification();
        parent.add(parentOffset + index, e);
        this.modCount = parent.modCount;
        this.size++;
    }
    
    //remove方法
    public E remove(int index) {
        rangeCheck(index);
        checkForComodification();
        E result = parent.remove(parentOffset + index);
        this.modCount = parent.modCount;
        this.size--;
        return result;
    }

    }

該SubLsit是ArrayList的內(nèi)部類,它與ArrayList一樣,都是繼承AbstractList和實(shí)現(xiàn)RandomAccess接口。同時(shí)也提供了get、set、add、remove等list常用的方法。但是它的構(gòu)造函數(shù)有點(diǎn)特殊,在該構(gòu)造函數(shù)中有兩個(gè)地方需要注意:

1、this.parent = parent;而parent就是在前面?zhèn)鬟f過來的list,也就是說this.parent就是原始list的引用。

2、this.offset = offset + fromIndex;this.parentOffset = fromIndex;。同時(shí)在構(gòu)造函數(shù)中它甚至將modCount(fail-fast機(jī)制)傳遞過來了。

我們?cè)倏磄et方法,在get方法中return ArrayList.this.elementData(offset + index);

這段代碼可以清晰表明get所返回就是原列表offset + index位置的元素。同樣的道理還有add方法里面的:

parent.add(parentOffset + index, e);
this.modCount = parent.modCount;
remove方法里面的

E result = parent.remove(parentOffset + index);
this.modCount = parent.modCount;

誠然,到了這里我們可以判斷subList返回的SubList同樣也是AbstractList的子類,同時(shí)它的方法如get、set、add、remove等都是在原列表上面做操作,它并沒有像subString一樣生成一個(gè)新的對(duì)象。

所以subList返回的只是原列表的一個(gè)視圖,它所有的操作最終都會(huì)作用在原列表上。

那么從這里的分析我們可以得出上面的結(jié)果應(yīng)該恰恰與我們上面的答案相反:

list1 == list2:false
list1 == list3:true

subList生成子列表后,不要試圖去操作原列表

從上面我們知道subList生成的子列表只是原列表的一個(gè)視圖而已,如果我們操作子列表它產(chǎn)生的作用都會(huì)在原列表上面表現(xiàn),但是如果我們操作原列表會(huì)產(chǎn)生什么情況呢?

public static void main(String[] args) {
List<Integer> list1 = new ArrayList<Integer>();
list1.add(1);
list1.add(2);

    //通過subList生成一個(gè)與list1一樣的列表 list3
    List<Integer> list3 = list1.subList(0, list1.size());
    //修改list1
    list1.add(3);

    System.out.println("list1'size:" + list1.size());
    System.out.println("list3'size:" + list3.size());
}

該實(shí)例如果不產(chǎn)生意外,那么他們兩個(gè)list的大小都應(yīng)該都是3,但是偏偏事與愿違,事實(shí)上我們得到的結(jié)果是這樣的:

list1'size:3
Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.ArrayList$SubList.checkForComodification(Unknown Source)
    at java.util.ArrayList$SubList.size(Unknown Source)
    at com.chenssy.test.arrayList.SubListTest.main(SubListTest.java:17)

list1正常輸出,但是list3就拋出ConcurrentModificationException異常,看過我另一篇博客的同仁肯定對(duì)這個(gè)異常非常,fail-fast?不錯(cuò)就是fail-fast機(jī)制,在fail-fast機(jī)制中,LZ花了很多力氣來講述這個(gè)異常,所以這里L(fēng)Z就不對(duì)這個(gè)異常多講了。我們?cè)倏磗ize方法:

public int size() {
            checkForComodification();
            return this.size;
        }

size方法首先會(huì)通過checkForComodification驗(yàn)證,然后再返回this.size。

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

該方法表明當(dāng)原列表的modCount與this.modCount不相等時(shí)就會(huì)拋出ConcurrentModificationException。

同時(shí)我們知道m(xù)odCount 在new的過程中 "繼承"了原列表modCount,只有在修改該列表(子列表)時(shí)才會(huì)修改該值(先表現(xiàn)在原列表后作用于子列表)。

而在該實(shí)例中我們是操作原列表,原列表的modCount當(dāng)然不會(huì)反應(yīng)在子列表的modCount上啦,所以才會(huì)拋出該異常。

對(duì)于子列表視圖,它是動(dòng)態(tài)生成的,生成之后就不要操作原列表了,否則必然都導(dǎo)致視圖的不穩(wěn)定而拋出異常。最好的辦法就是將原列表設(shè)置為只讀狀態(tài),要操作就操作子列表:

//通過subList生成一個(gè)與list1一樣的列表 list3

List<Integer> list3 = list1.subList(0, list1.size());

//對(duì)list1設(shè)置為只讀狀態(tài)

list1 = Collections.unmodifiableList(list1);

推薦使用subList處理局部列表

在開發(fā)過程中我們一定會(huì)遇到這樣一個(gè)問題:獲取一堆數(shù)據(jù)后,需要?jiǎng)h除某段數(shù)據(jù)。例如,有一個(gè)列表存在1000條記錄,我們需要?jiǎng)h除100-200位置處的數(shù)據(jù),可能我們會(huì)這樣處理:

for(int i = 0 ; i < list1.size() ; i++){
   if(i >= 100 && i <= 200){
       list1.remove(i);
       /*
        * 當(dāng)然這段代碼存在問題,list remove之后后面的元素會(huì)填充上來,
         * 所以需要對(duì)i進(jìn)行簡單的處理,當(dāng)然這個(gè)不是這里討論的問題。
         */
   }
}

這個(gè)應(yīng)該是我們大部分人的處理方式吧,其實(shí)還有更好的方法,利用subList。在前面LZ已經(jīng)講過,子列表的操作都會(huì)反映在原列表上。所以下面一行代碼全部搞定:

list1.subList(100, 200).clear();

簡單而不失華麗?。。。?!

保持compareTo和equals同步

在Java中我們常使用Comparable接口來實(shí)現(xiàn)排序,其中compareTo是實(shí)現(xiàn)該接口方法。我們知道compareTo返回0表示兩個(gè)對(duì)象相等,返回正數(shù)表示大于,返回負(fù)數(shù)表示小于。同時(shí)我們也知道equals也可以判斷兩個(gè)對(duì)象是否相等,那么他們兩者之間是否存在關(guān)聯(lián)關(guān)系呢?

public class Student implements Comparable<Student>{
    private String id;
    private String name;
    private int age;

    public Student(String id,String name,int age){
        this.id = id;
        this.name = name;
        this.age = age;
    }

    public boolean equals(Object obj){
        if(obj == null){
            return false;
        }

        if(this == obj){
            return true;
        }

        if(obj.getClass() != this.getClass()){
            return false;
        }

        Student student = (Student)obj;
        if(!student.getName().equals(getName())){
            return false;
        }

        return true;
    }

    public int compareTo(Student student) {
        return this.age - student.age;
    }

    /** 省略getter、setter方法 */
}

Student類實(shí)現(xiàn)Comparable接口和實(shí)現(xiàn)equals方法,其中compareTo是根據(jù)age來比對(duì)的,equals是根據(jù)name來比對(duì)的。

public static void main(String[] args){
        List<Student> list = new ArrayList<>();
        list.add(new Student("1", "chenssy1", 24));
        list.add(new Student("2", "chenssy1", 26));

        Collections.sort(list);   //排序

        Student student = new Student("2", "chenssy1", 26);

        //檢索student在list中的位置
        int index1 = list.indexOf(student);
        int index2 = Collections.binarySearch(list, student);

        System.out.println("index1 = " + index1);
        System.out.println("index2 = " + index2);
    }

按照常規(guī)思路來說應(yīng)該兩者index是一致的,因?yàn)樗麄儥z索的是同一個(gè)對(duì)象,但是非常遺憾,其運(yùn)行結(jié)果:

index1 = 0
index2 = 1

為什么會(huì)產(chǎn)生這樣不同的結(jié)果呢?這是因?yàn)閕ndexOf和binarySearch的實(shí)現(xiàn)機(jī)制不同。

indexOf是基于equals來實(shí)現(xiàn)的只要equals返回TRUE就認(rèn)為已經(jīng)找到了相同的元素。

而binarySearch是基于compareTo方法的,當(dāng)compareTo返回0 時(shí)就認(rèn)為已經(jīng)找到了該元素。

在我們實(shí)現(xiàn)的Student類中我們覆寫了compareTo和equals方法,但是我們的compareTo、equals的比較依據(jù)不同,一個(gè)是基于age、一個(gè)是基于name。

比較依據(jù)不同那么得到的結(jié)果很有可能會(huì)不同。所以知道了原因,我們就好修改了:將兩者之間的比較依據(jù)保持一致即可。

對(duì)于compareTo和equals兩個(gè)方法我們可以總結(jié)為:compareTo是判斷元素在排序中的位置是否相等,equals是判斷元素是否相等,既然一個(gè)決定排序位置,一個(gè)決定相等,所以我們非常有必要確保當(dāng)排序位置相同時(shí),其equals也應(yīng)該相等。

使其相等的方式就是兩者應(yīng)該依附于相同的條件。當(dāng)compareto相等時(shí)equals也應(yīng)該相等,而compareto不相等時(shí)equals不應(yīng)該相等,并且compareto依據(jù)某些屬性來決定排序。

參考文章

https://www.cnblogs.com/galibujianbusana/p/6600226.html

http://blog.itpub.net/69906029/viewspace-2641300/

https://www.cnblogs.com/itxiaok/p/10356553.html

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI