集合

1.集合接口
1.1集合的接口与实现分离
Java SE 5.0开始,集合类是带有类型参数的泛型类。
队列通常有两种实现方式:使用循环数组(SE 6中ArrayDeque类);使用链表(LinkedList类)。
这里写图片描述
一旦构建了集合,就不需要知道究竟使用了哪种实现。

  //可使用接口类型存放集合的引用:
  Queue<Customer> expressLane = new CircularArrayQueue<Customer>(100);

循环数组要比链表更高效,多数人优先选择循环数组。但循环数组容量有限,若要收集的对象数量没有上限,最好使用链表来实现。
1.2Java类库中的集合接口和迭代器接口
Java类库中,集合类的基本接口是Collection接口。
该接口基本方法:

   public interface Collection<E>{
       boolean add(E element);
       Iterator<E> iterator();//返回实现Iterator接口的对象,可依次访问元素
       ...
   }

1.2.1迭代器
Iterator接口包含3个基本方法:

    public interface Iterator<E>{
       E next();
       boolean hasNext();
       void remove();//删除上次访问的对象
    }
    //调用next时,迭代器越过下一个元素,并返回刚刚越过的那个元素的引用。

“for each”循环可与任何实现了Iterable接口的对象一起工作,该接口仅有一个方法:

     public interface Iterable<E>{
         Iterator<E> iterator();
     }

Collection接口扩展了Iterable接口。
1.2.2删除元素
Iterator接口的remove方法将会删除上次调用next方法时返回的元素。

   //删除字符串集合中第一个元素:
   Iterator<String> it = c.iterator();
   it.next();//越过第一个元素
   it.remove();//删除第一个元素
   !!如果调用remove之前没有调用next是不合法的,会抛出IllegalStateException异常

1.2.3泛型实用方法
2.具体的集合
这里写图片描述
2.1链表
Java中所有链表都是双向链接的。
这里写图片描述

   List<String> staff = new LinkedList<String>();
   staff.add("Amy");
   staff.add("Bob");
   staff.add("Carl");
   Iterator iter = staff.iterator();
   String first = iter.next();//访问第一个元素"Amy"
   String second = iter.next();//访问第二个元素"Bob"
   iter.remove();//删除上次访问的元素"Bob"

链表与泛型集合间有一个重要区别,链表是一个有序集合。
只有对自然有序的集合使用迭代器添加元素(依赖于位置的add方法)才有实际意义。
无序元素集合(例如:set集).
使用子接口ListIterator,包含add方法:

   interface ListIterator<E> extends Iterator<E>{
       void add(E element);//不返回boolean类型值,在迭代器位置之前添加一个元素
       E previous();//反向遍历链表,类似next
       boolean hasPrevious();
       ...
   }

LinkedList类的listIterator方法返回实现了ListIterator接口的迭代器对象:

  ListIterator<String> iter = staff.listIterator();

当用一个刚刚由Iterator方法返回,并指向链表表头的迭代器调用add操作时,新添加的元素将变成新表头。当迭代器越过链表的最后一个元素时(hasNext()返回false),添加的元素将变成列表的新表尾。如果链表有n个元素,则有n+1个位置可以添加新元素。
链表迭代器若发现它的集合被另一个迭代器修改了,或被该集合自身的方法修改了,就会抛出一个ConcurrentModificationException异常。

   List<String> list = ...;
   ListIterator<String> iter1 = list.listIterator();
   ListIterator<String> iter2 = list.listIterator();
   iter1.next();
   iter1.remove();
   iter2.next();//throws ConcurrentModificationException
   !!为了避免发生并发修改的异常,可遵循以下简单规则:根据需要给容器附加许多只能读取列表的迭代器,
   另外,再单独附加一个既能读又能写的迭代器。
   //链表只负责跟踪对列表的结构性修改(例:添加、删除元素),set操作不被视为结构性修改。
  for(int i=0;i<list.size();i++)
        list.get(i);
  //每次查找一个元素都要从列表头部重新开始,没有缓存,效率极低!!
  //get方法优化:索引大于size()/2时,从列表尾端开始搜索元素

2.2数组列表
List接口用于描述一个有序集合,并且集合中每个元素的位置十分重要。
2种访问元素协议:
(1)迭代器
(2)get()和set()方法随机访问元素(不适用于链表,对数组很有用)
ArrayList类实现了List接口,封装了一个动态再分配的对象数组。
//不需要同步时使用ArrayList,而不要使用Vector.
2.3散列集
若不在意元素的顺序,可以有几种能够快速查找元素的数据结构。
缺点:无法控制元素出现的次序。他们按照有利于操作目的的原则组织数据。
散列表(hash table)数据结构可以快速的查找所需要的对象。散列表为每个对象计算一个整数,称为散列码。
自定义类要实现这个类的hashCode方法,且应与equals方法兼容(a.equals(b)为true,a与b有相同散列码)。

  HashSet():构造一个空散列表
  HashSet(Collection<? extends E> elements):构造一个散列集,并将集合中的所有元素添加到散列集中;
  HashSet(int initialCapacity):构造一个空的具有指定容量(桶数)的散列集。
  HashSet(int initialCapacity,float loadFactor):构造一个指定容量和装填因子(0.0-1.0间的数值,
  当大于这个百分比时,散列表进行再散列)的空散列集。

