黑马程序员————Java集合详解剖析

来源:互联网 发布:html视频播放器源码 编辑:程序博客网 时间:2024/05/01 08:27

            -------Android培训 、Java培训 、期待与你交流!-------       



                            一.Java集合基本概念

1.1 Java集合的定义

      Java集合就是对同一类型的Java数据添加到同一容器中,可以对容器中的对象进行一些操作,例如:添加操作,删除操作,排序操作,迭代操作等一系列操作。

1.2 集合的用途

      Java集合目前有三种类型,一种是基于数组,一种是基于哈希表,一种是链表。Java集合底层结构进行包装,使用者不必知道底层的细节,使用者只需要知道集合相关的API,这样大大的减少了开发人员的开发难度,使代码的结构更加趋于面向对象。

1.3 集合的分类

   主要分为:一种是继承Collection接口,另一种就是继承Map接口。Collection接口:List接口,Set接口。另外还有一些工具类:Arrays,Collections。迭代接口:Iterator

1.4 集合泛型

     集合加入泛型的作用就是做到储存单一性,更利于集合的维护,操作;例如:List<String>list=new ArrayLIst<String>();这样加入泛型的话,该集合就只有加入字符串类型的元素,其他的类型元素则不能加入,做到了存储单一性,确保了集合操作安全。

                              

                               二.Java集合体系结构

2.1 集合的结构图

                                    Collection: 该接口是实现List接口的类和实现Set接口的类的祖宗接口,提供了一系列的通用接口。     

    AbsractCollection:该抽象类是JDK实现的一个巧妙运用,Set接口的实现类和List接口的实现类都继承了该类,该类做到了共享,分担了一大部分来自Collection的方法的实现,这样的话,Set接口的实现类就减轻了来自Collection的实现的压力,它就可以主要来实现继承Set接口的方法的实现,List接口的实现类也是如此。

     Map:该接口可以转变为Collection型的接口。

     AbsractMap:该抽象类的左右与上面的AbsractCollection一样。     

     Collections:集合类的工具类,主要对集合类进行包装。

     Arrays:该类的一个左右就是可以是数组类型的参数转变为List接口型集合类。

     Iterator:主要实现集合类元素的迭代操作。

 

                                三.List接口的子类

3.1 ArrayList

  实现原理:该类的底层主要是个对象型的数组[transient Object[] elementData]。

  简介:该类是非线程安全的,可以包含重复的对象,和null对象,随着元素的增加可以改变内部数组的大小。

  构造器方法:元素的个数和容器的容量是两个不同的概念。元素的个数<=容器容量。

  





     常用方法:

       add(int index,E e):该方法可以增加容器的元素个数,和容器的容量。在添加元素操作时,如果满足(元素的个数<=容器容量)那么就将元素添加进容器中,否则,元素就要扩容后,再添加进容器。扩容比例(1.5倍原容+1)。实际上就是对数组操作,也就是当添加的元素的索引超过数组的大小的时候,首先,调用 System.arraycopy(src, srcPos, dest, destPos,length)来重新创建一个(1.5倍原容量+1)数组,再把元素添加进数组。

      addAll(int index, Collection<? extends E> c):首先把C转变为数组类型,然后改变数组的初始长度,然后创建一个新的长度的数组的数组包含原来的元素和新的元素。

      clear():将底层数组的元素设置为null,然后将元素的个数设置为0。

      contains(Object o):将遍历数组元素中第一个为O的元素,如果存在,返回true,否则返回false。

      toArray():该方法的底层调用的是Arrays.copyOf方法,将容器转换为数组,实际也是数组的复制。

     set(int index, E element) :将底层数组的指定索引值覆盖掉。

      remove(int index):将底层数组的指定索引处的元素设置为null,然后调用System.arraycopy(src,srcPos, dest,destPos,length)来重新创建一个新的大小的数组。

     iterator():返回一个迭代器。

     listIterator():返回一个迭代器。

  应用举例:

public class Test {   public static void main(String[] args) {     ArrayList<String> list=new ArrayList<String>();  String[] city={"纽约","北京","上海","香港","台北","东京","首尔"};  list.addAll(Arrays.asList(city));  java.util.ListIterator<String>it=list.listIterator();  while(it.hasNext())  {  it.forEachRemaining(V->System.out.println(V));  }  }  }

