.集合

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,支持添加,倒序,控制迭代器起始位置。
</pre><p><pre name="code" class="java">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方法会产生类型不符的异常。
<pre name="code" class="java">public interface Comparable<T>
{
int compareTo(T other);
}
class Item implements Comparable<Item>
{
public int compareTo(Item other)
{
return this.partNumber-other.partNumber;
}
}
这个方法实现缺点是值可能会溢出。可以这样:return new Integer(parNumber).compareTo(other.partNumber);不过如果溢出几率很低就没必要用这个。
用compareTo方法比较有个缺陷是比较对象,那比较的条件是固定的,可能需要要其它域比较,重写类很麻烦,还会影响原来的对象。
解决方法,Comparator接口,声明了一个两个参数的方法可以比较两个对象
<pre name="code" class="java">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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值