zoukankan      html  css  js  c++  java
  • Java基础(六)——集合

    一、概述

    1、介绍

      为什么出现集合?
      答:面向对象语言对事物的体现都是以对象的形式,所以为了方便对多个对象的操作,对对象进行存储,集合就是存储对象最常用的一种方式。
      数组和集合类同是容器,有何不同?
      答:数组虽然也可以存储对象,但长度是固定的,集合长度是可变的。数组中可以存储基本数据类型,集合中只能存储对象(引用类型,基本类型的包装类型)。
      为什么会出现这么多的容器呢?
      答:因为每一个容器对数据的存储方式都有不同。这个存储方式称之为:数据结构。
      什么是迭代器呢?
      答:其实就是集合的取出元素的方式。

    2、集合框架

      简图:

      Collection:

      Map:

    二、Collection<E>(jdk1.2)

      集合中存储的都是对象的引用(地址)。

    1、常用方法

      add(Object obj):添加。
      addAll(Collection coll):添加。
      int size():获取有效元素的个数。
      void clear():清空集合。
      boolean isEmpty():是否是空集合。
      boolean contains(Object obj):是否包含某个元素,通过元素的equals方法来判断是否是同一个对象。
      boolean containsAll(Collection c):也是调用元素的equals方法来比较的。两个集合的元素挨个比较。

      boolean remove(Object obj) :删除,通过元素的equals方法判断是否是要删除的那个元素。只会删除找到的第一个元素。
      boolean removeAll(Collection coll):删除,取当前集合的差集。
      boolean retainAll(Collection c):取交集,把交集的结果存在当前集合中,不影响c。
      boolean equals(Object obj):集合是否相等。
      Object[] toArray():转成对象数组。
      hashCode():获取集合对象的哈希值。
      iterator():返回迭代器对象,用于集合遍历。

      代码示例:基本方法

     1 public class Main {
     2     public static void main(String[] args) {
     3         Collection<Object> coll = new ArrayList<>();
     4 
     5         coll.add("AA");
     6         coll.add("BB");
     7         coll.add(123); // 自动装箱
     8         coll.add(new Date());
     9 
    10         System.out.println(coll.size());  // 4
    11 
    12         Collection<Object> coll1 = new ArrayList<>();
    13         coll1.add(456);
    14         coll1.add("CC");
    15         coll1.add(123);
    16 
    17         coll.addAll(coll1);
    18         System.out.println(coll);
    19 
    20         coll.clear(); // 清空集合元素
    21         
    22         System.out.println(coll.isEmpty()); // true
    23     }
    24 }

      代码示例:初始化类

     1 // 预先定义的实体类
     2 public class Person {
     3 
     4     private String name;
     5     private int age;
     6 
     7     // 无参构造器
     8     // 有参构造器
     9     // getter & setter
    10     // toString()
    11 
    12     @Override
    13     public boolean equals(Object o) {
    14         System.out.println("Person equals()....");
    15         if (this == o) return true;
    16         if (o == null || getClass() != o.getClass()) return false;
    17         Person person = (Person) o;
    18         return age == person.age &&
    19                 Objects.equals(name, person.name);
    20     }
    21 
    22     @Override
    23     public int hashCode() {
    24         return Objects.hash(name, age);
    25     }
    26 }
    27 
    28 // 初始化的集合.下面的代码示例,集合都是这个.
    29 public Collection<Object> init() {
    30     Collection<Object> coll = new ArrayList<>();
    31     
    32     coll.add(123);
    33     coll.add(456);
    34     coll.add(new Person("Jerry", 20));
    35     coll.add("Tom");
    36     coll.add(false);
    37 
    38     return coll;
    39 }
     1 public void method1() {
     2     System.out.println(coll.hashCode());
     3 
     4     // 判断当前集合中是否包含obj
     5     // 想要按 Person 对象的属性来比较,需要复写 equals(Object o)
     6     // false --> true
     7     System.out.println(coll.contains(new Person("Jerry", 20)));
     8 
     9     // 当且仅当形参中的所有元素都存在于当前集合中.true
    10     // true
    11     System.out.println(coll.containsAll(Arrays.asList(123, 456)));
    12 
    13     coll.remove(1234);
    14     coll.remove(new Person("Jerry", 20));
    15     // 差集:A - B
    16     coll.removeAll(Arrays.asList(123, 4567));
    17 
    18     System.out.println(coll);
    19 
    20     // 两个集合完全相同,包括顺序相同(这个又具体子类决定)
    21     // System.out.println(coll.equals(coll1));
    22 
    23     // 交集
    24     coll.retainAll(Arrays.asList(123, 666));
    25     System.out.println(coll);
    26 }
    27 
    28 // 结果
    29 -1200490100
    30 Person equals()....
    31 Person equals()....
    32 Person equals()....
    33 true
    34 true
    35 Person equals()....
    36 Person equals()....
    37 Person equals()....
    38 [456, Tom, false]
    39 []

      结果分析:
      ①判定一个集合中是否包含某个对象,contains(Object o)方法,会调用equals(Object o)去比较,所以需要复写equals(Object o)方法。
      ②对象Person是第3个被加入到集合中的,比较的时候需要一个一个比较,所以equals()方法被调用了3次。
      ③remove(Object o)时同样equals()方法被调用了3次。

      代码示例:集合 <--> 数组

    1 public void method2() {
    2     // 集合 --> 数组
    3     final Object[] objects = coll.toArray();
    4     final Object[] array = coll.toArray(new Object[0]);
    5 
    6     // 数组 --> 集合
    7     final List<String> strings = Arrays.asList("A", "B", "C");
    8     final List<Integer> integers = Arrays.asList(123, 456);
    9 }

    2、Iterator<E>(迭代器)

      GOF给迭代器模式的定义为:提供一种方法访问一个容器(container)对象中各个元素,而又不需暴露该对象的内部细节。迭代器模式,就是为容器而生。类似于"公交车上的售票员"、"火车上的乘务员"、"空姐"。
      一种集合的取出元素的方式。定义在集合的内部,用于直接访问集合内 部的元素,是一个内部类。集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前。Iterator主要用于遍历Collection,不包括map。
      用foreach遍历Collection,底层还是使用的迭代器。迭代器原理:

      JDK8源码:

      代码示例:迭代器的使用

     1 // 注意:这里是迭代器里的remove,不是集合里面的。
     2 public void method3() {
     3     Iterator<Object> iterator = coll.iterator();
     4     while (iterator.hasNext()) {
     5         // next():①指针下移 ②将下移以后集合位置上的元素返回
     6         System.out.println(iterator.next());
     7     }
     8 
     9     // 返回一个全新的迭代器
    10     iterator = coll.iterator();
    11     while (iterator.hasNext()) {
    12         // iterator.remove(); 报错IllegalStateException
    13         final Object next = iterator.next();
    14 
    15         if ("Tom".equals(next)) {
    16             iterator.remove();
    17             // iterator.remove(); 报错IllegalStateException
    18         }
    19     }
    20 }

    3、List<E>、Queue<E>、Set<E>(重点)

      List<E>元素是有序的,可重复,因为该集合体系有索引。实现类:
      ArrayList:底层使用的数组数据结构。特点:查询速度很快,但是增删稍慢(会移动后面的元素)。初始长度10,50%延长。线程不同步的。jdk1.2
      LinkedList:底层使用的链表数据结构。特点:增删速度很快,查询稍慢。线程不同步的。
      Vector:底层使用的数组数据结构。初始长度10,100%延长。线程同步的。后被ArrayList替代了。jdk1.0

      Queue<E>有序的,可重复。实现类:
      Deque<E>(接口):
      LinkedList:底层使用的链表数据结构。特点:增删速度很快,查询稍慢。线程不同步的。

      Set<E>元素是无序的,不重复。没有索引。实现类:
      HashSet:底层使用的HashMap(数组+链表)数据结构。无序的,不可重复,线程不同步。可以存储null值。
      TreeSet:底层使用的TreeMap(排序二叉树,红黑树)数据结构。可以对set集合中的元素按指定属性进行排序。有序的。查询速度比List快。

    三、List<E>

    1、ArrayList<E>

      代码示例:迭代器的使用

     1 // 其他常用方法不演示
     2 public class Main {
     3     public static void main(String[] args) {
     4         List<String> list = new ArrayList<>();
     5         list.add("A");
     6         list.add("B");
     7         list.add("C");
     8         list.add("D");
     9 
    10         final Iterator<String> iterator = list.iterator();
    11         while (iterator.hasNext()) {
    12             System.out.print(iterator.next());
    13         }
    14     }
    15 }
    16 
    17 // 结果
    18 // A B C D

    2、Vector<E>

      枚举就是Vector特有的取出方式,和迭代器很像,其实是一样的,由于名称过长,后被迭代器取代了。

     1 public static void main(String[] args) {
     2     Vector<String> vector = new Vector<>();
     3 
     4     vector.add("A");
     5     vector.add("B");
     6 
     7     // 返回此向量的组件的枚举.
     8     final Enumeration<String> elements = vector.elements();
     9 
    10     while (elements.hasMoreElements()) {
    11         System.out.println(elements.nextElement());
    12     }
    13 }
    14 
    15 // 结果
    16 // A B

    3、Stack<E>(栈)

     1 // 先进后出
     2 public class Main {
     3     public static void main(String[] args) {
     4         Stack<String> stack = new Stack<>();
     5 
     6         // 压栈
     7         stack.push("A");
     8         stack.push("B");
     9         stack.push("C");
    10 
    11         while (!stack.empty()) {
    12             // 从栈顶弹出一个.会删除元素
    13             System.out.println(stack.pop());
    14         }
    15 
    16         // 从栈顶弹出一个.不会删除元素
    17         // stack.peek();
    18     }
    19 }
    20 
    21 // 结果
    22 C B A

    四、Queue<E>

    1、Queue<E>(队列)

     1 // 先进先出
     2 public class Main {
     3     public static void main(String[] args) {
     4         Queue<Integer> queue = new LinkedList<>();
     5 
     6         // 添加.入队
     7         queue.offer(1);
     8         queue.offer(10);
     9         queue.offer(5);
    10         queue.offer(9);
    11         System.out.println(queue);
    12 
    13         // 出队
    14         final Integer poll = queue.poll();
    15         System.out.println(poll);
    16         System.out.println(queue);
    17 
    18         // 获取.队头
    19         final Integer peek = queue.peek();
    20         System.out.println(peek);
    21         System.out.println(queue);
    22     }
    23 }
    24 
    25 // 结果
    26 [1, 10, 5, 9]
    27 1
    28 [10, 5, 9]
    29 10
    30 [10, 5, 9]

    2、Deque<E>(双端队列)

    3、LinkedList<E>(双向链表)

      对于频繁的插入或删除元素的操作,建议使用LinkedList类,效率较高。

      特有方法
      addFirst():在第一个位置添加元素。
      addLast()
      getFirst():获取第一个元素,但不删除元素。若没有,出现异常。
      getLast()
      removeFirst():获取元素,但是删除元素。若没有,出现异常。
      removeLast()

      在jdk1.6以后出现了替代方法。
      offerFirst():在第一个位置添加元素。
      offerLast()
      peekFirst():获取元素,但不删除元素。若没有,返回null。
      peekLast()
      pollFirst():获取元素,但是删除元素。若没有,返回null。
      pollLast()

    五、Set<E>

    1、介绍

      Set<E>元素是不重复。没有索引。实现类:
      HashSet:底层使用的HashMap(数组+链表)数据结构。无序的,不可重复,线程不安全。可以存储null值。
      LinkedHashSet:底层使用的 LinkedHashMap。无序的,不可重复。
      TreeSet:底层使用的TreeMap(排序二叉树,红黑树)数据结构。可以对set集合中的元素按指定属性进行排序。有序的。查询速度比List快。

      以HashSet为例理解无序、不重复:
      无序的:不等于随机性(并不是说打印的顺序没有按照添加的顺序),是指的存储的数据在底层数组中并非按照数组索引的顺序依次添加,而是根据数据的哈希值确定的索引位置。
      不重复:调用对象equals()方法保证相同元素只添加一次。
      要求:向Set(主要指:HashSet、LinkedHashSet)中添加的数据,其所在的类一定要重写hashCode()和equals()。重写的hashCode()和equals()尽可能保持一致性:相等的对象必须具有相等的散列码。

      重写 hashCode() 方法的基本原则:
      (1)程序运行时,同一个对象多次调用 hashCode() 方法应该返回相同的值。
      (2)当两个对象的 equals() 方法比较返回 true 时,这两个对象的 hashCode() 方法的返回值也应相等。
      (3)对象中用作 equals() 方法比较的 Field,都应该用来计算 hashCode 值。

      重写 equals() 方法的基本原则:
      当一个类有自己特有的"逻辑相等"概念,需要重写equals()的时候,总是要重写hashCode(),根据一个类的equals方法(改写后),两个截然不同的实例有可能在逻辑上是相等的,但是,根据Object.hashCode()方法,它们仅仅是两个对象。因此,违反了"相等的对象必须具有相等的散列码"。
      结论:复写equals方法的时候一般都需要同时复写hashCode方法。通常参与计算hashCode的对象的属性也应该参与到equals()中进行计算。

    2、HashSet<E>

      HashSet 按 Hash 算法来存储集合中的元素,因此具有很好的存取、查找和删除性能。
      底层数据结构:看JDK8的源码,HashSet底层是一个HashMap。而HashMap底层是数组+链表(7),数组+链表+红黑树(8)。
      元素添加过程:由于HashSet底层就是HashMap,所以它的原理只需要了解HashMap即可。

     1 // API使用
     2 public class Main {
     3     public static void main(String[] args) {
     4         Set<String> set1 = new HashSet<>();
     5         Set<String> set2 = new HashSet<>();
     6 
     7         set1.add("a");
     8         set1.add("b");
     9         set1.add("c");
    10 
    11         set2.add("c");
    12         set2.add("d");
    13         set2.add("e");
    14 
    15         // 取交集
    16         set1.retainAll(set2);
    17         System.out.println(set1); // [c]
    18         // 取并集
    19         // set1.addAll(set2);
    20         // System.out.println(set1); // [a, b, c, d, e]
    21         // 取差集
    22         // set1.removeAll(set2);
    23         // System.out.println(set1); // [a, b]
    24     }
    25 }

    3、LinkedHashSet<E>

      作为HashSet的子类,LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置(依然是无序的), 但它同时使用双向链表维护元素的添加次序,这使得元素看起来是以插入顺序保存的。也可以按照添加的顺序遍历。
      对于频繁的遍历操作,LinkedHashSet效率高于HashSet。LinkedHashSet插入性能略低于 HashSet。
      代码示例:

     1 public class Main {
     2     public static void main(String[] args) {
     3         Set set = new LinkedHashSet();
     4         set.add("AA");
     5         set.add(456);
     6         set.add(456);
     7 
     8         set.add(new User("刘德华", 60));
     9         
    10         Iterator iterator = set.iterator();
    11         while (iterator.hasNext()) {
    12             System.out.println(iterator.next());
    13         }
    14     }
    15 }
    16 
    17 // 结果:按照添加的顺序遍历.
    18 AA
    19 456
    20 User{name='刘德华', age=60}
    21 
    22 // 若Set set = new HashSet();则遍历不按照添加的顺序

      底层结构:

    4、TreeSet<E>

      底层是排序二叉树,所以①必须是相同类型的对象。②必须可排序。自然排序(实现Comparable接口)和定制排序(Comparator)。
      自然排序中,判断两个对象是否相同的标准为:compareTo()返回0,不是equals()。
      定制排序中,判断两个对象是否相同的标准为:compare()返回0。不是equals()。
      若compareTo始终返回0,则add始终只有一个元素。因为后面的都认为与前面的相同,因此没有加入进来。判断两个对象是否相等的标准是compareTo返回值相同。
      底层数据结构:看JDK8的源码,TreeSet底层是一个TreeMap。采用红黑树的存储结构。有序的。查询速度比List快。
      元素添加过程:
      代码示例:让User具有可比性,自然排序:Comparable

     1 public class User implements Comparable<User> {
     2     private String name;
     3     private int age;
     4 
     5     // 无参构造器
     6     // 有参构造器
     7     // getter & setter
     8     // toString()
     9     // 可以不复写equals、hashCode
    10     
    11     @Override
    12     public int compareTo(User user) {
    13         System.out.println(this.name + "-------------" + user.getName());
    14 
    15         if (this.age > user.age) {
    16             return 1;
    17         }
    18         if (this.age < user.age) {
    19             return -1;
    20         }
    21         return 0;
    22     }
    23 }
    24 
    25 // 测试类
    26 public static void main(String[] args) {
    27     TreeSet<User> treeSet = new TreeSet<>();
    28 
    29     treeSet.add(new User("java1", 30));
    30     treeSet.add(new User("java2", 20));
    31     treeSet.add(new User("java3", 31));
    32     treeSet.add(new User("java4", 60));
    33 
    34     final Iterator<User> iterator = treeSet.iterator();
    35     while (iterator.hasNext()) {
    36         final User user = iterator.next();
    37         System.out.println(user.getName() + "---------" + user.getAge());
    38     }
    39 }
    40 
    41 // 结果
    42 java1-------------java1 // 第一次
    43 java2-------------java1 // 2和1比,2在左边
    44 java3-------------java1 // 3和1比,3在右边
    45 java4-------------java1 // 4和1比,4在右边
    46 java4-------------java3 // 4和3比,4在右边
    47 java2---------20
    48 java1---------30
    49 java3---------31
    50 java4---------60

      结果分析:不难理解打印结果的比较次数。

      代码示例:指定比较器,定制排序:Comparator

     1 // 比较器
     2 public static void main(String[] args) {
     3     final Comparator<User> comparator = new Comparator<User>() {
     4         // 按照年龄从小到大排列
     5         @Override
     6         public int compare(User o1, User o2) {
     7             return Integer.compare(o1.getAge(), o2.getAge());
     8         }
     9     };
    10 
    11     TreeSet<User> treeSet = new TreeSet<>(comparator);
    12 
    13     treeSet.add(new User("java1", 30));
    14     treeSet.add(new User("java2", 20));
    15     treeSet.add(new User("java3", 31));
    16     treeSet.add(new User("java4", 60));
    17     treeSet.add(new User("java5", 60));
    18 
    19     final Iterator<User> iterator = treeSet.iterator();
    20     while (iterator.hasNext()) {
    21         final User user = iterator.next();
    22         System.out.println(user.getName() + "---------" + user.getAge());
    23     }
    24 }
    25 
    26 // 结果
    27 java2---------20
    28 java1---------30
    29 java3---------31
    30 java4---------60

    六、Map<K,V>

    1、介绍(重点)

      双列数据,存储key-value对的数据。用作键的对象必须实现hashCode方法和equals方法。实现类:
      HashMap:底层使用的(数组+链表7+红黑树8)数据结构。特点:可以存入null键null值。线程不同步,效率高。jdk1.2
      Hashtable:底层使用的(数组+链表)数据结构。特点:不可以存入null键null值。线程同步,效率低。jdk1.0
      TreeMap:底层使用的(排序二叉树,红黑树)数据结构。实现了SortedMap有序序列接口,特点:可以用于给map集合中的键按指定属性进行排序。线程不同步。
      WeakHashMap:底层使用的(数组+链表)数据结构。特点:键是"弱键"。

    2、HashMap<K,V>

      HashMap中的Entry对象是无序排列的。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。

      代码示例:三种遍历方式

     1 public class Main {
     2     public static void main(String[] args) {
     3         Map<String, String> map = new HashMap<>();
     4         map.put("A", "1");
     5         map.put("B", "2");
     6         map.put("c", "3");
     7 
     8         // 1.获取键集:keySet()
     9         final Set<String> keySet = map.keySet();
    10         for (String key : keySet) {
    11             System.out.println("keySet()----" + key + "----" + map.get(key));
    12         }
    13 
    14         // 2.获取值集:values()
    15         final Collection<String> values = map.values();
    16         for (String value : values) {
    17             System.out.println("values()----" + value);
    18         }
    19 
    20         // 3.获取entry集:entrySet()
    21         final Set<Map.Entry<String, String>> entries = map.entrySet();
    22         for (Map.Entry<String, String> entry : entries) {
    23             System.out.println("entrySet()----" + entry.getKey() + "----" + entry.getValue());
    24         }
    25     }
    26 }

    3、Hashtable<K,V>

    4、TreeMap<K,V>

      向TreeMap中添加key-value,要求key①必须是相同类型的对象。②必须可排序。用于给map集合中的键按指定属性进行排序。
      代码示例:让User具有可比性,自然排序:Comparable

     1 public class User implements Comparable<User> {
     2     private String name;
     3     private int age;
     4 
     5     // 无参构造器
     6     // 有参构造器
     7     // getter & setter
     8     // toString()
     9     // 可以不复写equals、hashCode
    10 
    11     // 年龄从小到大排列
    12     @Override
    13     public int compareTo(User user) {
    14         System.out.println(this.name + "---------" + user.getName());
    15         return Integer.compare(this.age, user.age);
    16     }
    17 }
    18 
    19 // 测试类
    20 public static void main(String[] args) {
    21     TreeMap<User, Integer> treeMap = new TreeMap<>();
    22     User u1 = new User("Tom", 23);
    23     User u2 = new User("Jerry", 32);
    24     User u3 = new User("Jack", 20);
    25     User u4 = new User("Rose", 18);
    26 
    27     treeMap.put(u1, 98);
    28     treeMap.put(u2, 89);
    29     treeMap.put(u3, 76);
    30     treeMap.put(u4, 100);
    31 
    32     for (Map.Entry<User, Integer> entry : treeMap.entrySet()) {
    33         System.out.println(entry.getKey() + "---->" + entry.getValue());
    34     }
    35 }
    36 
    37 // 结果
    38 Tom---------Tom
    39 Jerry---------Tom
    40 Jack---------Tom
    41 Rose---------Tom
    42 Rose---------Jack
    43 User{name='Rose', age=18}---->100
    44 User{name='Jack', age=20}---->76
    45 User{name='Tom', age=23}---->98
    46 User{name='Jerry', age=32}---->89

      结果分析:按键 User 指定属性 age 排序,不难理解打印结果的比较次数。排序二叉树:

      代码示例:指定比较器,定制排序:Comparator

     1 // 比较器
     2 public static void main(String[] args) {
     3     TreeMap<User, Integer> treeMap = new TreeMap<>(new Comparator<User>() {
     4         @Override
     5         public int compare(User u1, User u2) {
     6             return Integer.compare(u1.getAge(), u2.getAge());
     7         }
     8     });
     9 
    10     User u1 = new User("Tom", 23);
    11     User u2 = new User("Jerry", 32);
    12     User u3 = new User("Jack", 20);
    13     User u4 = new User("Rose", 18);
    14 
    15     treeMap.put(u1, 98);
    16     treeMap.put(u2, 89);
    17     treeMap.put(u3, 76);
    18     treeMap.put(u4, 100);
    19 
    20     for (Map.Entry<User, Integer> entry : treeMap.entrySet()) {
    21         System.out.println(entry.getKey() + "---->" + entry.getValue());
    22     }
    23 }
    24 
    25 // 结果.和自然排序的结果一致.
    26 User{name='Rose', age=18}---->100
    27 User{name='Jack', age=20}---->76
    28 User{name='Tom', age=23}---->98
    29 User{name='Jerry', age=32}---->89

    5、Properties

      常用来处理配置文件。key和value都是String类型。
      代码示例:读取属性文件

     1 // jdbc.properties
     2 name=Tom
     3 password=abc123
     4 
     5 // 未关闭资源
     6 public static void main(String[] args) throws Exception {
     7     Properties pros = new Properties();
     8 
     9     FileInputStream fis = new FileInputStream("jdbc.properties");
    10     pros.load(fis);
    11 
    12     String name = pros.getProperty("name");
    13     String password = pros.getProperty("password");
    14 
    15     System.out.println("name = " + name + ", password = " + password);
    16 }
    17 
    18 // 结果
    19 name = Tom, password = abc123

    6、ConcurrentHashMap<K,V>

      请查看标签:JDK源码。

    作者:Craftsman-L

    本博客所有文章仅用于学习、研究和交流目的,版权归作者所有,欢迎非商业性质转载。

    如果本篇博客给您带来帮助,请作者喝杯咖啡吧!点击下面打赏,您的支持是我最大的动力!

  • 相关阅读:
    atom介绍
    举例介绍重构(译)
    java单双派机制理解
    AngularJS开发指南03:HTML编译器
    AngularJS开发指南02:引导程序
    AngularJS开发指南01:AngularJS简介
    1.angular之Hello World
    31天重构学习笔记(java版本)
    一个农夫的故事 分类: 其他 2015-01-24 16:44 104人阅读 评论(0) 收藏
    一个农夫的故事 分类: 其他 2015-01-24 16:44 103人阅读 评论(0) 收藏
  • 原文地址:https://www.cnblogs.com/originator/p/15328879.html
Copyright © 2011-2022 走看看