英雄有泪

欲学惊人艺,须下苦功夫,深功出巧匠,苦练出真功。

导航

黑马程序员_Java基础:集合总结

一、集合概念

  相信大家都知道,java是一门面向对象的编程语言,而对事物的体现都是以对象的形式,所以为了方便对多个对象的操作,就对对象进行存储进行存储,集合就是存储对象最常用的一种方式,我们可以把集合看成是一个容器。

  同样,数组也是一种容器,那么集合和它有什么不同?

  1.数组虽然也可以存储对象,但是长度是固定的,而集合长度是可变的。
  2.数组中可以存储基本数据类型数据,集合只能存储对象。
  3.数组中储存的基本数据类型数据或对象要类型相同,而集合可以存储不同类型的对象。

  所以,当事先不知道要存放数据的个数,或者需要一种比数组下标存取机制更灵活的方法时,就需要用到集合类

 

二、集合的构成与分类

下面是一张网上找到的图片,对于集合中的类和接口有比较清晰的表现:

  1.Collection接口:

  Collection是最基本的集合接口,一个Collection代表一组对象,即Collection的元素(Elements)。一些 Collection允许相同的元素而另一些不行。一些能排序而另一些不行。Java JDK不提供直接继承自Collection的类,Java JDK提供的类都是继承自Collection的”子接口“如List和Set。

  所有实现Collection接口的类都必须提供两个标准的构造函数:无参数的构造函数用于创建一个空的Collection,另一个 Collection参数的构造函数用于创建一个新的Collection,这个新的Collection与传入的Collection有相同的元素。其实后一个构造函数就是复制一个Collection。

  那么如何取出Collection中的元素?无论Collection的实际类型如何,它都支持一个iterator()的方法,该方法返回一个迭代器,使用该迭代器即可逐一访问Collection中每一个元素。典型的用法如下:

1     Iterator it = collection.iterator(); // 获得一个迭代器。
2     while(it.hasNext()) {    //通过迭代器判断集合中是否还有元素。
3       Object obj = it.next(); // 得到下一个元素,类型为Object。
4     }

  Collection定义了集合框架的共性功能(方法):
  ①添加:add(e);  addAll(collection);
  ②删除:remove(e);  removeAll(collection);  clear();
  ③判断:contains(e);  isEmpty();
  ④获取:iterator();  size();
  ⑤获取交集:retainAll();
  ⑥集合变数组:toArray();

  Collection接口派生的两个接口是List和Set。

 

  (1)List接口

  List是有序的Collection,使用此接口能够精确的控制每个元素插入的位置。用户能够使用索引(元素在List中的位置,类似于数组下标)来访问List中的元素,这类似于Java的数组。List允许有相同的元素,List集合判断元素是否相同是通过对象的equals()方法。

  除了具有Collection接口必备的iterator()方法外,List还提供一个listIterator()方法,返回一个 ListIterator接口,和标准的Iterator接口相比,ListIterator多了一些add()之类的方法,允许添加,删除,设定元素, 还能向前或向后遍历。

  实现List接口的常用类有ArrayList,LinkedList,Vector。

 

  ArrayList类:

  ArrayList实现了可变大小的数组。它允许所有元素,包括null。

  每个ArrayList实例都有一个容量(Capacity),即用于存储元素的数组(数组结构)的大小。这个容量可随着不断添加新元素而自动增加。当需要插入大量元素时,在插入前可以调用ensureCapacity方法来增加ArrayList的容量以提高插入效率。

  注意ArrayListList是非同步的,如果多个线程同时访问一个ArrayList实例,而其中至少一个线程从结构上修改了该列表,则它必须保持外部同步。一种解决方法是在创建ArrayList时构造一个同步的List来包装它 

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

  数组结构(每个元素都有角标):

 

  LinkedList类:

  LinkedList实现了List接口,存储数据的方式是双链表,允许null元素。此外LinkedList 类还为在列表的开头及结尾 get、remove 和 insert 元素提供了统一的命名方法。这些操作使LinkedList可被用作堆栈(stack),队列(queue)或双向队列(deque)。

  注意LinkedList是非同步的,如果多个线程同时访问一个链接列表,而其中至少一个线程从结构上修改了该列表,则它必须保持外部同步。一种解决方法是在创建LinkedList时构造一个同步的List来包装

