java容器(容器深入研究--think in java 17章)

完整的java容器UML图:

                                                                 

填充容器:

Collections.nCopies()复制一个对象的引用产生一个包含n个相同元素的List。
Collections.fill()方法复制同一个对象引用来填充整个容器,并且只对List对像有用,但是产生的列表可以传递给构造器或addAll()方法。
Collections.fill()方法用处有限,只能替换已经在List中存在的元素,而不能添加新元素。
可以通过Map.putAll()和Collection.addAll()来初始化Map和Collection.
每个java.util容器都有自己的Abstract类,它们提供了该容器的部分实现,因此只须去实现那些产生想要的容器所必须的方法。如果所产生的容器是只读的
需要提供的方法的数量将减到最少。
为了创建只读的Map,可以继承AbstractMap并实现entrySet()。为了创建只读的Set,可以继承AbstractSet并实现itrrator()和size();

Collection的功能方法:

下面的列出了可以通过Collection执行的所有操作(不包括从Object继承而来的方法),Set和List也拥有这些操作。
boolean add(T)确保容器持有具有泛型类型T的参数。如果没有将此参数添加进容器,则返回false()(可选的)
boolean addAll(Collection<? extends T>) 添加参数中的所有元素。只要添加了任意元素就会返回true(可选的)
void clear() 移除容器中的所有元素(可选的)
boolean contains(T) 如果容器已经持有此参数则返回true
boolean containsAll(Collection<?>)如果容器持有此参数中的所有元素,则返回true.
boolean isEmpty()容器中没有元素时返回true
Iterator<T> itrrator()返回一个Iterator<T>,可以用来遍历容器中的元素。
boolean remove(Object)如果参数在容器中,则移除此元素的一个实例。如果做了移除动作,则返回true(可选的)
boolean removeAll(Collection<?>)移除参数中的所有元素。只要有移除动作发生就返回true(可选的)
boolean retainAll(Collection<?>)只保存参数中的元素(应用集合论的交集概念)。只要Collection发生了改变就返回true(可选的)
int size()返回容器中元素的数目
Object[] toArray()返回一个数组,该数组包含容器中的所有元素。
<T>T[]toArray(T[] a)返回一个数组,该数组包含容器中的所有元素。返回结果的运行时类型与参数数组a的类型相同,而不是单纯的Object.
共有操作不包括get()方法,因为Collection包括Set,而Set是自己维护内部顺序的(这使随机访问变的没有意义)

可选操作:(不解)

例如Collection接口提供了add方法,但他是可选的;若果你想要实现一个自定义的MyList,并且它是只读的,那么你可以不实现add方法,若调用了add方法,
则会报一个 UnsupportedOperationExpection错误
执行各种不同的添加和移除的方法在Collection接口中都是可选操作,这意味着实现类并不需要为这些方法提供功能定义。另外,将Collection当作
参数接受的大多数方法只会从Collection中读取,而Collection的读取方法都不是可选的。
未获支持的操作:
最常见的未获支持的操作来源于背后固定尺寸的数据结构支持的容器。当你用Arrays.asList()将数组转换为List时,就会得到这样的容器。
Collections类中的“不可修改”方法,可以创建会抛出UnsupportedOperationExpection错误的容器(包括map).
当试图更改Arrays.asList产生的List的尺寸或者修改Collections.unmodifiableXXX()返回的容器时都会抛出UnsupportedOperationExpection错误

List的功能方法:

大多数时候只是调用add()添加对象,使用get()一次取出一个元素,以及调用iterator()获取Iterator();

Set和存储顺序:

