天天看点

Java编程思想总结篇——第十七章(上)第十七章 容器深入研究

第十七章 容器深入研究

1 完整的容器分类法

容器分为Collection集合类,和Map键值对类2种。

Java编程思想总结篇——第十七章(上)第十七章 容器深入研究

Collection 接口:

1. List 接口  (按插入顺序保存,元素可以重复):

  • ArrayList (相当于大小可变的数组,随机访问快,插入移除慢)
  • LinkedList(插入移除快,随机访问慢,也实现了Queue接口)

2. set 接口(不能有重复元素)

  • HashSet(元素无序,查询速度非常快)
  • LinkedHashSet(按插入顺序保存,同时有HashSet的查询速度)
  • TreeSet(按元素升序保存对象,或者根据元素实现的比较器排序)

Queue接口(一头进一头出):

  • PriorityQueu(优先级高的先出,也实现了List接口)

Map接口:

  •  HashMap (查找速度快,内部无规则排序)
  • LinkedHashMap(按插入顺序排序,查找速度快)
  •  TreeMap(按升序保存对象,或者根据元素实现的比较器排序)

2 填充容器

所有Collection的构造器都可以接收另一个Collection(可以不同类型)来填充自己。

Collections类也有一些实用的static方法,其中包括fill,同Arrays一样只复制同一对象引用来填充整个容器的,并且只对List对象有用,但是所产生的列表可以传递给构造器或addAll方法。

使用最多的就是第三层的容器类,其实在第三层之上还有一层Abstract 抽象类,如果要实现自己的集合类,可以继承Abstract类,而不必实现接口中的所有方法。

数组有Arrays类填充,容器也有Collections类填充,这种工具类中一般都是静态方法不用创建它们的对象直接调用,所以很方便。.

Collections.addAll() 将一些元素添加到一个Collection中

Collections.nCopies() 复制一些元素到Collections中,返回一个List集合

Collections.fill() 复制元素填充到集合当中

享元模式:可在普通解决方案需要过多对象,或者产生普通对象太占用空间时使用享元。享元模式使得对象的一部分可以被具体化,因此,与对象中的所有事物都包含在对象内部不同,可以在更加高效的外部表中查找对象的一部分或整体。

AbstractList 实现了List接口

AbstractMap 实现了Map接口

AbstractSet 实现了Set接口

3 Collection的功能方法

Java编程思想总结篇——第十七章(上)第十七章 容器深入研究

Collection中的方法在List和Set中都实现了,List还添加了额外的方法,如get(),这在Collection和Set中都没有,因为Set无序所以无法确定位置。

迭代器接口方法:

hasNext(); 判断是否存在下一个对象元素

next(); 获取下一个元素

remove(); 移除元素

4 可选操作

可选的方法就是该方法在父类中会抛出异常,如果子类不需要该方法就不必重写它,一但调用则抛出异常,如果需要就去重写它的功能。

Collection中的 各种添加 移除方法都是可选的。AbstractList ,AbstractSet,AbstractQueue中就是实现了可选功能,调用这些抽象类中的方法就会抛出异常。

执行各种不同的添加和移除的方法在Collection接口中都是可选操作。这意味着实现类并不需要为这些方法提供功能定义。

可选操作声明调用某些方法将不会执行有意义的行为,相反,它们会抛出异常。如果一个操作是可选的,编译器仍旧会严格要求你只能调用该接口中的方法。

UnsupportedOperationException必须是一种罕见的事件。即,对于大多数类来说,所有的操作都应该是可以工作的,只有在特例中才会有未获得支持的操作。在Java容器类库中确实如此,因为只有99%的时间里面使用的所有的容器类,如ArrayList、LinkedList、HashSet和HashMap,以及其他具体的实现,都支持所有的操作。

如果一个操作是为获得支持的,那么在实现接口的时候就可能会导致UnsupportedOperationException异常。

最常见的未获支持的操作,源于背后由固定尺寸的数据结构支持的容器。当使用Arrays.asList将数组转换为List时,就会得到这样的容器。还可以通过使用Collections类中不可修改的方法,选择创建任何会抛出UnsupportedOperationException的容器:

因为Arrays.asList会生成一个List,它基于一个固定大小的数组,仅支持那些不会改变数组大小的操作。任何会引起对底层数据结构的尺寸进行修改的方法都会产生一个UnsupportedOperationException异常,以表示对未获支持操作的调用。

Arrays.asList产生固定尺寸的List,而Collections.unmodifiableList产生不可修改的列表。set方法可以在二者产生的对象之间有粒度上的变化。Arrays.asList可以修改元素,没有违反尺寸固定的特性。