1   List list = Collections.synchronizedList(new LinkedList(...));

  双链表结构(每个元素只知道前后的元素):

  Arraylist和Linkedlist的不同处:
  ①ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
  ②对于随机访问get和set,ArrayList优于LinkedList,因为LinkedList要移动指针。
  ③对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。

 

  Vector类:

  Vector非常类似ArrayList,也是数组结构存储数据,但是Vector是同步的。由Vector创建的Iterator,虽然和 ArrayList创建的Iterator是同一接口,但是,因为Vector是同步的,当一个Iterator被创建而且正在被使用,另一个线程改变了 Vector的状态(例如,添加或删除了一些元素),这时调用Iterator的方法时将抛出 ConcurrentModificationException,因此必须捕获该异常。同样,当需要插入大量元素时,在插入前可以调用ensureCapacity方法来增加ArrayList的容量以提高插入效率。另外,Vector还有特有的取出元素的方式,通过它的elements()获取Enumeration枚举来得到元素,实际上Enumeration和Iterator功能是一样的。

  Vector与ArrayList不同之处是:
  ①Vector是线程同步的,所以它也是线程安全的,而Arraylist是线程异步的,是不安全的。如果不考虑到线程的安全因素,一般用arraylist效率比较高。
  ②如果集合中的元素的数目大于目前集合数组的长度时,vector增长率为目前数组长度的100%,而arraylist增长率为目前数组长度的50%.如过在集合中使用数据量比较大的数据,用vector有一定的优势。
  ③Vector还有特有的取出元素的方式,Enumeration枚举。

 

  (2)Set接口  

  Set是一种不包含重复的元素的Collection,即任意的两个元素e1和e2都有e1.equals(e2)=false,Set最多有一个null元素。

  另外要注意,必须小心操作可变对象(Mutable Object)。如果一个Set中的可变元素改变了自身状态导致Object1.equals(Object2)=true将导致一些问题。

  实现Set接口的常用类有HashSet和TreeSet。

 

  HashSet类:

  Hashset存储数据的结构是哈希表,它不保证 set 的迭代顺序,特别是它不保证该顺序恒久不变。也就是说,HashSet中的元素是无序的。它是通过元素的hashCode()和equals()方法来保证元素的唯一性。如果元素的hashCode值不同,则不需调用equals()进行比较。如果元素的HashCode值相同,才会调用equals()判断两个元素是否相同。基于这种唯一性标准,HashSet中判断元素是否存在,以及删除等操作,依赖的方法也是元素的hashCode()和equals()方法。

  注意HashSet是非同步的,如果多个线程同时访问一个HashSet,而其中至少一个线程修改了该 set,那么它必须 保持外部同步。一种解决方法是在创建HashSet时构造一个同步的Set来包装它:

1     Set s = Collections.synchronizedSet(new HashSet(...));

  哈希表结构(当元素比较hashCode不同时,元素处于不同区域;当元素比较hashCode相同,equals比较不同时,元素会在同个区域):

 

  TreeSet类:

  TreeSet存储数据的结果是二叉树(红黑树),基于实现NavigableSet接口,它是使用元素的自然顺序对元素进行排序(当元素自身具有比较性时),或者根据创建 set 时提供的 Comparator进行排序(当元素自身不具备比较性,或者比较性不合需求时),具体取决于使用的构造方法。也就是说,TreeSet中的元素是有序的。它是通过元素的compareTo()或者构造时使用的比较器Comparator中的compare()方法来保证元素的唯一性。

  注意TreeSet是非同步的,如果多个线程同时访问一个TreeSet,而其中至少一个线程修改了该 set,那么它必须 外部同步。一种解决方法是在创建TreeSet时构造一个同步的Set来包装它:

