首页 > 其他 > 详细

集合(二)

时间:2020-06-09 00:33:15      阅读:50      评论:0      收藏:0      [点我收藏+]

一、List 集合

1. List接口的介绍

java.util.List接口继承Collection接口,是单列集合的一个重要分支,可以把实现了List接口的对象称为List集合。在List集合中允许出现重复的元素,所有的元素是以一种线性方式进行存储的,在程序中可以通过索引来访问集合中的指定元素。另外,List集合还有一个特点就是元素有序,即元素的存入顺序和取出顺序一致。

List接口特点:

  • 是元素存取有序的集合,存储元素和取出元素的顺序是一致的
  • 它是一个带有索引的集合,,包含了一些带索引的方法
    • 操作索引时,要防止索引越界异常
  • 允许存储重复的元素,通过元素的equals方法,来比较是否为重复的元素。

注:前面的List接口中的子类java.util.ArrayList,该类中的方法就来自List中定义。

2. List接口中常用方法

List作为Collection集合的子接口,不但继承了Collection接口中的全部方法,而且还增加了一些根据元素索引来操作集合的特有方法,如下:

  • public void add(int index, E element): 将指定的元素,添加到该集合中的指定位置上。
  • public E get(int index):返回集合中指定位置的元素。
  • public E remove(int index): 移除列表中指定位置的元素, 返回的是被移除的元素。
  • public E set(int index, E element):用指定元素替换集合中指定位置的元素,返回值的更新前的元素。

List集合特有的方法都是跟索引相关。重新复习一下:

public class ListDemo {
    public static void main(String[] args) {
		// 创建List集合对象
    	List<String> list = new ArrayList<String>();
    	
    	// 往 尾部添加 指定元素
    	list.add("图图");
    	list.add("小美");
    	list.add("不高兴");
    	
    	System.out.println(list);
    	// add(int index,String s) 往指定位置添加
    	list.add(1,"没头脑");
    	
    	System.out.println(list);
    	// String remove(int index) 删除指定位置元素  返回被删除元素
    	// 删除索引位置为2的元素 
    	System.out.println("删除索引位置为2的元素");
    	System.out.println(list.remove(2));
    	
    	System.out.println(list);
    	
    	// String set(int index,String s)
    	// 在指定位置 进行 元素替代(改) 
    	// 修改指定位置元素
    	list.set(0, "三毛");
    	System.out.println(list);
    	
    	// String get(int index)  获取指定位置元素
    	
    	// 跟size() 方法一起用  来 遍历的 
    	for(int i = 0;i<list.size();i++){
    		System.out.println(list.get(i));
    	}
    	//还可以使用增强for
    	for (String string : list) {
			System.out.println(string);
		}  	
	}
}

二、List的子类

1. ArrayList集合

java.util.ArrayList集合数据存储的结构是数组结构。可以动态增长和缩减的索引序列,它是基于数组实现的List类。特点是元素增删慢,查找快,由于日常开发中使用最多的功能为查询数据、遍历数据,所以ArrayList是最常用的集合。

许多人在开发时非常随意地使用ArrayList完成任何需求,这并不严谨,是不提倡的。

2. LinkedList集合

数据结构知识补充

单向链表

  • element:用来存放元素
  • next:用来指向下一个节点元素
  • 通过每个结点的指针指向下一个结点从而链接起来,最后一个节点的next指向null

技术分享图片

单向循环链表

  • element、next 跟前面一样
  • 在单向链表的最后一个节点的next会指向头节点,而不是指向null,这样形成一个环
    技术分享图片

双向链表

  • element:存放元素
  • pre:用来指向前一个元素
  • next:指向后一个元素
  • 双向链表是包含两个指针的,pre指向前一个节点,next指向后一个节点,但是第一个节点head的pre指向null,最后一个节点的tail指向null

技术分享图片

双向循环链表

element、pre、next 与双向链表一样

  • 第一个节点的pre指向最后一个节点,最后一个节点的next指向第一个节点,也形成一个“环”

技术分享图片

LinkedList的数据结构

技术分享图片

如上图所示,LinkedList底层使用的双向链表结构,有一个头结点和一个尾结点,双向链表意味着我们可以从头开始正向遍历,或者是从尾开始逆向遍历,并且可以针对头部和尾部进行相应的操作