3.2 Vector

  该集合类的功能和ArrayList基本一样,不过该类是线程安全的,该类的方法大多数都经过Synchronized修饰。常见的方法和ArrayList一样,但他的方法线程安全。

3.3 Stack

  简介:Stack类表示后进先出的对象堆栈。它通过五个操作对类Vector进行了扩展,允许将向量视为堆栈。它提供了通常的push和pop操作,以及取堆栈顶点的peek方法、测试堆栈是否为空的empty方法、在堆栈中查找项并确定到堆栈顶距离的方法。

  常见方法:

  push(E e):把项压入堆栈顶serach部。其作用与下面的方法完全相同

  pop():移除堆栈顶部的对象,并作为此函数的值返回该对象

  peek():查看堆栈顶部的对象,但不从堆栈中移除它

  search():返回对象在堆栈中的位置,以1为基数。如果对象。是堆栈中的一个项,此方法返回距堆栈顶部最近的出现位置到堆栈顶部的距离;堆栈中最顶部项的距离为1。使用equals方法比较o  

  empty():测试堆栈是否为空

3.4 LinkedList

  实现原理:该类的底层就是一个链表。通常用来做队列和链栈。

  简介:实现所有可选的列表操作,并且允许所有元素(包括null)。除了实现List接口外,LinkedList类还为在列表的开头及结尾get、remove和insert元素提供了统一的命名方法。这些操作允许将链接列表用作堆栈、队列或双端队列。此类实现Deque接口,为add、poll提供先进先出队列操作,以及其他堆栈和双端队列操作。所有操作都是按照双重链接列表的需要执行的。在列表中编索引的操作将从开头或结尾遍历列表(从靠近指定索引的一端)。

  常见方法:

       add(E e) 添加一个元素在底层链表的结尾。

   element()返回链表的头部。

   get(int index)返回此列表中指定位置处的元素
   listIterator(int index)返回此列表中的元素的列表迭代器(按适当顺序),从列表中指定位置开始。
   offer(E e)将指定元素添加到此列表的末尾(最后一个元素)。
   peek() 获取但不移除此列表的头(第一个元素)。
   poll()获取并移除此列表的头(第一个元素)
   pop()从此列表所表示的堆栈处弹出一个元素。
   push(E e)将元素推入此列表所表示的堆栈。
   remove()获取并移除此列表的头(第一个元素)
   set(int index, E element)将此列表中指定位置的元素替换为指定的元素。

应用举例: 

public class Test {   public static void main(String[] args) {    LinkedList<String> list=new LinkedList<String>();  String[] name={"孝感","武汉","宜昌","咸宁","黄冈"};  list.addAll(Arrays.asList(name));  list.addFirst("湖北");  Iterator<String> it=list.listIterator();  while(it.hasNext())  { System.out.println(it.next());  }  System.out.println("------------------");  list.addLast("云梦"); list.forEach(V->System.out.println(V));  System.out.println("------------------");  list.offer("随州"); list.forEach(V->System.out.println(V));  System.out.println("------------------");  list.offerLast("荆州");  list.forEach(V->System.out.println(V));  System.out.println("------------------");  list.pollLast();  list.forEach(V->System.out.println(V));  System.out.println("------------------");  }  }

                            四.Set接口的子类

4.1 HashSet

  实现原理:底层是哈希表。

  简介:此类实现Set接口,由哈希表(实际上是一个HashMap实例)支持。它不保证 set 的迭代顺序;特别是它不保证该顺序恒久不变。此类允许使用null元素。此类为基本操作提供了稳定性能,这些基本操作包括addremove和Size,假定哈希函数将这些元素正确地分布在桶中。对此 set 进行迭代所需的时间与HashSet实例的大小(元素的数量)和底层HashMap实例(桶的数量)的“容量”的和成比例。因此,如果迭代性能很重要,则不要将初始容量设置得太高(或将加载因子设置得太低)。 

  常用方法:

  boolean add(E e) :如果此 set 中尚未包含指定元素,则添加指定元素。 
  void clear() :从此 set 中移除所有元素。 
  Object clone():返回此 HashSet 实例的浅表副本:并没有复制这些元素本身。 
  boolean contains(Object o):如果此 set 包含指定元素,则返回 true。 
  boolean isEmpty():如果此 set 不包含任何元素,则返回 true。 
  Iterator<E> iterator():返回对此 set 中元素进行迭代的迭代器。 
  boolean remove(Object o):如果指定元素存在于此 set 中,则将其移除。 
  int size():返回此 set 中的元素的数量(set 的容量)。

 应用举例: 

       HashSet<String>set1  = new HashSet<String>();         set1.add("a");        set1.add("b");        set1.add("c");        set1.add("d");        set1.add("e");        set1.forEach(V->System.out.println(V)); 