1     SortedSet s = Collections.synchronizedSortedSet(new TreeSet(...)); 

  二叉树结构(比较后,小的元素在左子树,大的元素在右子树):

  

 

  2.Map接口

  Map没有继承Collection接口,Map提供key(键)到value(值)的映射。一个Map中不能包含相同的key,每个key只能映射一个 value。Map接口提供3种集合的视图,允许以键集、值集或键-值映射关系集的形式查看某个映射的内容。映射顺序定义为迭代器在映射的 collection 视图上返回其元素的顺序。某些映射实现可明确保证其顺序,如 TreeMap 类;另一些映射实现则不保证顺序,如 HashMap 类。Map集合没有直接取出元素的方法,想获取Map集合中的元素,需要先用entrySet()方法获取键-值映射关系的Set集合,或者使用keySet()方法获取键的Set集合。

  Map集合的共性功能(方法):
  ①添加:put(K key, V value);  putAll(Map<? extends K,? extends V> m);
  ②删除: remove(Object key);  clear();
  ③判断:containsValue(Object value);  containsKey(Object key);  isEmpty();
  ④获取:get(Object key);  size();  values();  entrySet();  keySet();

  实现Map接口的常用类有HashMap,TreeMap。

 

  HashMap类:

  基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作,并允许使用null键和null值(替代Hashtable)。其键集上的对象是通过元素的hashCode()和equals()方法来保证元素的唯一性。如果元素的hashCode值不同,则不需调用equals()进行比较。如果元素的HashCode值相同,才会调用equals()判断两个元素是否相同。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。

  注意HashMap是非同步的,如果多个线程同时访问一个哈希映射,而其中至少一个线程从结构上修改了该映射,则它必须保持外部同步。一种解决方法是在创建HashMap时构造一个同步的Map来包装它:

1    Map m = Collections.synchronizedMap(new HashMap(...));

 

  TreeMap类:

  基于二叉树的 Map 接口的实现。它所包含的映射关系是有序的(以键为标准),其键的排序方式和TreeSet中的元素相同。

  注意TreeMap是非同步的,如果多个线程同时访问一个映射,并且其中至少一个线程从结构上修改了该映射,则其必须外部同步。一种解决方法是在创建TreeMap时构造一个同步的Map来包装它:

1     SortedMap m = Collections.synchronizedSortedMap(new TreeMap(...));

 

三、集合的应用

1.例子一(将自定义对象作为元素存到ArrayList集合中,并去除重复元素):

 1 import java.util.ArrayList;
 2 import java.util.Iterator;
 3 
 4 /*
 5 将自定义对象作为元素存到ArrayList集合中,并去除重复元素。
 6 比如:存人对象。同姓名同年龄,视为同一个人。为重复元素。
 7 
 8 思路:
 9 1,对人描述,将数据封装进人对象。
10 2,定义容器,将人存入。
11 3,取出。
12 
13 List集合判断元素是否相同,依据是元素的equals方法。
14 */
15 class Person {
16     private String name;
17     private int age;
18     Person(String name,int age) {
19         this.name = name;
20         this.age = age;
21     }
22     
23     public boolean equals(Object obj) {
24         if(!(obj instanceof Person))
25             return false;
26         Person p = (Person)obj;
27         return this.name.equals(p.name) && this.age == p.age;
28     }
29     public String getName() {
30         return name;
31     }
32     public int getAge() {
33         return age;
34     }
35 }
36 
37 public class ArrayListTest {
38     public static void main(String[] args) {
39         ArrayList<Person> al = new ArrayList<Person>();        //使用泛型指定集合元素类型。
40 
41         al.add(new Person("lisi01",30));
42         al.add(new Person("lisi02",32));
43         al.add(new Person("lisi02",32));
44         al.add(new Person("lisi04",35));
45         al.add(new Person("lisi03",33));
46         al.add(new Person("lisi04",35));
47         
48         al = singleElement(al);
49         System.out.println("remove 03 :"+al.remove(new Person("lisi03",33)));//remove方法底层也是依赖于元素的equals方法。
50 
51         Iterator<Person> it = al.iterator();
52         while(it.hasNext()) {    //判断集合中是否还有元素。
53             Person p = it.next();
54             System.out.println(p.getName()+"::"+p.getAge());
55         }
56     }
57 
58 //    去掉重复元素。
59     public static ArrayList<Person> singleElement(ArrayList<Person> al) {
60         //定义一个临时容器。
61         ArrayList<Person> newAl = new ArrayList<Person>();
62         Iterator<Person> it = al.iterator();
63         while(it.hasNext()) {
64             Person p = it.next();
65 
66             if(!newAl.contains(p))
67                 newAl.add(p);
68         }
69         return newAl;
70     }
71 }