存储顺序如何维护在Set的不同实现间会有变化。
Set(接口) 存入Set的每个元素都必须是唯一的,因为Set不保存重复元素。加入Set的元素必须定义equals()方法以确保对象的唯一性。Set与Collection
有完全一样的接口。Set接口不保证维护元素的次序。
HashSet 为快速查找而设计的Set,存入HashSet的元素必须定义hashCode()
TreeSet 保持次序的Set,底层为树结构,使用它可以从Set中提取有序的序列。元素必须实现Comparable接口。
LinkedHashSet 具有HashSet的查询速度,且内部使用链表维护元素的顺序(插入的次序),于是在使用迭代器遍历Set时,结果会按元素插入的次序显示。
元素也必须定义hashCode()方法。
如果没有其他限制,HashSet应该是首选。
必须为散列存储和树形结构都创建一个equals()方法,但是hashCode()只有在这个类将被置于HashSet或LinkedHashSet中时才是必须的。
对于良好的编程习惯,应该在覆盖equals()方法时同时覆盖hashCode()方法。
如果一个类的对象被用于任何种类的排序容器中,例如SortedSet(TreeSet是其唯一实现),那么它必须实现Comparable接口或是传递一个比较器(实现了
Comparator的类)。
通常应使compareTo()方法和equals()方法一致,即如果equals()返回true,那么compareTo()应该返回0.

SortedSet中的元素可以保证处于排序状态,这使得它可以通过SortedSet接口中的下列方法提供附加的功能:
    Comparator comparator()返回当前Set使用的Comparator;或者返回null,表示以自然方式排序。
    Object first()返回容器中的第一个元素。
    Object last()返回容器中的最末一个元素。
    SortedSet subSet(fromElement,toElement)生成此Set的子集,范围从fromElement(包含)到toElement(不包含)。
    SortedSet headSet(toElement)生成此Set的子集,由小于toElement的元素组成。
    SortedSet tailSet(fromElement)生成此Set的子集,由大于或等于fromElement的元素组成。

队列:

除了并发应用,Queue在java中仅有LinkedList和PriorityQueue,它们的差异在于排序行为而不是性能。
优先级队列:可以在比较方法中设置两个优先级,主优先级和次优先级,主优先级相同的情况下比较次优先级。
双向队列可以在任何一端添加或移除元素,可以用addFirst/Last() getFirst/Last() removeFirst/Last()实现双向队列的功能。

理解Map:

性能:
性能是映射表中的一个重要问题,如果在get()中使用线性搜索时,执行速度会相当慢,而这也是HashMap提高速度的地方。HashMap使用了特殊的值,
称作散列码,来取代对键的缓慢搜索。
HashMap Map基于散列表的实现(它取代了Hashtable)。插入和查询“键值对”的开销是固定的。可以通过构造器设置容量和负载因子,以调整容器的性能。
LinkedHashMap 类似于HashMap,但是迭代遍历它时,取得“键值对”的顺序是按其插入次序,或者是最近最少使用(LRU)的次序。只比HashMap慢一点;而
在迭代访问时反而更快,因为它使用链表维护内部次序。
TreeMap 基于红黑树的实现,查看"键"或"键值对"时,它们会被排序(次序由Comparable或是Comparator决定)。TreeMap是唯一带有subMap()方法的Map,
它可以返回一个子树。
WeakHashMap 弱键映射,允许释放所指向的对象;这是为解决某类特殊问题而设计的。如果映射之外没有引用指向某个键,则此键可被垃圾收集器回收。
ConcurrentHashMap一种线程安全的Map,它不涉及同步加锁。我们将在"并发"一章中讨论。
IdentityHashMap 使用==代替equals()对"键"进行比较的散列映射。专门为特殊问题而设计。

Map中任何一个键都要有一个equals()方法,如果键被用于散列Map,那么它还必须具有恰当的hashCode()方法;如果键被用于TreeMap,那么它还必须实现
Comparable。

SortedMap:使用SortedMap(TreeMap是其唯一实现),可以确保键处于排序状态。这使它具有额外的功能,这些功能是由SortedMap接口中的下列方法提供的:
    Comparator comparator():返回当前Map使用的Comparator,或者返回null,表示以自然方式排序。
    T firstKey()返回Map中的第一个键。
    T lastKey()返回Map中的最后一个键。
    SortedMap subMap(fromKey,toKey)生成此Map的子集,范围由fromKey(包含)到toKey(不包含)的键确定。
    SortedMap headMap(toKey) 生成此map的子集,由键小于toKey的所有键值对组成。
    SortedMap tailMap(fromKey)生成此map的子集,由键大于或等于fromKey的所有键值对组成。