LinkedList的特性

通过查阅API,可得到以下特性:

  • linkedList是一个双向链表,并且实现了List和Deque接口中所有的列表操作
  • linkedList能存储任何元素,包括null
  • linkedList除了可以当链表使用还可以当作队列使用,并能进行相应的操作
  • linkedList在执行任何操作的时候,都必须先遍历此列表来靠近并通过index查找我们所需要的的值。这就告诉了我们这个是顺序存取,每次操作必须先按开始到结束的顺序遍历,而随机存取,就是arrayList,能够通过index随便访问其中任意位置的数据
  • 总结就是:linkedList是一个非线程安全的(异步),其中在操作Interator时,如果改变列表结构(adddelete等),就会发生fail-fast
    • 异步,也就是非线程安全
    • 双向链表。由于实现了list和Deque接口,能够当作队列来使用
    • 是顺序存取结构

方法

java.util.LinkedList集合数据存储的底层是链表结构,查询效率不高,但是插入和删除这种操作性能好 。包含了元素添加、删除的集合。在实际中对一个集合元素的添加与删除经常涉及到首尾操作,而LinkedList提供了大量首尾操作的方法。这些方法我们了解即可:

  • public void addFirst(E e):将指定元素插入此列表的开头。
  • public void addLast(E e):将指定元素添加到此列表的结尾。
  • public E getFirst():返回此列表的第一个元素。
  • public E getLast():返回此列表的最后一个元素。
  • public E removeFirst():移除并返回此列表的第一个元素。
  • public E removeLast():移除并返回此列表的最后一个元素。
  • public E pop():从此列表所表示的堆栈处弹出一个元素。
  • public void push(E e):将元素推入此列表所表示的堆栈。
  • public boolean isEmpty():如果列表不包含元素,则返回true。

LinkedList是List的子类,List中的方法LinkedList都是可以使用,这里就不做详细介绍,我们只需要了解LinkedList的特有方法即可。在开发时,LinkedList集合也可以作为堆栈,队列的结构使用。(了解即可)

方法演示:

public class LinkedListDemo {
    public static void main(String[] args) {
        LinkedList<String> link = new LinkedList<String>();
        //添加元素
        link.addFirst("abc1");
        link.addFirst("abc2");
        link.addFirst("abc3");
        System.out.println(link);
        // 获取元素
        System.out.println(link.getFirst());
        System.out.println(link.getLast());
        // 删除元素
        System.out.println(link.removeFirst());
        System.out.println(link.removeLast());

        while (!link.isEmpty()) { //判断集合是否为空
            System.out.println(link.pop()); //弹出集合中的栈顶元素
        }

        System.out.println(link);
    }
}

三、 Set接口

java.util.Set接口和java.util.List接口一样,继承自Collection接口,它与Collection接口中的方法基本一致,并没有对Collection接口进行功能上的扩充,只是比Collection接口更加严格了。

List接口不同的是

  • Set接口中元素无序,不允许存储重复的元素
  • 没有索引,没有带索引的方法,也不能使用普通的for循环遍历

Set集合有多个子类,这里我们学习其中的java.util.HashSetjava.util.LinkedHashSet这两个集合。

注:Set集合取出元素的方式可以采用:迭代器、增强for。

1. HashSet集合介绍

java.util.HashSetSet接口的一个实现类,特点是

  • 它所存储的元素是不可重复的
  • 没有索引,没有带索引的方法,也不能使用普通的for循环遍历
  • 是一个无序的集合,存储和取出的顺序可能不一致
  • 底层的实现其实是一个哈希表结构(java.util.HashMap支持),哈希表查询速度非常快

HashSet是根据对象的哈希值来确定元素在集合中的存储位置,因此具有良好的存取和查找性能。保证元素唯一性的方式依赖于:hashCodeequals方法。

先来使用一下Set集合存储,看下现象,再看看原理:

public class UseHashSet {
    public static void main(String[] args) {
        //创建 Set集合
HashSet<Integer> Hs = new HashSet<>();
        Hs.add(1);
        Hs.add(7);Hs
        Hs.add(1);
        Hs.add(2);
        //用迭代器遍历
        Iterator<Integer> it = Hs.iterator();
        while (it.hasNext()){
            Integer n = it.next();
            System.out.println(n);
        }
        //使用增强for遍历set集合
        for (Integer i : Hs){
             System.out.println(i);
        }
    }
}输出结果是"1,2,7"说明集合中不能存储重复元素:

注:根据结果发现字符串1只存储了一个,也就是说重复的元素set集合不存储。

2. HashSet集合存储数据的结构(哈希表)

哈希值【前提】

  • 是一个十进制的整数,由系统随机给出(就是对象的地址值,是一个逻辑地址,是模拟出来得到地址,不是数据实际存储的物理地址)
  • 在Object类有一个方法,可以获取对象的哈希值
    • int hashCode() 返回该对象的哈希码值。
    • hashCode方法的源码:
      • public native int hashCode();
      • native:代表该方法调用的是本地操作系统的方法

哈希表

  • JDK1.8之前
    • 哈希表底层采用数组+链表实现
    • 使用链表处理冲突,同一hash值的链表都存储在一个链表里。但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低
  • JDK1.8之后
    • 哈希表存储采用数组+链表+红黑树实现
    • 当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间

总而言之,JDK1.8引入红黑树很大程度上优化了HashMap的性能。也就是说保证HashSet集合元素的唯一,其实就是根据对象的hashCode和equals方法来决定的。如果我们往集合中存放自定义的对象,若要保证其唯一,就必须复写hashCode和equals方法建立属于当前对象的比较方式

set集合不允许重复的原理

前提:存储的元素必须重写hasCode方法和equals方法

        HashSet<String> set = new HashSet<>();
        String s1 = new String("asdf");
        String s2 = new String("asdf");
        set.add(s1);
        set.add(s2);
        set.add("你好");
        set.add("我是");
        set.add("asdf");
        System.out.println(set);
        //[我是, 你好, asdf]

对上面的代码:

  • Set集合在调用add方法的时候,add方法会调用元素的hasCode方法和equals方法,判断元素是否重复
    • add方法会调用hasCode方法,计算字符串"asdf"的哈希值
    • 在集合中寻找与这个哈希值相同的元素,若无就把s1存储到集合中

3. HashSet存储自定义类型元素

给HashSet中存放自定义类型元素前,需重写对象中的hashCode和equals方法建立自己的比较方式,保证HashSet集合中的对象唯一

创建自定义Student类

public class Student {
    private String name;
    private int age;
	