运行结果为:

remove 03 :true
lisi01::30
lisi02::32
lisi04::35

 

2.例子二(使用LinkList模拟一个堆栈或者队列数据结构):

 1 import java.util.LinkedList;
 2 
 3 /*
 4 使用LinkList模拟一个堆栈或者队列数据结构。
 5 
 6 堆栈:先进后出,如同一个杯子。
 7 队列:先进先出,如同一个水管。
 8  */
 9 class DuiLie {
10     private LinkedList link;
11     private LinkedList link_2;
12     
13     DuiLie() {
14         link = new LinkedList();
15         link_2 = new LinkedList();
16     }
17 
18     public void myAdd(Object obj) {
19         link.addFirst(obj);
20         link_2.addFirst(obj);
21     }
22 
23     public Object myGet() {
24         return link.removeFirst();        
25     }
26 
27     public Object myGet_2() {
28         return link.removeLast();
29     }
30 
31     public boolean isNull() {
32         if (link.isEmpty()) {
33             try {
34                 return link.isEmpty();
35             }
36             finally {
37                 link.addAll(link_2);
38             }
39         }
40         return link.isEmpty();                
41     }
42 }
43 
44 
45 public class LinkedListTest {
46     public static void main(String[] args) {
47         DuiLie dl = new DuiLie();
48 
49         dl.myAdd("java01");
50         dl.myAdd("java02");
51         dl.myAdd("java03");
52         dl.myAdd("java04");
53         
54         //堆栈形式;先进后出
55         while (!dl.isNull())
56             System.out.println(dl.myGet());
57         System.out.println();
58 
59         //队列形式:先进先出
60         while (!dl.isNull())
61             System.out.println(dl.myGet_2());
62     }
63 }

运行结果为:

java04
java03
java02
java01

java01
java02
java03
java04

 

3.例子三(Vector枚举):

 1 import java.util.Enumeration;
 2 import java.util.Vector;
 3 
 4 /*
 5 枚举就是Vector特有的取出方式。
 6 发现枚举和迭代器很像。
 7 其实枚举和迭代是一样的。
 8 
 9 因为枚举的名称以及方法的名称都过长。
10 所以被迭代器取代了。
11 */
12 public class VectorDemo {
13     public static void main(String[] args) {
14         Vector<String> v = new Vector<String>();
15 
16         v.add("java01");
17         v.add("java02");
18         v.add("java03");
19         v.add("java04");
20 
21         Enumeration<String> en = v.elements();
22         while(en.hasMoreElements()) {    //判断集合中是否还有元素。
23             System.out.println(en.nextElement());
24         }
25     }
26 }

运行结果为:

java01
java02
java03
java04

4.例子四(往HashSet集合中存入自定义对象):

 1 import java.util.HashSet;
 2 import java.util.Iterator;
 3 
 4 /*
 5 往HashSet集合中存入自定义对象。
 6 以人为例:
 7 姓名和年龄相同视为同一个人,重复元素。
 8 */
 9 public class HashSetTest {
10     public static void main(String[] args) {
11         HashSet<Person> hs = new HashSet<Person>();
12 
13         hs.add(new Person("a1",11));
14         hs.add(new Person("a2",12));
15         hs.add(new Person("a3",13));
16         hs.add(new Person("a2",12));
17 //        查看是否包含指定元素。
18         System.out.println("a1:"+hs.contains(new Person("a2",12)));
19 //        删除一个元素。
20         System.out.println(hs.remove(new Person("a3",13)));
21 
22         Iterator<Person> it = hs.iterator();
23         while (it.hasNext()) {
24             Person p = it.next();
25             System.out.println(p.getName()+"..."+p.getAge());
26         }
27     }
28 }
29 
30 
31 class Person {
32     private String name;
33     private int age;
34 
35     Person(String name,int age) {
36         this.name = name;
37         this.age = age;
38     }
39 
40     public String getName() {
41         return name;
42     }
43 
44     public int getAge() {
45         return age;
46     }
47 
48     public int hashCode() {
49         return name.hashCode()+age*39;
50     }
51 
52     public boolean equals(Object obj) {
53         if (!(obj instanceof Person))
54             return false;
55         Person p = (Person)obj;
56         return this.name.equals(p.name) && this.age == p.age;
57     }
58 }