5 List的功能方法

ArrayList的Add方法实现原理:  如果elementData中元素数大于10个则复制一份旧数组并扩容再将元素添加就去 。

remove方法:使用本地方法直接操作内存改变,但这也属于浅复制多线程下可能复制出错导致删不掉元素, numMoved =  size - index - 1; 最后将数组最后一个元素设为null 。

size是ArrayList内元素个数,并不是数组长度,ArrayList内数组长度默认最小是10。

LinkedList 内部是由一个内部静态类实现的一个双向链表。

List接口:

  1. get(int); 获取指定索引位置的列表元素
  2. set(int,E); 设置指定索引位置的列表元素
  3. add(int,E); 将指定的元素添加到此列表的索引位置。
  4. remove(int); 移除指定索引位置的元素;
  5. indexOf(Object); 从列表头部查找Object对象元素
  6. lastIndexOf(Objcet); 从列表尾部查找Object对象元素
  7. listIterator(); 返回列表迭代器
  8. listIterator(int); 返回指定索引位置的列表迭代器
  9. subList(int,int); 该方法返回的是父list一个视图,从fromIndex(包含),到toIndex(不包含)

 ListIterator迭代器接口方法:

  1. hasPrevious();  如果以逆向遍历列表,列表迭代器前面还有元素,则返回 true,否则返回false
  2. previous(); 返回列表中ListIterator指向位置前面的元素
  3. set(E); 从列表中将next()或previous()返回的最后一个元素返回的最后一个元素更改为指定元素e
  4. add(E);  将指定的元素插入列表,插入位置为迭代器当前位置之前
  5. nextIndex(); 返回列表中ListIterator所需位置后面元素的索引
  6. previousIndex(); 返回列表中ListIterator所需位置前面元素的索引

6 Set和存储顺序 

Set接口:继承来自Collection接口的行为。

存储顺序不同的set实现不仅具有不同的行为,而且它们对于可以在特定的set中放置元素的类型也有不同的要求:

Java编程思想总结篇——第十七章(上)第十七章 容器深入研究

加入

Set

的元素必须定义

equals()

方法以确保对象的唯一性。

HashSet底层是由HashMap实现的,add进去的值就put在了map中作为key值,为了减小开销,所有value值为同一个new Object() 。 HashSet如果没有其他限制,应该默认选择,因为它对速度进行了优化。

必须为散列存储和树形储存都创建equals方法,但是hashCode只有在类被置于HashSet和LinkedHashSet时才必须。但都应该覆盖equals方法,总是同时覆盖hashCode方法。

保证不重复:先检测有没有相同的hash值,如果没有就添加元素,如果有在equal比较key值,相同则不允许添加,不同则允许添加入map。

如果一个对象被用于任何种类的排序容器中,例如

SortedSet

TreeSet

是其唯一实现),那么它必须实现

Comparable

接口。

注意,

SortedSet

的意思是“按对象的比较函数对元素排序”,而不是指“元素插入的次序”。插入顺序用

LinkedHashSet

来保存。

SortedSet中的元素可以保证处于排序状态,SortedSet接口中的下列方法提供附加的功能:

  1. Comparator comparator()返回当前Set使用的Comparator;或者返回null,表示以自然方式排序。
  2. Object first()返回容器中的第一个元素。
  3. Object last()返回容器中的最末一个元素。
  4. SortedSet subSet(fromElement, toElement)生成此Set的子集,范围从fromElement(包含)到toElement(不包含)。
  5. SortedSet headSet(toElement)生成此Set的子集,由小于toElement的元素组成
  6. SortedSet tailSet(fromElement)生成此Set的子集,由大于或等于fromElement的元素组成

LinkedHashSet也是由 LinkedHashMap实现。

TreeSet底层由TreeMap实现。TreeSet 实现了NavigableSet接口,而NavigableSet接口继承了SortedSet接口 ,NavigableSet提供了搜索功能方法,SortedSet 提供了排序功能方法,

TreeSet(Comparator<? super E> comparator) ,凡是带有比较排序功能容器都有一个能传入比较器Comparator对象的构造方法,使用这个构造器可以根据自己实现的Comparator排序。

7 队列

除了并发应用外,Queue在Java SE5中仅有的两个实现是

LinkiedList

PriorityQueue

,它们仅有排序行为的差异,性能上没有差异。