LinkedHashMap:为了提高速度,LinkedHashMap散列化所有元素,但是在遍历键值对时,却又以元素的插入顺序返回键值对(System.out.println()会遍历该
映射,因此可以看到遍历的结果。),可以在构造器中设定LinkedHashMap,使之采用基于访问的最近最少使用(LRU)算法,于是没有被访问过的元素就出现
在队列的前面。(三个参数的构造函数的最后一个设置为true)

散列与散列码:

当用到使用散列算法的容器时,必须同时覆盖equals()和hashCode()方法。hashCode()方法产生哈希值,可转化成对应哈希表的下标,哈希表上同同一下标的
元素被放入链表中,用equals()遍历链表便可找到对应键的键值对。
正确的equals()方法必须满足下列5个条件:
1)自反性。对任意x,x.equals(x)一定返回true。
2)对称性。对任意x和y,如果y.equals(x)返回true,则x.equals(y)也返回true
3)传递性。对任意x,y,z,如果有x.equals(y)返回true,y.equals(z)返回true,则x.equals(z)返回true。
4)一致性。对任意x和y,如果对象中用于等价比较的信息没有改变,则无论调用x.equals(y),返回的结果应保持一致。
5)对任何不为null的x,x.euqals(null)一定返回false。

如果要使用自己的类作为HashMap的键,必须同时覆盖hashCode()和equals().

hashCode()并不总是能够返回唯一的标识码,但是euqals()方法必须严格的判断两个对象是否相等。

散列算法:存取一组元素最快的数据结构是数组,所以使用它来表示键的信息。数组并不保存键本身,而是通过键对象生成一个数字,将其作为数组的下标。
这个数字就是散列码,由定义在Object中的可能被覆盖的hashCode()生成。为解决冲突问题(或者数组容量固定问题),不同的键可以产生相同的下标
(即可以发生冲突)。冲突由外部链接处理:数组并不直接保存值,而是保存值的list。然后对list中的值用equals()方法进行线性查询。

首先,你无法控制backet数组下标值的产生。这个值依赖于具体的HashMap对象的容量,而容量的改变与容器的充满程度和负载因子有关。
hashCode()生成的结果经过处理成为桶位的下标。因为在生成桶的下标前,hashCode()还需要做进一步的处理,所以散列码的生成范围并不重要,只要是int
即可。
好的hashCode()应该产生分布均匀的散列码。
hashCode()基本指导:
1)给int变量result赋予某个非0值常量,如17。
2)为对象内每个有意义的域f(即每个可以做equals()操作的域)计算出一个int散列码c:
        boolean                                         c=(f?0:1)
        byte,char,short或int                           c=(int)f
        long                                           c=(int)(f^(f>>>32))
        float                                           c=Float.floatToIntBits(f);
        double                                           long l=Double.doubleToLongBits(f);
                                                    c=(int)(l^(l>>>32))
        Object,其equals调用这个域的equals()         c=f.hashCode()
        数组                                        对每个元素应用上面的规则。
3)合并计算得到散列码 :        result=37*result + c;
4)返回result。
5)检查hashCode()最后生成的结果,确保相同的对象有相同的散列码。


实用方法:

java中有大量用于容器的卓越的使用方法,它们被表示为java.util.Collections类内部的静态方法:

checkedCollection(Collection<T>,Class<TreeMap> type) ,checkedList(List<T>,Class<T> type),checkedMap(Map<K,V>,Class<K> keyType,Class <V> valueType),
checkedSet(Set<T>,Class<T> type),checkedSortedMap(SortedMap<T>,Class<T> type)
产生Collection或者Collection的具体子类型的动态类型的安全的视图。在不可能使用静态
检查版本时使用这些方法。