运行结果为:

a1:true
true
a2...12
a1...11

 

5.例子五(使用TreeSet添加字符串,字符串按长度排序):

 1 import java.util.Comparator;
 2 import java.util.Iterator;
 3 import java.util.TreeSet;
 4 
 5 /*
 6 按照字符串长度排序。
 7 
 8 字符串本身具备比较性,但是它的比较方式不是所需要的。
 9 这时只能使用比较器。
10  */
11 public class TreeSetTest {
12     public static void main(String[] args) {
13 //        元素本身的比较性不合需求,构造时加上比较器。
14         TreeSet<String> ts = new TreeSet<String>(new StrLenCompare());
15 
16         ts.add("abcd");
17         ts.add("cc");
18         ts.add("cba");
19         ts.add("aaa");
20         ts.add("z");
21         ts.add("hahaha");
22 
23         Iterator<String> it = ts.iterator();
24         while (it.hasNext())
25         {
26             String s = (String)it.next();
27             System.out.println(s+"...length..."+s.length());
28         }
29     }
30 }
31 
32 //比较器比较内容。
33 class StrLenCompare implements Comparator<String> {
34     public int compare(String s1,String s2) {
35         int num = new Integer(s1.length()).compareTo(new Integer(s2.length()));
36         if (num==0)
37             return s1.compareTo(s2);
38         return num;
39     }
40 }

运行结果为:

z...length...1
cc...length...2
aaa...length...3
cba...length...3
abcd...length...4
hahaha...length...6

 

6.例子六(使用HashMap添加不重复的元素(映射关系)):

 1 import java.util.HashMap;
 2 import java.util.Iterator;
 3 import java.util.Map;
 4 import java.util.Set;
 5 
 6 /*
 7 每一个学生都与对应的归属地。
 8 学生Student,地址String。
 9 学生属性:姓名,年龄。
10 注意:姓名和年龄相同的视为同一个学生。
11 保证学生的唯一性。
12 
13 思路:
14 1,描述学生。
15 2,因为学生和地址存在映射关系。定义Map容器,将学生作为键,地址作为值存入。
16 3,获取Map集合中的元素。
17 */
18 class Student implements Comparable<Student> {
19     private String name;
20     private int age;
21     Student(String name,int age) {
22         this.name = name;
23         this.age = age;
24     }
25 
26     public int compareTo(Student s) {
27         int num = new Integer(this.age).compareTo(new Integer(s.age));
28         if (num==0) {
29             return this.name.compareTo(s.name);
30         }
31         return num;
32     }
33 
34     public int hashCode() {
35         return this.name.hashCode()+this.age*34;
36     }
37 
38     public boolean equals(Object obj) {    //这里重写的是父类Object的equals方法,参数类型必须Object,否则重写失败。
39         if(!(obj instanceof Student))
40             throw new ClassCastException("类型不匹配");
41         Student s = (Student)obj;
42         return this.name.equals(s.name) && this.age==s.age;
43     }    
44 
45     public String getName() {
46         return name;
47     }
48 
49     public int getAge() {
50         return age;    
51     }
52 
53     public String toString() {
54         return name+":::"+age;
55     }
56 }
57 
58 
59 public class HashMapTest {
60     public static void main(String[] args) {
61         HashMap<Student,String> hm = new HashMap<Student,String>();
62 
63         hm.put(new Student("lisi1",21),"beijing");
64         hm.put(new Student("lisi1",21),"tianjin");
65         hm.put(new Student("lisi2",22),"shanghai");
66         hm.put(new Student("lisi3",23),"nanjing");
67         hm.put(new Student("lisi4",24),"wuhan");
68 
69         //第一种取出方式:keySet
70         Set<Student> keySet = hm.keySet();
71         Iterator<Student> it = keySet.iterator();
72 
73         while (it.hasNext()) {
74             Student s = it.next();
75             String addr = hm.get(s);
76             System.out.println(s+"..."+addr);
77         }
78         System.out.println();
79         
80 /*        //第二种取出方式:entrySet
81         Set<Map.Entry<Student,String>> entrySet = hm.entrySet();
82         Iterator<Map.Entry<Student,String>> iter = entrySet.iterator();
83 
84         while (iter.hasNext()) {
85             Map.Entry<Student,String> me = iter.next();
86             Student s = me.getKey();
87             String addr = me.getValue();
88             System.out.println(s+"....."+addr);
89         }*/
90     }
91 }