Queue接口:

  1. boolean add(E e);   添加一个元素,添加失败时会抛出异常
  2. boolean offer(E e); 添加一个元素,通过返回值判断是否添加成功
  3. E remove(); 移除一个元素,删除失败时会抛出异常
  4. E poll();   移除一个元素,返回移除的元素
  5. E element(); 在队列的头部查询元素,如果队列为空,抛出异常
  6. E peek(); 在队列的头部查询元素,如果队列为空,返回null
  7. Queue在Java SE5中仅有的两个实现是LinkedList和PriorityQueue,它们的差异在于排序行为而不是性能

列表中的每个对象都包含一个字符串和一个主要的以及次要的优先级值,该列表的排序顺序也是通过实现Comparable而进行控制的。

双向队列就像一个队列,但是可以在任意一段添加或移除元素。在LinkedList中包含支持双向队列的方法,但在Java标准类库中没有任何显式的用于双向队列的接口。因此,LinkedList无法去实现这样的接口,无法像前面转型到Queue那样向上转型为Deque。但是可以使用组合创建一个Deque类,并直接从LinkedList中暴露相关方法:

  1. queue.addFirst();   向队列首部添加元素
  2. queue.addList();    向队列尾部添加元素
  3. queue.getLast();    获取队列尾部元素
  4. queue.getFirst();   获取队列首部元素
  5. queue.removeFirst();    移除队列首部元素
  6. queue.removeLast(); 移除队列尾元素
  7. queue.size();   返回队列大小

 8 理解Map

Map接口:

  1. size(); 返回Map大小
  2. isEmpty();  判断Map集合是否为空
  3. containsKey();  判断Mpa集合是否包括Key键
  4. containsVaule(); 判断Map集合是否包括Vaule
  5. get(Object);    获得Map集合键为Object的Vaule
  6. put(K,V);   添加一个K,V键值对
  7. remove(Object); 移除K键值对
  8. putAll(Map);    添加所有元素Map集合
  9. clear();    清空Map中所有集合元素
  10. keySet();   返回键Key的Set集合
  11. values();   返回值Vaule的Set集合
  12. entrySet(); 返回Map.entrySet集合
  13. remove(Objcet,Object);  移除K,V集合
  14. replace(K,V,V); 替换键值对

get进行线性搜索时,执行速度会相当慢,这正是HashMap提高速度的地方。

HashMap的底层数据结构:

散列表(哈希表)即 数组+单链表  默认数组大小是16,数组大小一直保持是2n, 数组下标是 key的hash值进行扰乱  再与  数组大小减1  按位 做 & 运算得出。

HashMap使用特殊值,称作散列码,来取代对键的缓慢搜索。散列码是相对唯一的,用以代表对象的int值,它是通过将该对象的某些信息进行转换而生成的。

hashCode()是跟类Object中的方法,因此所有Java对象都能产生散列码。HashMap就是使用对象的hashCode进行快速查询的,此方法能够显著提高性能。

Java编程思想总结篇——第十七章(上)第十七章 容器深入研究

SortedMap:

使用SortedMap(TreeMap的唯一实现),可确保键处于排序状态。使得它具有额外的功能,这些功能由SortedMap接口中的下列方法提供:

  1. Comparator comparator():返回当前Map使用的Comparator;或者返回null,表示以自然方式排序
  2. T firstKey返回Map中的第一个键
  3. T lastKey返回Map中的最末一个键
  4. SortedMap subMap(fromKey,toKey)生成此Map的子集,范围由fromKey(包括)到toKey(不包含)的键确定
  5. SortedMap headMap(toKey)生成此Map的子集,由键小于toKey的所有键值对组成
  6. SortedMap tailMap(fromkey)生成此Map的子集,由键大于或等于fromKey的所有键值对组成

LinkedHashMap:

为了提高速度,LinkedHashMap散列化所有的元素,但在遍历键值对时,却又以元素的插入顺序返回键值对。此外,可以在构造器中设定LinkedHashMap,使之使用基于访问的最近最少使用(LRU)算法,于是没有被访问过的元素就会出现在队列的前面。

键值对是以插入顺序进行遍历的,但是,在LRU版本中,在只访问过前六个元素后,最后三个元素移到了队列前面。然后再次访问元素0时,它被移到了队列后端。

映射表(也称为关联数组Associative Array)

HashMap使用了特殊的值,称作散列码(hash code),来取代对键的缓慢搜索。散列码是“相对唯一”的、用以代表对象的int值,它是通过将该对象的某些信息进行转换而生成的。 

hashCode()是根类Object中的方法,因此所有对象都能产生散列码。  

对Map中使用的键的要求与对Set中的元素的要求一样:

任何键都必须具有一个equals()方法;

如果键被用于散列Map,那么它必须还具有恰当的hashCode()方法;

如果键被用于TreeMap,那么它必须实现Comparable。