    //省略两个构造方法和get、set方法
	//下面是重写equals和hashCode方法
    @Override
    public boolean equals(Object o) {
        if (this == o)
            return true;
        if (o == null || getClass() != o.getClass())
            return false;
        Student student = (Student) o;
        return age == student.age &&
               Objects.equals(name, student.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}
public class HashSetDemo2 {
    public static void main(String[] args) {
        //创建集合对象   该集合中存储 Student类型对象
        HashSet<Student> stuSet = new HashSet<Student>();
        //存储 
        Student stu = new Student("于谦", 43);
        stuSet.add(stu);
        stuSet.add(new Student("郭德纲", 44));
        stuSet.add(new Student("于谦", 43));
        stuSet.add(new Student("郭麒麟", 23));
        stuSet.add(stu);

        for (Student stu2 : stuSet) {
            System.out.println(stu2);
        }
    }
}
执行结果:
Student [name=郭德纲, age=44]
Student [name=于谦, age=43]
Student [name=郭麒麟, age=23]

4. LinkedHashSet

我们知道HashSet保证元素唯一,若元素存放进去是没有顺序的,为了保证有序,可以使用LinkedHashSet集合。该集合是HashSet的一个子类,特点是

  • 底层是一个哈希表(数组+链表/红黑树)+链表:多了一条链表(记录元素的存储顺序),保证元素有序
public class LinkedHashSetDemo {
	public static void main(String[] args) {
		Set<String> set = new LinkedHashSet<String>();
		set.add("bbb");
		set.add("aaa");
		set.add("abc");
		set.add("bbc");
        Iterator<String> it = set.iterator();
		while (it.hasNext()) {
			System.out.println(it.next());
		}
	}
}
结果:
  bbb
  aaa
  abc
  bbc

5. 可变参数

可变参数:

  • JDK1.5之后出现的新特性

使用前提:

  • 当方法的参数列表数据类型已经确定,但参数的个数不确定,就可以使用可变参数

使用格式:定义方法时使用

  • 修饰符 返回值类型 方法名(参数类型[] 形参名){ }

可变参数的原理:

  • 可变参数底层就是一个数组,根据传递参数个数不同,会创建不同长度的数组,来存储这些参数
  • 传递的参数个数,可以是0个(不传递),1,2...多个

可变参数的注意事项:

  • 一个方法的参数列表,只能有一个可变参数
  • 如果方法的参数有多个,那么可变参数必须写在参数列表的末尾

同样是代表数组,但是在调用这个带有可变参数的方法时,不用创建数组(这就是简单之处),直接将数组中的元素作为实际参数进行传递,其实编译成的class文件,将这些元素先封装到一个数组中,在进行传递。这些动作都在编译.class文件时,自动完成了。

代码演示:

public class ChangeArgs {
    public static void main(String[] args) {
        int[] arr = { 1, 4, 62, 431, 2 };
        int sum = getSum(arr);
        System.out.println(sum);
        //  6  7  2 12 2121
        // 求 这几个元素和 6  7  2 12 2121
        int sum2 = getSum(6, 7, 2, 12, 2121);
        System.out.println(sum2);
    }

    /*
     * 完成数组  所有元素的求和 原始写法
     
      public static int getSum(int[] arr){
        int sum = 0;
        for(int a : arr){
            sum += a;
        }
        
        return sum;
      }
    */
    //可变参数写法
    public static int getSum(int... arr) {
        int sum = 0;
        for (int a : arr) {
            sum += a;
        }
        return sum;
    }
}

注:

  • 上述add方法在同一个类中,只能存在一个。因为会发生调用的不确定性
  • ... 用在参数上,称之为可变参数
  • 如果在方法书写时,这个方法拥有多参数,参数中包含可变参数,可变参数一定要写在参数列表的末尾位置。

四、Collections工具类

1. 概述

Java提供了一个操作Set、List和Map等集合的工具类:Collections,该工具类提供了大量方法对集合进行排序、查询和修改等操作,还提供了将集合对象置为不可变、对集合对象实现同步控制等方法。
这个类不需要创建对象,内部提供的都是静态方法

2. 常用功能

排序

public static <T> boolean addAll(Collection<T> c, T... elements)
									往集合中添加一些元素: 
													Collections.addAll(list,3, 9, -2,5,-1,6);
static void reverse(List<?> list)
									反转列表中元素的顺序。
static void shuffle(List<?> list)
									对List集合元素进行随机排序。
static void sort(List<T> list)
									根据元素的自然顺序 对指定列表按升序进行排序
static <T> void sort(List<T> list, Comparator<? super T> c)
									根据指定比较器产生的顺序对指定列表进行排序。
static void swap(List<?> list, int i, int j)
									在指定List的指定位置i,j处交换元素。
static void rotate(List<?> list, int distance)
									当distance为正数时,将List集合的后distance个元素“整体”移到前面;当distance为负数时,将list集合的前distance个元素“整体”移到后边。该方法不会改变集合的长度

代码演示:

public class CollectionsDemo {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<Integer>();
        //原来写法
        //list.add(12);
        //list.add(14);
        //list.add(15);
        //list.add(1000);
        //采用工具类 完成 往集合中添加元素  
        Collections.addAll(list, 3,-2,9,5,-1,6);
        System.out.println(list);
        //输出:[3, -2, 9, 5, -1, 6]
        //排序:按照升序排序
        Collections.sort(list);
        System.out.println(list);//[-2, -1, 3, 5, 6, 9]
        //根据下标进行交换
        Collections.swap(list, 2, 5);
        System.out.println(list);//输出:[-2, -1, 9, 5, 6, 3]
        //后两个整体移动到前边
		Collections.rotate(list, 2);
        System.out.println(list);//输出:[6, 9, -2, -1, 3, 5]
    }
}

查找、替换操作

static <T> int binarySearch(List<? extends Comparable<? super T>>list, T key)
		使用二分搜索法搜索指定列表,以获得指定对象在List集合中的索引。
		注意:此前必须保证List集合中的元素已经处于有序状态。
static Object max(Collection coll)
		根据元素的自然顺序,返回给定collection 的最大元素。
static Object max(Collection coll,Comparator comp):
		根据指定比较器产生的顺序,返回给定 collection 的最大元素。
static Object min(Collection coll):
		根据元素的自然顺序,返回给定collection 的最小元素。
static Object min(Collection coll,Comparator comp)
		根据指定比较器产生的顺序,返回给定 collection 的最小元素。
static <T> void fill(List<? super T> list, T obj) :
		使用指定元素替换指定列表中的所有元素。
static int frequency(Collection<?> c, Object o)
		返回指定 collection 中等于指定对象的出现次数。
static int indexOfSubList(List<?> source, List<?> target) :
		返回指定源列表中第一次出现指定目标列表的起始位置;如果没有出现这样的列表,则返回-1。
static int lastIndexOfSubList(List<?> source, List<?> target)
		返回指定源列表中最后一次出现指定目标列表的起始位置;如果没有出现这样的列表,则返回-1。
static <T> boolean replaceAll(List<T> list, T oldVal, T newVal)
		使用一个新值替换List对象的所有旧值oldVal

代码演示:

public class CollectionsTest1 {
	public static void main(String[] args) {
		ArrayList list = new ArrayList();
		Collections.addAll(list,3, -2,9,5,-1,6);
		//[3, -2, 9, 5, -1, 6]
		System.out.println(list);
		//输出最大元素9
		System.out.println(Collections.max(list));
		//输出最小元素:-2
		System.out.println(Collections.min(list));
		//将list中的-2用1来代替
		System.out.println(Collections.replaceAll(list, -2, 1));
		//[3, 1, 9, 5, -1, 6]
		System.out.println(list);
		list.add(9);
		//判断9在集合中出现的次数,返回2
		System.out.println(Collections.frequency(list, 9));
		//对集合进行排序
		Collections.sort(list);
		//[-1, 1, 3, 5, 6, 9, 9]
		System.out.println(list);
		//只有排序后的List集合才可用二分法查询,输出2
		System.out.println(Collections.binarySearch(list, 3));

3. Comparator比较器

我们还是先研究这个方法

public static <T> void sort(List<T> list):将集合中元素按照默认规则排序。

不过这次存储的是字符串类型。

public class CollectionsDemo2 {
    public static void main(String[] args) {
        ArrayList<String>  list = new ArrayList<String>();
        list.add("cba");
        list.add("aba");
        list.add("sba");
        list.add("nba");
        //排序方法
        Collections.sort(list);
        System.out.println(list);
    }
}

结果:

[aba, cba, nba, sba]

我们使用的是默认的规则完成字符串的排序,那么默认规则是怎么定义的呢?

排序简单的说就是两个对象之间比较大小,在JAVA中提供了两种比较的方式,一种是比较死板的采用java.lang.Comparable接口实现,一种是灵活的在需要排序的时候去选择的java.util.Comparator接口

若采用public static <T> void sort(List<T> list)方法完成排序,实际上是被排序的类型实现Comparable接口完成比较的功能,在String类型上如下:

public final class String implements java.io.Serializable, Comparable<String>, CharSequence {

String类实现了这个接口并完成了比较规则的定义,但这样就把规则写死了,那我想要字符串按照第一个字符降序排列,这样就要修改String的源代码,这并不方便,我们可以使用

public static <T> void sort(List<T> list,Comparator<? super T> )方法灵活的完成,这个里面涉及到了Comparator这个接口,该接口位于java.util包下,排序是comparator能实现的功能之一,该接口代表一个比较器,比较器具有可比性!顾名思义就是做排序的,通俗地讲需要比较两个对象谁排在前谁排在后,那么比较的方法就是:

  • public int compare(String o1, String o2):比较其两个参数的顺序。

    两个对象比较的结果有三种:大于,等于,小于。

    如果要按照升序排序,
    则o1 小于o2,返回(负数),相等返回0,01大于02返回(正数)
    如果要按照降序排序
    则o1 小于o2,返回(正数),相等返回0,01大于02返回(负数)

操作如下:

public class CollectionsDemo3 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<String>();
        list.add("cba");
        list.add("aba");
        list.add("sba");
        list.add("nba");
        //排序方法  按照第一个单词的降序
        Collections.sort(list, new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return o2.charAt(0) - o1.charAt(0);
            }
        });
        System.out.println(list);
    }
}