运行结果为:

lisi3:::23...nanjing
lisi1:::21...tianjin
lisi2:::22...shanghai
lisi4:::24...wuhan

7.例子七(使用TreeMap添加有序的元素(映射关系)):

 1 import java.util.Comparator;
 2 import java.util.Iterator;
 3 import java.util.Map;
 4 import java.util.Set;
 5 import java.util.TreeMap;
 6 
 7 /*
 8 需求:每一个学生都与对应的归属地。
 9 学生Student,地址String。
10 学生属性:姓名,年龄。
11 对学生对象的年龄进行升序排序。
12 
13 因为数据是以键值对形式存在的。
14 所以要是哟昂可以排序的Map集合,TreeMap。
15 */
16 class StuNameComparator implements Comparator<Student> {
17     public int compare(Student s1,Student s2) {
18         int num = s1.getName().compareTo(s2.getName());
19         if (num==0)
20             return new Integer(s1.getAge()).compareTo(new Integer(s2.getAge()));
21         return num;
22     }
23 }
24 
25 public class TreeMapTest {
26     public static void main(String[] args) {
27         //因为Student本身定义有比较性,可以直接满足需求。如果是按姓名排序,则需要加上StuNameComparator比较器。
28         TreeMap<Student,String> tm = new TreeMap<Student,String>(/*new StuNameComparator()*/);
29 
30         tm.put(new Student("blisi3",23),"nanjing");
31         tm.put(new Student("lisi1",21),"beijing");
32         tm.put(new Student("alisi14",24),"wuhan");
33         tm.put(new Student("lisi1",21),"tianjin");
34         tm.put(new Student("alisi11",21),"tianjin");
35         tm.put(new Student("alisi12",22),"shanghai");
36 
37         Set<Map.Entry<Student,String>> entrySet = tm.entrySet();
38         Iterator<Map.Entry<Student,String>> it = entrySet.iterator();
39         while (it.hasNext()) {
40             Map.Entry<Student,String> me = it.next();
41             Student s = me.getKey();
42             String addr = me.getValue();
43             System.out.println(s+"..."+addr);
44         }
45     }
46 }
47 
48 
49 class Student implements Comparable<Student> {
50     private String name;
51     private int age;
52     Student(String name,int age) {
53         this.name = name;
54         this.age = age;
55     }
56 
57     public int compareTo(Student s) {
58         int num = new Integer(this.age).compareTo(new Integer(s.age));
59         if (num == 0) {
60             return this.name.compareTo(s.name);
61         }
62         return num;
63     }
64 
65     public int hashCode() {
66         return this.name.hashCode()+this.age*34;
67     }
68 
69     public boolean equals(Object obj) {
70         if(!(obj instanceof Student))
71             throw new ClassCastException("类型错误");
72         Student s = (Student)obj;
73         return s.name.equals(this.name) && this.age == s.age;
74     }
75 
76     public String getName() {
77         return name;
78     }
79 
80     public int getAge() {
81         return age;
82     }
83 
84     public String toString() {
85         return name+":::"+age;
86     }
87 }

运行结果为:

alisi11:::21...tianjin
lisi1:::21...tianjin
alisi12:::22...shanghai
blisi3:::23...nanjing
alisi14:::24...wuhan

 

四、总结

  如果涉及到堆栈,队列等操作,应该考虑用List,对于需要快速插入,删除元素,应该使用LinkedList,如果需要快速随机访问元素,应该使用ArrayList。
  如果程序在单线程环境中,或者访问仅仅在一个线程中进行,考虑非同步的类,其效率较高,如果多个线程可能同时操作一个类,应该使用同步的类或者在类意外加上同步。
  要特别注意对哈希表的操作,作为key的对象要正确复写equals和hashCode方法。
  尽量返回接口而非实际的类型,如返回List而非ArrayList,这样如果以后需要将ArrayList换成LinkedList时,客户端代码不用改变。这就是针对抽象编程。

 

posted on 2015-08-04 00:08  丨敲破苍穹灬  阅读(401)  评论(0)    收藏  举报