• java中的 集合 (一)


    一、集合是什么?

      Java集合类存放于 java.util 包中,是一个用来存放对象的容器。

    注意:①、集合只能存放对象。比如你存一个 int 型数据 1放入集合中,其实它是自动转换成 Integer 类后存入的,Java中每一种基本类型都有对应的引用类型。

       ②、集合存放的是多个对象的引用,对象本身还是放在堆内存中。

       ③、集合可以存放不同类型,不限数量的数据类型。

     Collection是最基本的集合接口,一些Collection允许相同的元素而另一些不行。一些能排序而另一些不行。

    2、Iterable 和 Iterator 的区别

        1. 两者都是接口,Iterable位于java.lang包下,Iterator位于java.util包下,两者之间的关系是Iterable接口提供了一个获取Iterator接口实例的方法。

        2.  Iterator是迭代器,如果一个对象拥有迭代器,那么就可以将对象中的所有元素和内容遍历出来,所以所有实现了Iterable接口的所有类都拥有迭代器Iterator。

                  3.  Collection 接口 继承 Iterable接口(Java集合的顶层接口)(不包括 map 系列的集合,Map接口 是 map 系列集合的顶层接口)

    迭代器Iterator 的方法:

        Object next():返回迭代的下一个元素。,返回值是 Object,需要强制转换成自己需要的类型

        boolean hasNext():判断容器内是否还有可供访问的元素

        void remove():删除迭代器刚越过的元素

    ListIterator相对Iterator增加了如下3个方法:

        boolean hasPrevious():返回该迭代器关联的集合是否还有上一个元素。

        Object previous():返回该迭代器的上一个元素。

        void add():在指定位置插入一个元素。

    public class Test {
    
        public static void main(String[] args) {
            ArrayList list = new ArrayList();
            list.add("a");
            list.add("b");
            list.add("c");
            list.add("d");
    //迭代器遍历
    //遍历原理:得到迭代器-->是否有下一个元素-->如果有就返回下一个元素.    
                Iterator it = list.iterator();
                while(it.hasNext()){       //hasNext()判断是否存在下一个元素
                     Object next = it.next(); //返回迭代的下一个元素。 
                    System.out.print(next);   //abcd
                }  
    //       单项迭代器就是单项迭代器
                Iterator it2 = list.iterator();
                while(it2.hasNext()){ 
    //      在迭代器中使用 list 直接添加或删除元素,  报 运行时修改异常  ConcurrentModificationException
                    //list.add(3);   
                    //list.remove("a"); 
    //     但是set方法却不报错,因为指针没有移动
                    //list.set(3, "我set方法却不报错,哈哈");
                    System.out.print(it2.next());   //abcd
             如果:这里加入:it2.remove(); 遍历一个删一个,最后list集合里面的全没了。
    } it2.remove(); // 从迭代器指向的 collection集合 中移除迭代器返回的最后一个元素 //双向迭代遍历 interface ListIterator<E> extends Iterator<E> // 双向迭代器可以从前向后迭代,同时也可以从后向前迭代, // 在API中是用ListIterator来实现的,该接口继承Iterator接口 ListIterator listIt=list.listIterator();//获得列表迭代器,扩展了Iterator接口 while(listIt.hasNext()){//向后遍历 System.out.print(listIt.next()); //abc listIt.add(" 我可以用耶 "); } System.out.println("=======下面开始反向迭代======="); while(listIt.hasPrevious())//返回该迭代器关联的集合是否还有上一个元素。 {//返回该迭代器的上一个元素。 System.out.print(listIt.previous());// 我可以用耶 c 我可以用耶 b 我可以用耶 a } } }

         

    3.面试题:ArrayList和LinkedList的关系和区别?

        1.原理不一样  ArrayList基于数组,改查快, 添删慢  ;LinkedList基于链表实现,添加删除快   查询修改慢

        2.两者都是集合框架中的容器,可以存储重复的数据,都是List接口的实现类

        3.LinkedList可以用来模拟队列以及堆栈的数据结构

        4.LinkedList相对于ArrayList多了很多的方法,其实操作链表的头部和尾部的一些方法

    4. Queue  [kju:]  和 Deque  ['dek]

    结构:QueueDeque都是接口,Deque接口继承Queue接口,LinkedListDeque的实现类。

    1. Queue表示一种队列,也是一种数据结构,它的特点是先进先出,因此在队列这个接口里面提供了一些操作队列的方法,同时LinkedList也具有这些方法;
    2. Deque(Double ended queues双端队列),支持在两端插入或者移除元素; 那也应该具有操作双端队列的一些方法;
    3. LinkedList是他们的子类,说明都具有他们两者的方法;LinkedList也可以充当队列,双端队列,堆栈多个角色;

     5 .ArrayList类的常用方法

    每个ArrayList实例都有一个容量,该容量是指用来存储列表元素的数组的大小。默认初始容量为10。随着ArrayList中元素的增加,它的容量也会不断的自动增长。在每次添加新的元素时,ArrayList都会检查是否需要进行扩容操作,扩容操作带来数据向新数组的重新拷贝,所以如果我们知道具体业务数据量,在构造ArrayList时可以给ArrayList指定一个初始容量,这样就会减少扩容时数据的拷贝问题。当然在添加大量元素前,应用程序也可以使用ensureCapacity操作来增加ArrayList实例的容量,这可以减少递增式再分配的数量。

          注意,ArrayList实现不是同步的。ArrayList不是线程安全的,只能用在单线程环境下,多线程环境下可以考虑用Collections.synchronizedList(List l)函数返回一个线程安全的ArrayList类,也可以使用concurrent并发包下的CopyOnWriteArrayList类。

            List list = Collections.synchronizedList(new ArrayList(...)); 

            ——ArrayList   线程不安全,查询速度快

      ——Vector  线程安全,但速度慢,已被ArrayList替代

      ——LinkedList  链表结果,增删速度快

    package ArrayList类的常用方法;
    
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    
    public class Test {
        public static void main(String[] args) {
    /*构造方法:    1.    ArrayList()  构造一个初始容量为10的空列表,这是最常用的构造方法    
               2.    ArrayList(Collection<? extends E> c) 
                            构造一个包含指定 collection 的元素的列表,这些元素是按照该collection的迭代器返回它们的顺序排列的
                  3.    ArrayList(int initialCapacity)  构造一个具有指定初始容量的空列表。 
    */            
    /*//1. 增:  4个方法:
    //        1.1     add(E e):将指定的元素添加到此列表的尾部。
    //        1.2     add(int index, E element):将指定的元素插入此列表中的指定位置。
    //      1.3     addAll(Collection<? extends E> c):按照指定 collection的迭代器所返回的元素顺序,
    //              将该 collection中的所有元素添加到此列表的尾部。
    //      1.4     addAll(int index, Collection<? extends E> c):从指定的位置开始,将指定 collection 中的所有元素插入到此列表中。     
     */
    //案例:
            ArrayList list = new ArrayList();
            list.add(5);
            list.add(2);
            list.add(2, '你');//将置顶元素添加到指定位置(插入算法)
            System.out.println(list);   //[5, 2, 你]
            
            ArrayList list2 = new ArrayList();
            list2.add("13");
            list2.add("14");
            list2.add("呀");
    
            list.addAll(list2);
            System.out.println(list);   //[5, 2, 你, 13, 14, 呀]
            
            list.addAll(2, list2);
            System.out.println(list);  //[5, 2, 13, 14, 呀, 你, 13, 14, 呀]
            
            list.set(5, "你好呀");
            System.out.println(list);  //[5, 2, 13, 14, 呀, 你好呀, 13, 14, 呀]
    
    /* 2. 删:   4个方法:
    //        2.1        clear()  移除此列表中的所有元素。 
    //        2.2     remove(int index):移除此列表中指定位置上的元素。
    //        2.3     remove(Object o):移除此列表中首次出现的指定元素(如果存在)。
    //         2.4        removeAll():移除此 collection 中那些也包含在指定 collection 中的所有元素
     */
    //    案例:
            list.remove(2);
            list.remove("14");
            System.out.println(list);  // [5, 2, 呀, 你好呀, 13, 14, 呀]
        
            list.removeAll(list2);
            System.out.println(list);  //[5, 2, 你好呀]
            list.clear();
            System.out.println(list);  //[]
            
    /*3. 改:
              3.1     set(int index, E element):用指定的元素替代此列表中指定位置上的元素。
              3.2     ensureCapacity(int minCapacity) 增加此 ArrayList 实例的容量,手动扩容
              3.3     trimToSize()  将此 ArrayList 实例的容量调整为列表的当前大小 .
                                   比如最开始容器大小为100,后来发现容器只添加了5个,就可以使用该方法,调整为只有5个元素的容器
    */    //案例:    
            ArrayList list3 = new ArrayList(50);
            list3.add("你好");
            list3.add("我好");
            list3.add("大家好");
            list3.set(2, "enen");
            System.out.println(list3);  //[你好, 我好, enen]
            list3.ensureCapacity(40);
            System.out.println(list3.size());   //3
            list3.trimToSize();
    /*4. 查:    
               4.1     size()  返回此列表中的元素数。
              4.2    get(int index)  返回此列表中指定位置上的元素 
             4.3    indexOf(Object o)  返回此列表中首次出现的指定元素的索引,或如果此列表不包含元素,则返回 -1
             4.4     lastIndexOf(Object o)  返回此列表中最后一次出现的指定元素的索引,或如果此列表不包含索引,则返回 -1。
     */    
            System.out.println(list3.get(1));            //我好
            System.out.println(list3.indexOf("我好")); //1
            
    /*5. 其他方法
     */        System.out.println(list.isEmpty());           //true
             System.out.println(list3.contains("我好"));  //true
    //           Object[] toArray()  按适当顺序(从第一个到最后一个元素)返回包含此列表中所有元素的数组 
             System.out.println(Arrays.toString(list3.toArray())); //[你好, 我好, enen]
        }
    
    }

     6.LinkedList类的常用方法

     首先我们先看LinkedList的定义:

    public class LinkedList<E>
        extends AbstractSequentialList<E>
        implements List<E>, Deque<E>, Cloneable, java.io.Serializable

          从这段代码中我们可以清晰地看出LinkedList继承AbstractSequentialList,实现List、Deque、Cloneable、Serializable。其中AbstractSequentialList提供了 List 接口的骨干实现,从而最大限度地减少了实现受“连续访问”数据存储(如链接列表)支持的此接口所需的工作,从而以减少实现List接口的复杂度。Deque一个线性 collection,支持在两端插入和移除元素,定义了双端队列的操作。

    package LinkedList类的常用方法;
    
    import java.util.AbstractSequentialList;
    import java.util.Deque;
    import java.util.List;
    
    public class Test {
        public static void main(String[] args) {
    // LinkedList<E>  extends AbstractSequentialList<E>
    //        implements List<E>, Deque<E>, Cloneable, java.io.Serializable
        
    //1. 构造方法: 
    /*    1.1  LinkedList()空参构造,仅仅只是将header节点的前一个元素、后一个元素都指向自身。
            public LinkedList() {
                header.next = header.previous = header;
            }
            
        1.2     LinkedList(Collection<? extends E> c)   
            构造一个包含指定 collection 中的元素的列表,这些元素按其 collection 的迭代器返回的顺序排列。
            public LinkedList(Collection<? extends E> c) {
                this();
                addAll(c);
            }
    */
    //2. 两个字段  :    size表示的LinkedList的大小,header表示链表的表头
            //Entry为节点对象。
    /*        private transient Entry<E> header = new Entry<E>(null, null, null);
            private transient int size = 0;
    */        
            
    //3. 常用方法 LinkedList的方法和ArrayList差不多,因为他们都实现了List接口,在List接口中已经定义了大部分的共有方法,
    //由于LinkedList使用的数据结构是链表方式,所以在有些方法上有些差别,差别在于可以在容器的头部和尾部对元素做操作:

     下面介绍LinkedList实现Deque接口的方法

    public class Test {
        public static void main(String[] args) {
            Deque<Integer> deq = new LinkedList();
            deq.add(1);
            deq.add(2);
            deq.add(3);
            deq.add(4);
            deq.add(5);
            System.out.println(deq);  // [1, 2, 3, 4, 5]
    //1. peek()和element()方法 都是获取(不移除此双端队列)队列的头部
    //        区别:peek(),如果链表为空,则返回null。element(),如果链表为空,则抛异常。
            System.out.println(deq.peek());          //  1
            System.out.println(deq.element());        // 1
            
    //2.  peekFirst() 获取但不移除此列表的第一个元素;如果此列表为空,则返回 null。
    //      peekLast()  获取但不移除此列表的最后一个元素;如果此列表为空,则返回 null。 
            System.out.println(deq.peekFirst());    //1
            System.out.println(deq.peekLast());     //5
            
    //3. poll()   pop() 获取并移除此列表的头(第一个元素)     pop()出栈      push压栈
    //        poll()和 pop()区别:  如果此列表为空,pop()抛出NoSuchElementException,poll()则返回 null
            System.out.println(deq.poll());         //1
            System.out.println(deq);                //[2, 3, 4, 5]
            System.out.println(deq.pop());          //2
            System.out.println(deq);                //[3, 4, 5]
            
    //4.  pollFirst()  获取并移除此列表的第一个元素;如果此列表为空,则返回 null。 
    //      pollLast()  获取并移除此列表的最后一个元素;如果此列表为空,则返回 null。 
            System.out.println(deq.pollFirst());    //3
            System.out.println(deq.pollLast());     //5
            
    //5   push(E e)   将元素推入此列表所表示的堆栈 ,
    //            如果成功,则返回 true,如果当前没有可用空间,则抛出 IllegalStateException。
            deq.push(520131);
            System.out.println(deq);  //[520131, 4]
        }
    }

    7.HashSet类的常用方法

    HashSet堪称查询速度最快的集合,因为其内部是以HashCode来实现的。

    它内部元素的顺序是由哈希码来决定的,所以它不保证set 的迭代顺序;特别是它不保证该顺序恒久不变。

    package HashSet类的常用方法;
    
    import java.util.AbstractSet;
    import java.util.HashSet;
    import java.util.Iterator;
    import java.util.Set;
    
    public class Test3 {
        public static void main(String[] args) {
    //继承关系: class HashSet<E> extends AbstractSet<E>  implements Set<E>, Cloneable, java.io.Serializable
            
            
    /*1. 字段
            //基于HashMap实现,底层使用HashMap保存所有元素
            private transient HashMap<E,Object> map;
    
            //定义一个Object对象作为HashMap的value
            private static final Object PRESENT = new Object();
    */
            
    /*2. 构造方法
            2.1//初始化一个空的HashMap,并使用默认初始容量为16和加载因子0.75。
            public HashSet() {
                map = new HashMap<>();
            }
            
             2.2// 构造一个包含指定 collection 中的元素的新 set。
            public HashSet(Collection<? extends E> c) {
                map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
                addAll(c);
            }
            
            
             2.3//构造一个新的空 set,其底层 HashMap 实例具有指定的初始容量和指定的加载因子
            public HashSet(int initialCapacity, float loadFactor) {
                map = new HashMap<>(initialCapacity, loadFactor);
            }
               
            
             2.4//构造一个新的空 set,其底层 HashMap 实例具有指定的初始容量和默认的加载因子(0.75)。
            public HashSet(int initialCapacity) {
               map = new HashMap<>(initialCapacity);
            }
               
            
            2.5 在API中我没有看到这个构造函数,今天看源码才发现(原来访问权限为包权限,不对外公开的)
             * 以指定的initialCapacity和loadFactor构造一个新的空链接哈希集合。
             * dummy 为标识 该构造函数主要作用是对LinkedHashSet起到一个支持作用
           HashSet(int initialCapacity, float loadFactor, boolean dummy) {
                map = new LinkedHashMap<>(initialCapacity, loadFactor);
            }
    */
    /*3.普通方法
     *     前言:HashSet的方法和ArrayList的方法大同小异,差别在于:
            HashSet是无序的,所以涉及到index索引的方法在HashSet里面都没有. 
    */        
            HashSet set1 = new HashSet();
            HashSet set2 = new HashSet();
            for (int i = 0; i < 4; i++) {
                set1.add(i);   //[0, 1, 2, 3]
            }
            for (int i = 2; i < 6; i++) {
                set2.add(i);   //[2, 3, 4, 5]
            }
            set1.add(set2);
            System.out.println(set1);   //[0, 1, 2, 3, [2, 3, 4, 5]]
            set1.addAll(set2);
            System.out.println(set1);   //[0, 1, 2, 3, 4, 5, [2, 3, 4, 5]]
    //迭代器遍历        
            Iterator iterator = set1.iterator();
            while(iterator.hasNext()){
                Object next = iterator.next();
                System.out.print(next);  //012345[2, 3, 4, 5]
            }
            System.out.println(set1.contains(3)); //true
            set1.remove(set2);
            System.out.println(set1); //[0, 1, 2, 3, 4, 5]
            set1.clear();
            System.out.println(set1); //[]
        }
    
    }

    8.TreeSet类的常用方法

    TreeSet中默认要求里面的元素进行自然排序,强制要求里面的所有元素必须按照Comparable中的compareTo方法进行比较,如果容器里面的对象不具备compareTo方法此时就会抛出异常报错。

    所以必须要让容器中的元素实现Comparable接口,这样它才具备compareTo方法。

    1.TreeSet实例在调用add方法时会调用容器对象的compareTo方法对元素进行比较

    2.TreeSet实例中对象必须是实现了Comparable接口

    package TreeSet类的常用方法;
    
    public class Test4 {
        public static void main(String[] args) {
    //1.继承关系
    /*        public class TreeSet<E> extends AbstractSet<E>
            implements NavigableSet<E>, Cloneable, java.io.Serializable
    */
    //2.变量
    /*        private transient NavigableMap<E,Object> m;
            
            //PRESENT会被当做Map的value与key构建成键值对
             private static final Object PRESENT = new Object();
    */
    //3.构造方法
    /*        //3.1默认构造方法,根据其元素的自然顺序进行排序
            public TreeSet() {
                this(new TreeMap<E,Object>());
            }
            
            //3.2构造一个包含指定 collection 元素的新 TreeSet,它按照其元素的自然顺序进行排序。
            public TreeSet(Comparator<? super E> comparator) {
                    this(new TreeMap<>(comparator));
            }
            
            //3.3构造一个新的空 TreeSet,它根据指定比较器进行排序。
            public TreeSet(Collection<? extends E> c) {
                this();
                addAll(c);
            }
            
            //3.4构造一个与指定有序 set 具有相同映射关系和相同排序的新 TreeSet。
            public TreeSet(SortedSet<E> s) {
                this(s.comparator());
                addAll(s);
            }
            //3.5
            TreeSet(NavigableMap<E,Object> m) {
                this.m = m;
            }
    */
    //4.主要方法
    /*        1、add:将指定的元素添加到此 set(如果该元素尚未存在于 set 中)。
    
            public boolean add(E e) {
                    return m.put(e, PRESENT)==null;
                }
            2、addAll:将指定 collection 中的所有元素添加到此 set 中。
    
            public  boolean addAll(Collection<? extends E> c) {
                    // Use linear-time version if applicable
                    if (m.size()==0 && c.size() > 0 &&
                        c instanceof SortedSet &&
                        m instanceof TreeMap) {
                        SortedSet<? extends E> set = (SortedSet<? extends E>) c;
                        TreeMap<E,Object> map = (TreeMap<E, Object>) m;
                        Comparator<? super E> cc = (Comparator<? super E>) set.comparator();
                        Comparator<? super E> mc = map.comparator();
                        if (cc==mc || (cc != null && cc.equals(mc))) {
                            map.addAllForTreeSet(set, PRESENT);
                            return true;
                        }
                    }
                    return super.addAll(c);
                }
    
            3、ceiling:返回此 set 中大于等于给定元素的最小元素;如果不存在这样的元素,则返回 null。
    
            public E ceiling(E e) {
                    return m.ceilingKey(e);
                }
            4、clear:移除此 set 中的所有元素。
    
            public void clear() {
                    m.clear();
                }
            5、clone:返回 TreeSet 实例的浅表副本。属于浅拷贝。
    
            public Object clone() {
                    TreeSet<E> clone = null;
                    try {
                        clone = (TreeSet<E>) super.clone();
                    } catch (CloneNotSupportedException e) {
                        throw new InternalError();
                    }
    
                    clone.m = new TreeMap<>(m);
                    return clone;
                }
        
            6、comparator:返回对此 set 中的元素进行排序的比较器;如果此 set 使用其元素的自然顺序,则返回 null。
    
            public Comparator<? super E> comparator() {
                    return m.comparator();
                }
            7、contains:如果此 set 包含指定的元素,则返回 true。
    
            public boolean contains(Object o) {
                    return m.containsKey(o);
                }
            8、descendingIterator:返回在此 set 元素上按降序进行迭代的迭代器。
    
            public Iterator<E> descendingIterator() {
                    return m.descendingKeySet().iterator();
                }
            9、descendingSet:返回此 set 中所包含元素的逆序视图。
    
            public NavigableSet<E> descendingSet() {
                    return new TreeSet<>(m.descendingMap());
                }
            10、first:返回此 set 中当前第一个(最低)元素。
    
            public E first() {
                    return m.firstKey();
                }
            11、floor:返回此 set 中小于等于给定元素的最大元素;如果不存在这样的元素,则返回 null。
    
            public E floor(E e) {
                    return m.floorKey(e);
                }
            12、headSet:返回此 set 的部分视图,其元素严格小于 toElement。
    
            public SortedSet<E> headSet(E toElement) {
                    return headSet(toElement, false);
                }
            13、higher:返回此 set 中严格大于给定元素的最小元素;如果不存在这样的元素,则返回 null。
    
            public E higher(E e) {
                    return m.higherKey(e);
                }
            14、isEmpty:如果此 set 不包含任何元素,则返回 true。
    
            public boolean isEmpty() {
                    return m.isEmpty();
                }
            15、iterator:返回在此 set 中的元素上按升序进行迭代的迭代器。
    
            public Iterator<E> iterator() {
                    return m.navigableKeySet().iterator();
                }
            16、last:返回此 set 中当前最后一个(最高)元素。
    
            public E last() {
                    return m.lastKey();
                }
            17、lower:返回此 set 中严格小于给定元素的最大元素;如果不存在这样的元素,则返回 null。
    
            public E lower(E e) {
                    return m.lowerKey(e);
                }
            18、pollFirst:获取并移除第一个(最低)元素;如果此 set 为空,则返回 null。
    
            public E pollFirst() {
                    Map.Entry<E,?> e = m.pollFirstEntry();
                    return (e == null) ? null : e.getKey();
                }
            19、pollLast:获取并移除最后一个(最高)元素;如果此 set 为空,则返回 null。
    
            public E pollLast() {
                    Map.Entry<E,?> e = m.pollLastEntry();
                    return (e == null) ? null : e.getKey();
                }
            20、remove:将指定的元素从 set 中移除(如果该元素存在于此 set 中)。
    
            public boolean remove(Object o) {
                    return m.remove(o)==PRESENT;
                }
            21、size:返回 set 中的元素数(set 的容量)。
    
            public int size() {
                    return m.size();
                }
            22、subSet:返回此 set 的部分视图
    
                 返回此 set 的部分视图,其元素范围从 fromElement 到 toElement。
                 
                 public NavigableSet<E> subSet(E fromElement, boolean fromInclusive,
                         E toElement,   boolean toInclusive) {
                         return new TreeSet<>(m.subMap(fromElement, fromInclusive,
                              toElement,   toInclusive));
                 }
                 
                  //返回此 set 的部分视图,其元素从 fromElement(包括)到 toElement(不包括)。
            
                 public SortedSet<E> subSet(E fromElement, E toElement) {
                     return subSet(fromElement, true, toElement, false);
                 }
    
            23、tailSet:返回此 set 的部分视图
    
                   返回此 set 的部分视图,其元素大于(或等于,如果 inclusive 为 true)fromElement。
            
                public NavigableSet<E> tailSet(E fromElement, boolean inclusive) {
                    return new TreeSet<>(m.tailMap(fromElement, inclusive));
                }
                
                //返回此 set 的部分视图,其元素大于等于 fromElement。
               
                public SortedSet<E> tailSet(E fromElement) {
                    return tailSet(fromElement, true);
                }
    */
        }
    }

    部分内容参考别人的作品: https://www.cnblogs.com/chenssy/

  • 相关阅读:
    【转】 cin、cin.get()、cin.getline()、getline()、gets()等函数的用法
    HDU How many prime numbers
    《大学ACM的总结 》(转载)
    POJ 最小公倍数
    HDU 开门人和关门人
    HDU shǎ崽 OrOrOrOrz
    HDU Saving HDU 2111
    HDU 1106 排序
    strtok函数()
    HDU 2187汶川地震
  • 原文地址:https://www.cnblogs.com/gshao/p/10116891.html
Copyright © 2020-2023  润新知