结果如下:

[sba, nba, cba, aba]

4. Comparable和Comparator的区别

Comparable接口:强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,类的compareTo方法被称为它的自然比较方法。只能在类中实现compareTo()一次,不能经常修改类的代码实现自己想要的排序。实现此接口的对象列表(和数组)可以通过Collections.sort(和Arrays.sort)进行自动排序,对象可以用作有序映射中的键或有序集合中的元素,无需指定比较器。

Comparator接口强行对某个对象进行整体排序。可以将Comparator 传递给sort方法(如Collections.sort或 Arrays.sort),从而允许在排序顺序上实现精确控制。还可以使用Comparator来控制某些数据结构(如有序set或有序映射)的顺序,或者为那些没有自然顺序的对象collection提供排序。

总的来说就是:

  • Comparable:自己(this)和别人(参数)比较,自己需要实现Comparable接口,重写比较的规则compareTo方法
  • Comparator:相当于找一个第三方的裁判比较
    • Comparator的排序规则
      • o1-o2:升序
      • o2-o1;降序

如:

创建一个学生类,存储到ArrayList集合中完成指定排序操作。

Student 初始类

public class Student{
    private String name;
    private int age;

  //省略两个构造方法和get、set方法
	//重写toString方法
    @Override
    public String toString() {
        return "Student{" +
               "name=‘" + name + ‘\‘‘ +
               ", age=" + age +
               ‘}‘;
    }
}