2.4树集
TreeSet类比散列集有所改进。树集是一个有序集合,对集合遍历时,每个值将自动按照排序后的顺序呈现。
排序是用树结构完成的(当前是红黑树)。迭代器总是以排好序的顺序访问每个元素。

   SortedSet<String> sorter = new TreeSet<String>();
   sorter.add("Bob");
   sorter.add("Amy");
   sorter.add("Carl");
   for(String s:sorter)
       System.out.println(s);
   //打印顺序:Amy->Bob->Carl

将一个元素添加到树中要比添加到散列表中慢,但与将元素添加到数组或链表的正确位置相比还是快很多。
若树中包含n个元素,查找新元素位置比较次数:log2(n);
2.5对象的比较

   SortedSet<Item> sortByDescription = new TreeSet<Item>(
      new Comparator<Item>(){ //不是Comparable接口
        public int compare(Item a,Item b){
          String descrA = a.getDescription();
          String descrB = b.getDescription();
          return descrA.compareTo(descrB);
        }
      }
   );

树的排序是整体排序,任意两个元素必须是可比的。
Java SE 6起,TreeSet类实现了NavigableSet接口。接口增加了几个便于定位元素及反向遍历的方法。
2.6队列与双端队列
队列可在尾部添加元素,头部删除元素;
双端队列可在头部和尾部同时添加或删除元素。
Java SE 6中引入Deque接口,由ArrayDeque和LinkedList类实现。
2.7优先级队列
优先级队列使用了一个优雅且高效的数据结构,称为“堆”。堆是一个可以自我调整的二叉树。
与TreeSet一样,一个优先级队列既可以保存实现了Comparable接口的类对象,也可保存在构造器中提供比较器的对象。
使用优先级队列的典型示例是任务调度。每一个任务有一个优先级,任务以随机顺序添加到队列中。每启动一个新任务,都将优先级最高的任务从队列删除。
2.8映射表
映射表用来存放键/值对。
Java类库为映射表提供了两个通用的实现:HashMap和TreeMap。两个类都实现了Map接口。
散列映射表(HashMap):对进行散列;
树映射表(TreeMap):用的整体顺序对元素进行排序,并将其组织成搜索树。
键必须是唯一的。若对同一个键两次调用put方法,第二个值就会取代第一个值。实际上,put将返回用这个键参数存储的上一个值。
集合框架并没有将映射表本身视为一个集合。然而,可以获得映射表的视图。

  3个视图:
  键集:Set<K> keySet();
  值集合:Collection<K> values();
  键/值对集:Set<Map.Entry<K,V>> entrySet();//条目集的元素是静态内部类Map.Entry的对象
   //枚举映射表中所有键
   Set<String> sets = map.keySet();
   for(String key:keys){
       ...
   }
   //枚举各个条目,同时查看键与值
   for(Map.Entry<String,Employee> entry:staff.entrySet()){
        String key = entry.getKey();
        Employee value = entry.getValue();
        ...
   }

2.9专用集与映射表类
2.9.1弱散列映射表
2.9.2链接散列集和链接映射表
2.9.3枚举集与枚举映射表
2.9.4标识散列映射表
3.集合框架
框架是一个类的集,包含很多超类。Java集合类库构成了集合类的框架。
Java SE 1.4引入了一个标记接口RandomAccess.接口没有任何方法,可用来检测集合是否支持高效的随机访问:

  if(c instanceof RandomAccess){
     ...
  }
  else{
     ...
  }
  //ArrayList类和Vector类都实现了RandomAccess接口。

有关Set接口:集的equals方法定义两个集相等的条件是它们包含相同的元素但顺序不必相同。
3.1视图与包装器
通过使用视图,可以获得其他的实现了集合接口和映射表接口的对象。
3.1.1轻量级集包装器
Arrays类的静态方法asList将返回一个包装了普通Java数组的List包装器。
这个方法可以将数组传递给期望得到列表或集合变元的方法,如:

   Card[] cardDeck = new Card[52];
   List<Card> cardList = Arrays.asList(cardDeck);
   //返回的对象不是ArrayList,而是视图对象
  //返回实现List接口的不可修改的对象
  List<String> settings = Collections.nCopies(100,"DEFAULT");//Collections类
  //创建一个包含100个字符串的List,每个串都被设置为"DEFAULT"

3.1.2子范围
可为很多集合建立子范围视图

   //取出第10-19个元素:
   List group2 = staff.subList(10,20);
   group2.clear();//删除整个子范围
   //对于有序集和映射表,可以使用排序顺序而不是元素位置建立子范围
SortedSet接口声明方法:  
      SortedSet<E> subSet(E from,E to);
      SortedSet<E> headSet(E to);
      SortedSet<E> tailSet(E from);