max(Collection),min(Collection) 返回参数Collection中最大或最小的元素--采用Collection内置的自然比较法。
max(Collection,Comparator),min(Collection,Comparator) 返回参数Collection中最大或最小的元素--采用Comparator进行比较。
indexOfSubList(List source,List target) 返回target在source中第一次出现的位置,或者找不到返回-1
lastIndexOfSubList(List source,List target) 返回target在source中最后一次出现的位置,或者找不到返回-1
replaceAll(List<T>,T oldVal,T newVal) 使用newVal替换所有oldVal
reverse(List) 逆转所有元素的次序。
reverseOrder(),reverseOrder(Comparator<T>)返回一个Comparator,它可以逆转实现了Comparator<T>的对象集合的自然顺序。第二个版本可以逆转所提供的Comparator的顺序。
rotate(List,int distance) 所有元素向后移动distance个位置,将末尾的元素循环到前面来。
shuffle(List),shuffle(List,Random)随机改变指定列表的顺序。第一种形式提供了其自己的随机机制,你可以通过第二种形式提供自己的随机机制。
sort(List<T>),sort(List<T>,Comparator<? Super T> c) 使用List<T>中的自然排序,第二种形式允许提供用于排序的Comparator。
copy(List<? super T> dest,List<? extends T> src) 将src中的元素复制到dest
swap(List,int i,int j) 交换list中位置i与位置j的元素。通常比自己写的代码快。
fill(List <? super T>,T x) 用对象x替换list中的所有元素
nCopies(int n,T x) 返回大小为n的List<T>,此List不可改变,其中的引用都指向x。
disjoint(Collection,Collection) 当两个集合没有任何相同的元素时,返回true。
frequency(Collection , Object x) 返回Collection中等于x的元素个数。
emptyList(),emptyMap(),emptySet() 返回不可变的空List,Map,或Set。这些方法都是泛型的,因此所产生的结果将被参数化为所希望的类型。
singleton(T x),singletonList(T x) ,singletonMap(K key,V value) 产生不可变的Set<T>,List<T>或者Map<K,V>,它们都只包含基于给定参数的内容而
形成的单一项。
list(Enumeration<T> e) 产生一个ArrayList<T>,它包含的元素顺序与Enumeration(Iterator的前身)返回的顺序相同。用来转换遗留的老代码。
enumeration(Collection<T>) 为参数生成一个旧式的Enumration<T>

List的排序和查询:

如果使用Comparator进行排序,那么binarySearch()必须使用相同的Comparator。

Collection或Map的同步机制:

将容器传递给Collections.synchronizedXXX()方法可以得到同步容器。
快速报错:java有一种保护机制,能够防止多个进程同时修改同一个容器的内容。
如果在迭代遍历某个容器的过程中,拎一个进程介入其中,并且插入,删除或修改此容器内的某个对象,那么就会出现问题:也许迭代过程已经处理过
容器中的该元素了,也许还没处理,也许在调用size()之后尺寸改变了。
java采用快速报错机制。它会探查容器上的任何除了当前进程所进行的操作以外的所有变化,一旦发现其他进程修改了容器,就会立刻抛出ConcurrentModi
ficationException异常。
例如:在获取了迭代器后又向容器添加元素,就会报错。


持有引用:


有三个继承自抽象类Reference的类:SoftReference,WeakReference和PhantomReference。当垃圾回收器正在考察的对象只能通过某个Reference对象才
“可获得”时,上述这些不同的派生类为垃圾回收器提供了不同级别的间接性指示。
对象是可获得的,是只有一个或多个普通引用直接或间接的指向此对象。如果一个对象是可获得的,那么垃圾回收器就不能释放它。
WeakHashMap用来保存WeakReferance。它使得规范映射更易于使用。在这种映射中,每个值只保存一份实例以节省内存空间,当程序需要使用那个值的时候
,便在映射中查询现有的对象,然后使用它。
向WeakHashMap添加键值没有什么特殊要求,映射会自动使用WeakReference包装它们。


Java1.0/1.1的容器:

Vector和Enumeration
在java1.0/1.1中,Vector是唯一可以自我扩展的序列,所以被大量使用。基本上可以把它看作是ArrayList。在订正过的java容器类库中,Vector被改造过
,可将其归类为Collection和List。
Enumeration接口比Iterator小,只有两个名字很长的方法:boolean hasMoreElements(),如果此枚举包含更多的元素,该方法就返回true;另一个为Object
 nextElement(),该方法返回此枚举的下一个元素。
 应该在代码中避免用这些老的类库。
栈Stack:设计非常糟糕,永远也别用。
BitSet:
如果想要高效率的存储大量"开/关"信息,BitSet是很好的选择。不过它的效率仅对空间而言,如果需要高效的访问时间,BitSet比本地数组稍微慢一点。

猜你喜欢

转载自blog.csdn.net/yanshaoshuai/article/details/81070948
今日推荐