集合

来源:互联网 发布:淘宝自动购买软件 编辑:程序博客网 时间:2024/04/29 03:32

13.1.1将集合的接口与实现分离

集合接口如:Queue指定使用队列数据结构,添加删除等方法,或者说明实现可以怎么创建构造器。Queue接口实现方式通常是  循环数组,链接

使用循环数组的实现:ArrayDeque,链表:LinkedList

创建集合对象技巧:Queue<String> a=new LinkedList<String>();当我们不想使用链表结构时,就只要修改成Queue<String> a=new ArrayDeque<String>();


集合框架中有一些抽象方法,如:AbstractQueue,实现了Queue接口的所有方法,这是为了创建自己的队列类设计的,让实现更加方便。


1.2java类库中的集合接口和迭代器接口

Collection接口是集合的基本接口。

当中有几个方法比如 Iterator<E> iterator();这个方法将返回一个迭代器对象。

public interface Iterator<E>{          E next();         boolean hasNext();          void remove();}
next方法可以不断的读取集合元素直到没有元素读取读取失败抛出NoSuchElementException.可以调用hasNext方法确定是否有下一个元素,有将返回true。

下面是例子:

List<String> a=new ArrayList<String>();Iterator b=a.iterator();while(a.hasNext()){           String s=a.next();            //do some thing}
如果你只是想遍历集合,有一种更加方便的方式: for each

List<String> a=new ArrayList<String>();for(String s:a){      System.out.println(s);             //can do more some thing }
for each 循环可以与任何实现了Iterable接口的对象一起工作。

public Interface Iterable<E>{       Iterator<E> iterator();}
集合框架中的类都实现了这个接口,都可以使用for each循环,不过实现Map接口的映射表比较特殊,需要把键-值视图提取出来才能遍历。
迭代集合时的顺序应不同集合有所不同,ArrayList是按照添加顺序排序,迭代时从0角标开始。

删除元素

每个集合都提供了删除元素的方法,而迭代器也有一个删除方法,他们有一些区别。在稍后介绍Listiterator时说明。

迭代器的remove方法会删除上次next方法调用的元素

it.next(); it.move();如果删除前没有调用next将派出一个IllegalStateException。

it.next(); it.move();it.next(); it.move();这是删除两个连续元素的方式。


Collection和Iterator接口都是泛型类,看Api了解其中一些泛型方法。



2.0具体的集合


2.1链表

链表作为一种数据结构,被用作管理集合元素。那具体的集合是怎么样的呢?

链表优点:删除,添加元素速度较快

缺点:定位元素位置慢

具体集合类:LinkedList,LinkedHashSet

先了解下链表是怎么管理元素的。链表能记住元素的添加顺序,把元素储存在节点上,节点还记录了下个元素的指引和上个元素指引(双向链接)。

使用迭代器删除元素

List<String> s=new LinkedList<String>();s.add("a");  s.add("b"); s.add("c");Iterator it=s.iterator();it.next();it.remove();

链表的元素位置一个连一个,想要操控某个位置元素必须1个个元素迭代,使用add(int index,E element),remove(int index),get(int index)
对某个位置进行操作,都是依赖迭代器的,底层都是不断调用next移动位置,所以对需要操控位置的方法效率比较低

Iterator接口没有add方法,使用起来不方便,直接使用集合add效率低。

设计了一个实现List集合都有的迭代器:ListIterator,支持添加,倒序,控制迭代器起始位置。

interface ListIterator implements Iterator//ListIterator实现了Iterator接口{E previous()//反向返回下一个元素boolean hasPrevious()//反向是否有下个元素set(E e)//替换上一次调用next或previous返回的元素nextIndex()//下个元素角标previousIndex()//反向下个元素角标}

ListIteator增加了几个方法方便在于,原来的Iterator迭代器创建后,集合进行了改动会导致快速失败,再调用迭代器方法将抛出异常(hasNext除外)

使用ListIterator添加元素:

List<String> s=new LinkedList<String>();s.add("Amy");s.add("Bob");s.add("Carl");ListIterator<String> it=s.listIterator()//迭代器记得写类型限定it.next();it.add("Juliet");//把元素添加到迭代器位置后面第一个位置

set方法修改next方法或previous方法返回的元素,它们是依赖关系

List<String> s=new LinkedList<String>();s.add("321");ListIterator<String> it=s.listIterator();String a=it.next();it.set("123");

listIterator(int index)方法可以控制迭代器位置,当参数为集合size时,调用previous可反向遍历集合

当一个集合创建了两个迭代器,一个迭代器对集合进行了修改,或者集合本身方法对集合进行了修改,另一个迭代器发现了这些修改会抛出ConcurrentModification异常(布尔方法不会抛出异常)。

list <String> s=new LinkedList<String>();

Listiterator it=s.listIterator();

ListIterator it2=s.listIterator();

it.add("123");

it.add("321");//抛出ConcurrentModification异常

为了避免此异常的发生需遵循下列原则:可以根据需要给容器附加许多的迭代器,但是这些迭代器只能读取列表。另外再单独附加一个既能读又能写的迭代器。

集合采用一个简单的方法检测并发修改。集合跟踪记录操作次数,每个迭代器都维护一个计数值,当计数值和集合的操作数不一致时,抛出异常

有个例外是set方法不会影响计数值和操作数。
链表不支持随机访问。想查看第N元素只能从头迭代。还是提供了get(int 位置)方法,但效率却比较低,当你需要在链表使用get方法时就应该考虑这种集合是否合适

例:创建两个链表,把他们合并,从第二个链表中每隔一个元素删除一个元素,最后调用removeAll

public class Test0{       public static void main(String[]args)       {    LinkedList<String> a=new LinkedList<String>();    a.add("A");    a.add("B");    a.add("C");    LinkedList<String> b=new LinkedList<String>();    b.add("1");    b.add("2");    b.add("3");    ListIterator<String> ait=a.listIterator();    Iterator<String> bit=b.iterator();        while(bit.hasNext())    {    if(ait.hasNext())ait.next();    ait.add(bit.next());//a集合每隔一个元素添加一个b集合元素    }    System.out.println(a);        bit=b.iterator();    while(bit.hasNext())    {    bit.next();    if(bit.hasNext())    {    bit.next();    bit.remove();//相隔一个元素删除一个    }    }    System.out.println(b);        a.removeAll(b);    System.out.println(a);  }


2.2数组列表

具体集合:ArrayList,Vector

优点:支持随机访问且效率较高

缺点:底层是一个动态数组,当删除或添加时效率低

ArrayList的方法不是同步的,Vector的方法都是同步的,依情况决定使用哪个

2.3散列表

具体集合:HashSet,LinkedHashSet

优点:查找,删除,添加快

缺点:无序,依赖hash code,需要自定义equals,hashCode方法

散列码又称哈希码hash code,散列表依赖于散列码进行排序,散列码很难预知,所以一般说散列表是无序的。(数字散列码按顺序数字大小排列)

散列表是怎么储存元素的?

散列表用链表数组实现,每个列表被叫做桶。元素储存在桶里,如果一个散列表桶数(长度)为128,一个对象是的散列码是76268,用76268%128,结果108就是这对象储存的桶的位置。有时候会出现桶已经被占了(散列码一样),就用equals方法比较桶中对象是否一样,如果相同不储存,如果不同就存进这个桶中,排在最后。

散列码合理随机,桶数目够大,可减少比较的次数。


散列表都要指定初始桶数,一般设为预计的75%-150%。默认桶数----16

当散列表满了就会自动增加桶数,增加桶数取原桶数的平方。先按照装填因子--默认0.75,使用的桶数超过75%就创建新的列表,把旧表的元素重新散列到新表,再删除旧表

下面是一个对HashSet集合中元素进行添加,计算添加消耗的时间,并遍历,使用java aaa < xxx.txt,能读取到文件单词。

import java.util.*;class aaa {public static void main(String[] args) {        Set<String> words =new HashSet<>Scanner in=new Scanner(System.in);while(in.hasNext()){   String word=in.next();   long callTime=System.currentTimeMills();   words.add(word);   callTime=System.currentTimeMills()-callTime;   totalTime+=callTime;}Iterator<String> iter=words.iterator();for(int i=1;i<=20&&iter.hasNext();i++){     System.out.println(iter.next());}System.out.println(".......");System.out.println(words.size()+"distinct"+totalTime+"milliseconds.");}}


HashSet集合就是查找快,方法很少,作为临时容器很好用,想进行复杂操作再把元素转移就行了。


2.4树集

具体集合:TreeSet,LinkedTreeSet

特点:速度中规中矩,是有序的,使用了红黑树数据结构,对元素大小敏感的操作有优势。元素必须实现Comparable接口。添加速度没有散列表快。只能存储一种类型元素。

树集的对象比较

 树集是有序的,元素比较大小后按红黑树结果排列。那元素是怎么比较的呢?

前面学过实现Comparable接口的ComparTo方法,实现两个相同类型不同对象比较大小,树集集合就是使用这种比较方法,因为只能同类型间比较所以树集只存储同类型对象,即使并没有指定类型限定,不然调用ComparTo方法会产生类型不符的异常。

public interface Comparable<T>{         int compareTo(T other); }class Item implements Comparable<Item>{           public int compareTo(Item other)          {                        return this.partNumber-other.partNumber;          }}

这个方法实现缺点是值可能会溢出。可以使用大数值。不过如果溢出几率很低就没必要用这个。

用compareTo方法比较有个缺陷是比较对象,那比较的条件是固定的,可能需要要其它域比较,重写类很麻烦,还会影响原来的对象。

解决方法,Comparator接口,声明了一个两个参数的方法可以比较两个对象

public interface Comparator<T>{            int compare(T a,T b);} class ItemComparator implements Comparator<Item>{           public int compar(Item a,Item b)      {               String s=a.get();               String ss=b.get();               return s.CompareTo(ss);      }}ItemComparator comp=new ItemComparator();//比较器SortedSet sort=new TreeSet<Item>(comp);//把比较器传递进集合

使用匿名内部类减少代码

<pre name="code" class="java">SortedSet sort=new TreeSet<Item>(new Comparator<Item>(){            public int compar(Item a,Item b)      {               String s=a.get();               String ss=b.get();               return s.CompareTo(ss);      }}  );

Comparator接口还有个equals方法,在添加其它集合元素时(addAll),能提高效率。

TreeSet可以说除了速度比HashSet差点,优点多多,可以取代HashSet吗?

如果不要求排序那么就必要替代,而且TreeSet使用比较进行排序,但是有些对象比较起来是比较苦难的,如矩形对象怎么比较,面积?长宽呢?

java6开始TreeSet实现了NavigableSet接口,拥有更多操作集合的方法。

2.6队列和双端队列

队列,先进先出,添加元素在尾端,删除头部元素。

双端队列,在队列两端都能添加删除

实现了Queue接口有队列属性,实现Deque接口的有双端队列属性。

2.7优先级队列

具体集合:PriorityQueue

使用堆数据结构

优点:迅速删除最小的元素
缺点:功能少

PriorityQueue集合迭代元素按照添加顺序,删除却总是删除集合中最小的。

remove()方法返回删除得元素

2.8映射表

这种数据结构通过储存键和值,键和值有一一对应的映射关系,通过键能精确查找到值。

具体集合:HashSet,TreeSet

散列表映射只对键映射,树映射表只对键比较排序,只对键散列或比较,值不参与。

往映射表添加对象,必须也提供一个对应的键,键可以为任何应用类型,如以下采用字符串类型。

Map<String,Employee> s=new HashMap<String,Employee>();String a="123";  Employee b=new Employee();s.put(a,b);
检索元素时提供键

Employee e=s.get(a);
如果查找不到此键将返回null。


键必须是唯一的,映射表不允许重复的键

调用了两次put方法添加键和元素,第二次添加的值将覆盖第一次的值,且第二次调用put将返回上一次储存的值。


集合框架没有把映射表本身视为一个集合。不过映射表包装了三个视图,都实现了Collection接口。

三个视图分别是,键集,值集和键-值对。通过三个方法能返回三个视图。

Set<K> KeySet()

Collection<K> values()

Set<Map.Entry<K,V>> entrySet()

注意KeySet()不是HashSet,也不是TreeSet,反而HashSet,TreeSet的底层是映射表。

KeySet是一个实现了Set接口的某个类的对象。  

枚举映射表的所有键:

Set<String> keys=map.keySet();for(String key:keys){          //do some thing with key}
枚举键值对

for(Map.Entry<String,Employee> entry:s.entrySet()){             String key=entry.getKey();             Employee value =entry.getValue();            // do something with key,value}

当返回映射表视图,如键集,可以使用迭代器删除键,等于也删除了对应的值。但是不能添加值,因为映射表必须键值同时添加。如果调用add方法将抛出UnsupportedOperationException异常。条目集在概念上讲可以添加新条目。




2.9专用集和映射表类

1弱散列映射表

当一个键的引用不能再使用了,无法通过键得到对应的值,这个键值却无法被删除。

垃圾回收机制也无法回收,因为映射表是活动的,存储的桶也是活动的,这时候可以使用WeakHashMap完成回收工作。当对键的唯一引用来自散列表条目时,这个数据结构将与垃圾回收器协同工作一起删除键值。

WeakHashMap使用了弱引用保存键,WeakReference对象把引用保存到另一个对象中。如果某个对象只能有WeakReference引用,垃圾回收器将回收它。但是是先把这个弱引用放到队列中,WeakHashMap周期检查队列将它删除。


2链接散列集和链接映射表

LinkedHashSet记住了添加顺序的散列表,使用迭代器遍历是按添加顺序迭代。

LinkedHashMap使用了访问顺序。访问顺序:每次调用get或put,调用的条目将从当前位置删除,并放在了链表的尾部。

构造器LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)accessOrder参数值为true采用访问顺序,false采用插入顺序。

可以创建一个LinkedHashMap子类,经常调用的元素在链表尾部,当链表长度满了,就删除N个在头部的元素,保证了不常用的元素删除,链表长度不会增长。


3.枚举集和枚举映射表

EnumSet是一个枚举类型元素集。EnumSet没有构造器,只能使用静态工厂方法构造这个集

EnumMap是一个键为枚举类型的映射表。必须在构造器指定键类型class对象

EnumMap<enum,Employee> e=new EnumMap<enum,Employee>(enum.class)

注释:API中会看到E extends Enum<E>类型,表明E为枚举类型。任何枚举类都继承了Enum。


4标识散列映射表

IdentityHashMap,这个类使用了System.identityHashCode方法计算哈希值,和Object类的hashCode方法计算哈希值一样。这个集合使用==而非equals比较对象。


13.3集合框架

框架是一个类的集,它奠定了创建高级功能的基础。框架包含很多超类,这些超类拥有非常有用的功能,策略和机制。框架使用者创建的子类可以扩展超类的功能,而不必重新创建这些基本的机制。

了解框架知识,对于实现用于多种集合类型的泛型算法,或者是想要增加新的集合类型是很多帮助的。

集合有两个基本的接口:Collection和Map

可以使用boolean add(E element)在集合中插入元素,映射表使用V put(K key ,V value)保存键-值。

想要读取集合某个元素,可以使用迭代器访问它们,也可以用get方法。

List是一个有序集合。元素可以添加到集合特定位置,可以使用列表迭代器或者整数索引。List接口定义了几个随机访问方法。

void add (int index,E element)E get(int index)void remove(int index)

                                                                集合框架的接口

为了避免使用成本较高的随机访问操作,高效随机访问集合都实现了标记接口RandomAccess。如:ArrayList,Vector。

c instanceof RandomAccess

Set接口拒绝添加重复的元素,Set集合间使用equals方法只判断元素是否相等顺序不必相同。hashCode方法要保证相同的集合散列码也要相同。

SortedSet和SortedMap接口暴露了用于排序的比较器对象,并且定义的方法可以获得子集视图。

NavigableSet和NavigableMap包含了几个用于在有序集和映射表中查找和遍历的方法

集合接口有大量方法,抽象集合类实现了接口方法,为创建集合类提供了便捷。

AbstractCollection,AbstractList,AbstractSequentialList,AbstractSet,AbstractQueue,AbstractMap

java类库支持下面的具体类

LinkedList,ArrayList,ArrayQueue,HashSet,TreeSet,PriorityQueue,HashMap,TreeMap


                                                                集合框架的类

还有一些遗留的类

Vector,Stack,Hashtable,Properties


3.1视图和包装器

集合框架中没有明示的集合--视图,通过视图可以获得其他的实现了集合接口和映射表接口的对象。如映射表keySet方法返回的集合

视图并不是新建一个新集合,而是实现了集合接口的对象,这个类的方法对原集合进行操作。像镜像。

视图的操作能影响原集合,原集合的操作也会改变视图

1.轻量级集包装器

返回数组视图

Card[]card =new Card[10];List<Card> cList=Arrays.asList(card);
返回的是数组视图但不是ArrayLst集合。不能改变视图的大小,否则将抛出UnsupportedException异常。

java5.0开始asList方法是可变参数public static <T>List<T>asList(T... a),可以传数组也可以传其它类型。

List<Card> cList=Arrays.asList("ab","bf","sdf");//创建的是字符串的视图。

Collections.nCopies(n,object),创建一个实现List接口的不可修改的对象,存有n个object对象。其实都只是object的视图。

List<String> settings=Collections.nCopies(100,"ert");只存在一个“ert”。


Collections.singleton(object)返回一个实现了Set接口不可修改的单元素集。

Collections.singletonList(object)返回实现了List接口的

Collections.singletonMap(object)返回实现Map接口的

2.子范围

抽取集合一部分作为视图

有序集合利用索引抽取

List group2=staff.subList(10.20);//包括10,不包括20.
比较排序集合利用元素抽取

SortedSet<E> subSet(E from, E to)

SortedSet<E> headSet( E to)

SortedSet<E> tailSet(E from)


SortedMap<K ,V> subMap(K from, V to)

SortedMap<K ,V> headMap(K to)

SortedMap<K ,V> tailMap(K from)

子范围视图可以使用原集合的所有操作,子范围的操作会反映到原集合。


java6引入一个继承SortedSet,SortedMap的接口NavigableSet,NavigableMap,操作能力更强,可以指定是否包括边界

如:Navigable<E> subSet(E from,boolean formInclisive,E to ,boolean toInclusive)

3.不可修改的视图

产生集合的不可修改视图,这些视图对现有的集合增加了一个运行时的检查。如果发现试图对集合进行修改,就抛出一个异常。

有六个方法获得不可修改视图:

Collection.unmmodifiableCollection

Collection.unmmodifiableList

Collection.unmmodifiableSet

Collection.unmmodifiableSortedSet

Collection.unmmodifiableMap

Collection.unmmodifiableSortedMap

警告:unmmodifiableCollection的equals使用了Object.equals,hashCode。

4.同步视图

集合框架有一些线程安全集合,想实现线程安全还有一种方式,Collections.synchronizedMap等静态方法

Map<String,Employee> map=Collections.synchronizedMap(new hashMap<String,Employee>());
现在另一个线程调用时都必须等前一个方法调用完成。

5.被检视的视图
以下代码,逃避了泛型的类型检查

ArrayList<String> a=new ArrayList<String>();ArrayList b=a;b.add(123);
使用被检视视图避免此问题

List safeStrings =Collections.checkedList(a,String.class);

safe.add(123);//抛出了一个ClassCastException异常

警告:Collections.checkedList()方法参数传入一个要检查的类型的class对象,如ArrayList<Pair<String>>的被检视视图参数为Pair.class,所以Pair<Double>无法检查出错误



 

13.3.2批操作

Collections的一些批量操作方法

两个集合交集,result.retainAll(b);集合result和b的交集

Map<String,Employee> staffMap=new TreeMap<String,Employee>();

Set<String> terminatedIDs=new hashSet<String>();

staffMap.keySet().removeAll(terminatedIDs);//removeAll,工人集合删除一批ID在集中的工人

还有addAll(Collection s),clear().

3.3集合与数组之间的转换

Object[] toArray()//集合转换为数组,为Object[]类型
<T> T[] toArray(T[] a)集合存放到数组a,数组不够长久创建一个类型相同的数组存放

数组转集合,一般集合都提供一个构造器传递集合,先把创建数组的视图再传给构造器。

4.0算法

排序和混排

Collections.sort(satff);使用compareTo方法排序,

Collections.sort(satff,compartor2);没实现Comparator接口的传递一个比较器

Collections.sort(satff,Collections.reverseOrder());对compareTo结果进行逆转

Collections.sort(satff,Collections.reverseOrder(comparator2));对comparator2比较器结果进行逆转

只能对实现了List接口的集合使用Sort(),此排序使用了归并排序,把所有的元素转入一个数组,排序,再复制回集合。


支持排序需要集合时可修改的,但不必是可修改大小

有关术语

如果列表支持Set方法,那么列表是可更改的

如果列表支持remove,add方法,那么列表是可改变大小的


混排:把列表元素打散,Collection.shuffle(List a),如果列表没有实现RandomAccess接口,shuffle方法将元素复制到数组,打散再复制回来。



4.2二分查找

Collections.binarySearch方法查找元素。注意,集合必须先排好序,不然会返回错误答案,集合必须实现List接口,没有实现Comparable接口还要提供比较器

i=Collections.binarySearch(c,element,comparator);

返回的值大于0,表示返回值为查找值得索引,负值为没找到值,不过可以利用这个负值插入查找的元素c.add(-i-1,element);

二分查找在采用随机访问才有优势,如果集合为一个链表,将自动转为线性查找。(奇怪,不能把集合转为数组?想想当然不行,有转到数组的时间早就查找到元素了)

注释:以前会检查集合是否实现了AbstractSequentialList接口,实现了就采用线性查找,否则就二分查找。现在binarySearch方法会检查是否实现了RandomAccess接口。


4.3简单算法


             

13.5遗留的集合

Hashtable类和Vector在HashMap和ArrayList需要实现线程同步是使用


枚举

遗留集合使用Enumeration进行遍历,类似于Iterator,有两个方法

hasMoreElements  是否有下个元素

nextElement  返回下个元素


有些遗留的方法参数类型依然是Enumeration类型

SequenceInputStream类构造器SequenceInputStream(Enumeration<? extends InputStream> e) 


属性映射表

键与值都是字符串

表可以保存到一个文件中,也可以从文件中加载

使用一个默认的辅助表

实现映射表的java平台类叫Properties




0 0
原创粉丝点击