Java SE 6引入的NavigableSet接口声明方法:可指定是否包括边界
      NavigableSet<E> subSet(E from,boolean fromInclusive,E to,boolean toInclusive);
      NavigableSet<E> headSet(E to,boolean toInclusive);
      NavigableSet<E> tailSet(E from,boolean fromInclusive);
SortedMap接口方法:
      SortedMap<K,V> subMap(K from,K to);
      SortedMap<K,V> headMap(K to);
      SortedMap<K,V> tailMap(K from);
Java SE 6引入的NavigableMap接口声明方法:可指定是否包括边界     
      NavigableMap<K,V> subMap(K from,boolean fromInclusive,K to,boolean toInclusive);
      NavigableMap<K,V> headMap(K to,boolean toInclusive);
      NavigableMap<K,V> tailMap(K from,boolean fromInclusive); 

3.1.3不可修改的视图
不可修改的视图并不是集合本身不可修改。
3.1.4同步视图
若由多个线程访问集合,就必须确保集不会被意外的破坏。
类库设计者使用视图机制来确保常规集合的线程安全,而不是实现线程安全的集合类。

  //静态synchronizedMap方法将任何一个映射表转换成具有同步访问方法的Map:
  Map<String,Employee> map = Collections.synchronizedMap(new HashMap<String,Employee>());

3.1.5被检验视图
用来对泛型类型发生问题时提供调试支持。

   ArrayList<String> strings = new ArrayList<String>;
   ArrayList rawList = strings;//只产生警告
   rawList.add(new Date());//成功添加一个Date对象,运行时检测不到错误
   //安全列表,add方法检测插入的对象是否属于给定的类
   List<String> safeStrings = Collections.checkedList(strings,String.class);
   ArrayList rawList = safeStrings;
   rawList.add(new Date());//抛出ClassCastException异常

3.2批操作
使用类库中的批操作避免频繁的使用迭代器。

  //找出两个集的交
  Set<String> result = new HashSet<String>(a);
  result.retainAll(b);
  //批操作应用于视图,删除指定员工
  Map<String,Employee> staffMap = ...;
  Set<String> terminatedIDs = ...;//待删除员工的ID集
  staffMap.removeAll(terminatedIDs);

3.3集合与数组间的转换

   String[] values = ...;
   HashSet<String> staff = new HashSet<String>(Arrays.asList(values));//转换为集合
   Object[] values = staff.toArray();//产生对象数组,不能类型转换
   String[] values = staff.toArray(new String[0]);//返回的数组与所创建数组一样
   values = staff.toArray(new String[staff.size()]);//指定数组大小

4.算法
泛型集合接口有一个很大的优点,即:算法只需要实现一次。
4.1排序与混排
Collections类中的sort方法可对实现了List接口的集合进行排序:

  List<String> staff = new LinkedList<String>();
  Collections.sort(staff);//假定列表元素实现了Comparable接口
  Collections.sort(staff,Collections.reverseOrder());//静态方法返回比较器,降序排序

Java中,将列表的所有元素转入一个数组,并使用一种归并排序的变体对数组进行排序,然后将排序后的序列复制回列表。时间复杂度:O(nlog2(n)).
集合类库中使用的归并排序算法比快速排序慢一些,但有一个主要的优点:稳定(不需要交换相同的元素)。
Collections类有一个算法shuffle,可以随机的混排列表中元素的顺序:
时间复杂度:O(n*a(n)).a(n)是访问元素的平均时间

  ArrayList<Card> cards = ...;
  Collections.shuffle(cards);
  //如果提供的列表没有实现RandomAccess接口,shuffle方法将元素复制到数组中,然后打乱数组中元素的顺序,
  最后将打乱顺序后的元素复制回列表。

4.2二分查找
Collections类的binarySearch方法实现了这个算法。集合(实现List接口)必须是排好序的。

  i = Collections.binarySearch(c,element);//c:集合;element:待查找元素
  i = Collections.binarySearch(c,element,comparator);//comparator:比较器
  //返回正值或0:匹配对象的索引
  //返回负值:没有匹配的元素。但可利用其确定插入新元素element的位置(-i-1),保持有序性

只有采用随机访问,二分查找才有意义。如果为binarySearch(检查列表参数是否实现RandomAccess接口,实现则采用二分查找,否则线性查找)方法提供一个链表,它将自动变为线性查找。
4.3简单算法
4.4编写自己的算法
尽可能的使用接口,而不要使用具体的实现。
例:

  void fillMenu(JMenu menu,ArrayList<JMenuItem> items){
      for(JMenuItem item:items){
         menu.addItem(item);
      }
  }
  //调用程序必须在ArrayList中提供选项
  void fillMenu(JMenu menu,Collection<JMenuItem> items){
      for(JMenuItem item:items){
         menu.addItem(item);
      }
  }
  //可以用ArrayList或LinkedList,甚至Arrays.asList包装器包装的数组调用这个方法
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值