4.2 SortSet

  简介:进一步提供关于元素的总体排序的Set。这些元素使用其自然顺序进行排序,或者根据通常在创建有序set 时提供的Comparator进行排序。该set的迭代器将按元素升序遍历set。提供了一些附加的操作来利用这种排序(此接口是SortedMap的set对应接口)插入有序set的所有元素都必须实现Comparable接口(或者被指定的比较器所接受)另外所有这些元素都必须是可互相比较的:set中的任意两个元素e1和e2,执e1.compareTo(e2)或comparator.compare(e1, e2)都不得抛出ClassCastException。试图违反此限制将导致违反规则的方法或者构造方法调用抛出ClassCastException。注意,如果有序set要正确实现 Set接口,则有序set所维持的顺序(无论是否提供了明确的比较器)都必须与equals一致。这是因为Set接口是按照equals操作定义的,但有序set使用它的compareTo(或 compare)方法对所有元素进行比较,因此从有序set的角度来看,此方法认为相等的两个元素就是相等的。即使顺序与equals不一致,有序set的行为仍然是定义良好的,只不过没有遵守Set接口的常规协定。所有通用有序set实现类都应该提供4个标准构造方法:1)void(无参数)构造方法,它创建一个空的有序set,按照元素的自然顺序进行排序。2)带有一个Comparator类型参数的构造方法,它创建一个空的有序set,根据指定的比较器进行排序。3)带有一个Collection类型参数的构造方法,它创建一个新的有序set,其元素与参数相同,按照元素的自然顺序进行排序。4)带有一个SortedSet类型参数的构造方法,它创建一个新的有序set,其元素和排序方法与输入的有序set相同。无法保证强制实施此建议,因为接口不能包含构造方法。注:一些方法返回具有受限范围的子集。这些范围区间是半开的,也就是说,它们包括低端点,但不包括高端点(如果适用)。如果需要一个闭区间(同时包含两个端点),且元素类型允许计算给定值的后继值,则只需要请求lowEndpointsuccessor(highEndpoint)的子区间。 

  常见方法:

   Comparator comparator():返回Set锁使用的Comparator对象,或者用null表示它使用Object自有的排序方法。
   Object first():返回最小的元素。
   Object last():返回最大的元素。
   SortedSet subSet(fromElement, toElement):返回Set的子集,其中的元素从fromElement开始到toElement为止
   SortedSet headset(toElement):返回Set的子集,其中的元素都应小于toElement。
   SortedSet headSet(toElement):返回Set的子集,其中的元素都应大于fromElement。
   注意,SortedSet意思是“根据对象的比较顺序”,而不是“插入顺序”进行排序。

4.3 TreeSet

  实现原理:底层是二叉树

  简介:基于TreeMap的 NavigableSet 实现。使用元素的自然顺序对元素进行排序,或者根据创建set时提供的 Comparator 进行排序,具体取决于使用的构造方法。此实现为基本操作(add、remove 和 contains)提供受保证的 log(n) 时间开销。注意,如果要正确实现Set接口,则set维护的顺序(无论是否提供了显式比较器)必须与 equals 一致。这是因为Set接口是按照equals操作定义的,但TreeSet实例使用它的compareTo(或 compare)方法对所有元素进行比较,因此从set的观点来看,此方法认为相等的两个元素就是相等的。即使set的顺序与equals不一致,其行为也是 定义良好的;它只是违背了Set接口的常规协定。 

  应用举例:

public class Test {   public static void main(String[] args) {                   //不指定排序器          TreeSet<String>set1  = new TreeSet<String>();          set1.add("a");        set1.add("b");        set1.add("c");        set1.add("d");        set1.add("e");        System.out.println("set1="+set1);            //指定排序器          Comparator<String> sort=new  Comparator<String>(){              public int compare(String o1, String o2)            {return o2.compareTo(o1);}};        TreeSet<String> set2=new TreeSet<String>(sort);          set2.add("a");        set2.add("b");        set2.add("c");        set2.add("d");        set2.add("e");        System.out.println("Set2="+set2);      }  }  

                                      五.Map接口的子类

5.1 HashMap

  实现原理:底层是哈希表

  简介:基于哈希表的Map接口的实现。此实现提供所有可选的映射操作,并允许使用 null值和null键(除了非同步和允许使用 null之外,HashMap类Hashtable大致相同)此类不保证映射的顺序,特别是它不保证该顺序恒久不变。此实现假定哈希函数将元素适当地分布在各桶之间,可为基本操作(get和put)提供稳定的性能。迭代 collection 视图所需的时间与 HashMap 实例的“容量”(桶的数量)及其大小(键-值映射关系数)成比例。所以,如果迭代性能很重要,则不要将初始容量设置得太高(或将加载因子设置得太低)。HashMap 的实例有两个参数影响其性能:初始容量 和加载因子。容量 是哈希表中桶的数量,初始容量只是哈希表在创建时的容量。加载因子是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行 rehash 操作(即重建内部数据结构),从而哈希表将具有大约两倍的桶数。通常,默认加载因子(.75)在时间和空间成本上寻求一种折衷。加载因子过高虽然减少了空间开销,但同时也增加了查询成本(在大多数 HashMap 类的操作中,包括 get 和 put 操作,都反映了这一点)在设置初始容量时应该考虑到映射中所需的条目数及其加载因子,以便最大限度地减少rehash 操作次数。如果初始容量大于最大条目数除以加载因子,则不会发生 rehash 操作。如果很多映射关系要存储在 HashMap 实例中,则相对于按需执行自动的rehash操作以增大表的容量来说,使用足够大的初始容量创建它将使得映射关系能更有效地存储。 

 常用方法:

  void clear() 从此映射中移除所有映射关系。 
  Object clone() 返回此 HashMap 实例的浅表副本:并不复制键和值本身。 
  boolean containsKey(Object key) 如果此映射包含对于指定键的映射关系,则返回 true。 
  boolean containsValue(Object value) 如果此映射将一个或多个键映射到指定值,则返回 true。 
  Set<Map.Entry<K,V>> entrySet() 返回此映射所包含的映射关系的 Set 视图。 
  V get(Object key) 返回指定键所映射的值;如果对于该键来说,此映射不包含任何映射关系,则返回 null。 
  boolean isEmpty()如果此映射不包含键-值映射关系,则返回 true。 
  Set<K> keySet() 返回此映射中所包含的键的 Set 视图。 
  V put(K key, V value) 在此映射中关联指定值与指定键。 
  V remove(Object key) 从此映射中移除指定键的映射关系(如果存在)。 
  int size() 返回此映射中的键-值映射关系数。 
  Collection<V> values() 返回此映射所包含的值的 Collection 视图。 

  应用举例:

public class Test {          public static void main(String[] args) {                    HashMap<Integer, String> map= new HashMap<Integer, String>();          map.put(1, "北京");          map.put(2, "上海");          map.put(3, "重庆");          map.put(4, "天津");        map.forEach((K,V)->System.out.println(V));           }  }  

5.2 TreeMap

  实现原理:底层是红黑树

  简介:基于红黑树的NavigableMap实现。该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的Comparator 进行排序,具体取决于使用的构造方法。此实现为containsKey、get、put和remove操作提供受保证的log(n)时间开销。这些算法是Cormen、Leiserson和Rivest的 Introduction to Algorithms 中的算法的改编。注意,如果要正确实现Map接口,则有序映射所保持的顺序(无论是否明确提供了比较器)都必须与 equals 一致。这是因为Map接口是按照equals操作定义的,但有序映射使用它的compareTo(或compare)方法对所有键进行比较,因此从有序映射的观点来看,此方法认为相等的两个键就是相等的。即使排序与equals不一致,有序映射的行为仍然是 定义良好的,只不过没有遵守Map接口的常规协定。 

  常用方法:

       Map.Entry<K,V> ceilingEntry(K key) 返回一个键-值映射关系,它与大于等于给定键的最小键关联;如果不存在这样的键,则返回 null。 
  void clear() 从此映射中移除所有映射关系。          
  Comparator<? super K> comparator() 返回对此映射中的键进行排序的比较器;如果此映射使用键的自然顺序,则返回 null。        
  boolean containsKey(Object key)  如果此映射包含指定键的映射关系,则返回 true。        
  boolean containsValue(Object value)     如果此映射为指定值映射一个或多个键,则返回 true。     
  NavigableSet<K> descendingKeySet()  返回此映射中所包含键的逆序 NavigableSet 视图。        
  NavigableMap<K,V> descendingMap()    返回此映射中所包含映射关系的逆序视图。      
  Set<Map.Entry<K,V>> entrySet()返回此映射中包含的映射关系的 Set 视图。           
  Map.Entry<K,V> firstEntry()返回一个与此映射中的最小键关联的键-值映射关系;如果映射为空,则返回 null。 
  K firstKey() 返回此映射中当前第一个(最低)键。                
  V get(Object key)  返回指定键所映射的值,如果对于该键而言,此映射不包含任何映射关系,则返回 null。   
  SortedMap<K,V> headMap(K toKey)  返回此映射的部分视图,其键值严格小于toKey。                         
  Set<K> keySet()   返回此映射包含的键的Set视图。                           
  void putAll(Map<? extends K,? extends V> map)    将指定映射中的所有映射关系复制到此映射中。       
  V remove(Object key)        如果此 TreeMap 中存在该键的映射关系,则将其删除。      
  SortedMap<K,V> subMap(K fromKey,K toKey)返回此映射的部分视图,其键值的范围从fromKey到toKey.        
  SortedMap<K,V> tailMap(K fromKey) 返回此映射的部分视图,其键大于等于fromKey。       
  Collection<V> values() 返回此映射包含的值的Collection视图。
 

  应用举例:

public class Test {          public static void main(String[] args) {                    //不指定排序器          TreeMap<String, String> treeMap1 = new TreeMap<String, String>();          treeMap1.put("2", "1");          treeMap1.put("b", "1");          treeMap1.put("1", "1");          treeMap1.put("a", "1");          System.out.println("treeMap1="+treeMap1);            //指定排序器          Comparator<String> sort=new  Comparator<String>(){              public int compare(String o1, String o2)            {return o2.compareTo(o1);}};        TreeMap<String, String> treeMap2 =new TreeMap<String,String>(sort);          treeMap2.put("2", "1");          treeMap2.put("b", "1");          treeMap2.put("1", "1");          treeMap2.put("a", "1");          System.out.println("treeMap2="+treeMap2);      }  }  

5.3 HashTable

  实现原理:底层是哈希表

  简介:此类实现一个哈希表,该哈希表将键映射到相应的值。任何非null对象都可以用作键或值。为了成功地在哈希表中存储和获取对象,用作键的对象必须实现hashCode方法和equals方法。Hashtable的实例有两个参数影响其性能:初始容量 和加载因子。容量 是哈希表中桶的数量,初始容量就是哈希表创建时的容量。注意,哈希表的状态为open:在发生“哈希冲突”的情况下,单个桶会存储多个条目,这些条目必须按顺序搜索。加载因子是对哈希表在其容量自动增加之前可以达到多满的一个尺度。初始容量和加载因子这两个参数只是对该实现的提示。关于何时以及是否调用rehash方法的具体细节则依赖于该实现。通常,默认加载因子(.75)在时间和空间成本上寻求一种折衷。加载因子过高虽然减少了空间开销,但同时也增加了查找某个条目的时间(在大多数Hashtable操作中,包括get和put操作,都反映了这一点)。初始容量主要控制空间消耗与执行rehash操作所需要的时间损耗之间的平衡。如果初始容量大于Hashtable所包含的最大条目数除以加载因子,则永远不会发生rehash 操作。但是,将初始容量设置太高可能会浪费空间如果很多条目要存储在一个Hashtable中,那么与根据需要执行自动rehashing操作来增大表的容量的做法相比,使用足够大的初始容量创建哈希表或许可以更有效地插入条目。

  常用方法:

  boolean contains(Object value)  测试此映射表中是否存在与指定值关联的键。 
  boolean containsKey(Object key) 测试指定对象是否为此哈希表中的键。 
  boolean containsValue(Object value) 如果此Hashtable将一个或多个键映射到此值,则返回true。 
  Enumeration<V> elements() 返回此哈希表中的值的枚举。 
  Set<Map.Entry<K,V>> entrySet()  返回此映射中包含的键的Set视图。 
  boolean equals(Object o) 按照 Map接口的定义,比较指定Object与此Map是否相等。  
  int hashCode() 按照Map接口的定义,返回此Map的哈希码值。 
  Enumeration<K> keys()返回此哈希表中的键的枚举。 
  Set<K> keySet() 返回此映射中包含的键的Set视图。 
  V put(K key, V value) 将指定key映射到此哈希表中的指定value。 
  protected  void rehash() 增加此哈希表的容量并在内部对其进行重组,以便更有效地容纳和访问其元素。 
  V remove(Object key) 从哈希表中移除该键及其相应的值。 
  Collection<V> values() 返回此映射中包含的键的 Collection 视图。 

  应用举例:

public class Test {public class Test {  public static void main(String[] args) { Hashtable<String, Integer> num = new Hashtable<String, Integer>();    num.put("one", 1);   num.put("two", 2);   num.put("three", 3);   System.out.println(num.containsKey("one"));     System.out.println(num.containsValue(1));    Set<String> set=num.keySet();    System.out.println(set.contains("one"));    num.forEach((K,V)->System.out.println(V));//输出容器的元素    Collection<Integer> c=num.values();    System.out.println(c.contains(1));

                          六.迭代器的详解

6.1 迭代器原理:实际上就是个数组,或链表。

6.2 要点:当返回一个迭代器的时候,除非迭代器自生的行为改变了容器结构,否则其他的行为,将导致迭代器的遍历失败,这其实是一个保护操作,防止多线程不同步造成对容器的不同步修改,造成程序的错误。迭代器的remove()方法在next()方法之后调用,否则抛出错误。容器中有一个保存修改的次数变量:modCount,每当修改一次,该参数增加1,初次调用迭代器方法时,将该参数赋值给迭代器的exceptCount,只有迭代器自己的操作才会对该参数有影响,当modCount!=exceptCount时,会产生异常抛出。其实迭代器操作的数组(链表)和容器的底层数组是同一个,任何对迭代器的操作,都会将对容器的底层数组产生影响。

6.3 举例:

public class Test {public static void main(String[] args) { List<String> list=new ArrayList<String>(); String[] name={"张三","李四","王五"}; Collection<String> c=Arrays.asList(name); list.addAll(c); Iterator<String> it=list.iterator(); while(it.hasNext()) { System.out.println(it.next()); }}}
非迭代器修改:

public class Test {public static void main(String[] args) { List<String> list=new ArrayList<String>(); String[] name={"张三","李四","王五"}; Collection<String> c=Arrays.asList(name); list.addAll(c); Iterator<String> it=list.iterator(); list.remove(2); while(it.hasNext()) { System.out.println(it.next()); }}}/*运行结果:Exception in thread "main" java.util.ConcurrentModificationExceptionat java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)at java.util.ArrayList$Itr.next(ArrayList.java:851)at collection.Test.main(Test.java:20)*/
在next()之前调用remove()

public class Test {public static void main(String[] args) { List<String> list=new ArrayList<String>(); String[] name={"张三","李四","王五"}; Collection<String> c=Arrays.asList(name); list.addAll(c); Iterator<String> it=list.iterator(); it.remove(); while(it.hasNext()) { System.out.println(it.next()); }}}/*运行结果:Exception in thread "main" java.lang.IllegalStateExceptionat java.util.ArrayList$Itr.remove(ArrayList.java:864)at collection.Test.main(Test.java:17)*/
迭代器操作的数组和容器底层数组同一个

public class Test {public static void main(String[] args) { List<String> list=new ArrayList<String>(); String[] name={"张三","李四","王五"}; Collection<String> c=Arrays.asList(name); list.addAll(c); Iterator<String> it=list.iterator(); while(it.hasNext()) { it.next(); it.remove(); } System.out.println("list容器的大小:"+list.size());}}/*运行结果:list容器的大小:0 */

0 0