测试类:

public class Demo {

    public static void main(String[] args) {
        // 创建四个学生对象 存储到集合中
        ArrayList<Student> list = new ArrayList<Student>();

        list.add(new Student("rose",18));
        list.add(new Student("jack",16));
        list.add(new Student("abc",16));
        list.add(new Student("ace",17));
        list.add(new Student("mark",16));

        //让学生 按照年龄排序 升序
		//Collections.sort(list);//要求 该list中元素类型  必须实现比较器Comparable接口

        for (Student student : list) {
            System.out.println(student);
        }

    }
}

发现,当我们调用Collections.sort()方法的时候 程序报错了。

原因:如果想要集合中的元素完成排序,那么必须要实现比较器Comparable接口。

于是我们就完成了Student类的一个实现,如下:

public class Student implements Comparable<Student>{
    ....
    @Override
    public int compareTo(Student o) {
        return this.age-o.age;//升序
    }
}

再次测试,代码就通过了:

Student{name=‘jack‘, age=16}
Student{name=‘abc‘, age=16}
Student{name=‘mark‘, age=16}
Student{name=‘ace‘, age=17}
Student{name=‘rose‘, age=18}

5. 扩展

如果在使用的时候,想要独立的定义规则去使用 可以采用Collections.sort(List list,Comparetor c)方式,自己定义规则:

Collections.sort(list, new Comparator<Student>() {
    @Override
    public int compare(Student o1, Student o2) {
        return o2.getAge()-o1.getAge();//以学生的年龄降序
    }
});

效果:

Student{name=‘rose‘, age=18}
Student{name=‘ace‘, age=17}
Student{name=‘jack‘, age=16}
Student{name=‘abc‘, age=16}
Student{name=‘mark‘, age=16}

如果想要规则更多一些,可以参考下面代码:

Collections.sort(list, new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                // 年龄降序
                int result = o2.getAge()-o1.getAge();//年龄降序

                if(result==0){//第一个规则判断完了 下一个规则 姓名的首字母 升序
                    result = o1.getName().charAt(0)-o2.getName().charAt(0);
                }

                return result;
            }
        });

效果如下:

Student{name=‘rose‘, age=18}
Student{name=‘ace‘, age=17}
Student{name=‘abc‘, age=16}
Student{name=‘jack‘, age=16}
Student{name=‘mark‘, age=16}

集合(二)

原文:https://www.cnblogs.com/lf-637/p/13069641.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!