查看“核心技术:集合”的源代码
←
核心技术:集合
跳到导航
跳到搜索
因为以下原因,您没有权限编辑本页:
您请求的操作仅限属于该用户组的用户执行:
用户
您可以查看和复制此页面的源代码。
[[category:JavaCore]] == Java 集合框架 == === 将集合的接口与实现分离 === 与现代的数据结构类库的常见情况一样, Java 集合类库也将接口( interface ) 与实现( implementation) 分离。<br/> 以队列为例: : [[File:队列.png|600px]] 队列接口的最简形式可能类似下面这样: <syntaxhighlight lang="java"> public interface Queue<E> // a simplified form of the interface in the standard library { void add(E element) ; E remove(); int size(); } </syntaxhighlight> 而,队列通常有两种实现方式: # 一种是使用循环数组; # 另一种是使用链表; : [[File:队列的实现.png|600px]] === Colloction 接口 === 在Java 类库中,集合类的基本接口是Collection 接口。这个接口有两个基本方法 <syntaxhighlight lang="java"> public interface Collection<b { boolean add(E element); Iterator<E> iteratorQ; ... } </syntaxhighlight> # add 方法用于向集合中添加元素。如果添加元素确实改变了集合就返回true, 如果集合没有发生变化就返回false; # iterator 方法用于返回一个实现了Iterator 接口的对象; #: 可以使用这个迭代器对象依次访问集合中的元素。 === 迭代器 === '''[[关于Iterable与Iterator的那些事]]''' Iterator 接口包含4 个方法: <syntaxhighlight lang="java"> public interface Iterator<E> { E next(); // 逐个访问集合中的每个元素(如果到达了集合的末尾将抛出一个NoSuchElementException) boolean hasNext(); // 遍历时判断是否还有下一个元素 void remove(); // default void forEachRemaining(Consumer<? super E> action) ; // 对迭代器的每一个元素调用action(Consumer是一个函数式接口,所以可用lambda表达式) } </syntaxhighlight> 如果想要査看集合中的所有元素,就请求一个迭代器,并在hasNext 返回true 时反复地调用next 方法。例如: <syntaxhighlight lang="java"> Collection<String> c = . . .; Iterator<String> iter = c.iteratorO; while (iter.hasNextO) { String element = iter.next0; // do something with element } </syntaxhighlight> 用“ foreach” 循环可以更加简练地表示同样的循环操作: <syntaxhighlight lang="java"> for (String element : c) { // do something with element } </syntaxhighlight> * '''编译器简单地将“for each” 循环翻译为带有迭代器的循环''',“for each”循环可以与任何实现了Iterable 接口的对象一起工作; *: Collection 接口扩展了(而非继承)Iterable 接口。因此,对于标准类库中的任何集合都可以使用“for each” 循环。 * 在Java SE 8 中,甚至不用写循环。可以调用'''forEachRemaining'''方法并提供 lambda 表达式(它会处理一个元素)。 *: 将对迭代器的每一个元素调用这个lambda 表达式,直到再没有元素为止。 *: <syntaxhighlight lang="java"> i terator.forEachRemai ni ng(el ement -> do something with element); </syntaxhighlight> * '''元素被访问的顺序取决于集合类型'''。 *: 如对 ArrayList:迭代器将从索引 0 开始,每次迭代索引值加 1; *: 然而,如对 HashSet 迭代:每个元素将会按照某种随机的次序出现。(能够遍历到集合中的所有元素,但却无法预知元素被访问的次序) ==== 关于迭代器的位置 ==== '''应该将Java 迭代器认为是位于两个元素之间(而非指向一个元素)'''。当调用next 时,迭代器就越过下一个元素,并返回刚刚越过的那个元素的引用。<br/> [[File:迭代器的位置.png|400px]] * Iterator 接口的 '''remove 方法将会删除上次调用next 方法时返回的元素'''; 如: # 删除集合首个元素: #: <syntaxhighlight lang="java"> Iterator<String> it = c.iterator(); it.nextO; // skip over the first element it.remove(); // now remove it </syntaxhighlight> # 删除两个相邻的元素: #: <syntaxhighlight lang="java"> it.remove(); it.next0; it.remove(); // 而非 // it.remove(); // it.remove(); </syntaxhighlight> === 泛型使用方法 === * 由于Collection 与Iterator 都是'''泛型接口''',可以编写操作任何集合类型的实用方法。 * Java 类库提供了一个类AbstractCollection,它将基础方法size和iterator抽象化了,但是提供了例行方法。 *: 所以,一个具体的集合类可以扩展AbstractCollection 类了(实际上也是这样的) *: 然而,这种“伴随类”并不是最好的方式,在原接口中将方法定义为“默认方法”会更好。 ==== java.util.Collection<E> ==== # Iterator < E> iterator() #: 返回一个用于访问集合中每个元素的迭代器。 # int size() #: 返回当前存储在集合中的元素个数。 # boolean isEmpty() #: 如果集合中没有元素, 返回true。 # boolean contains(Object obj) #: 如果集合中包含了一个与obj 相等的对象, 返回true。 # boolean containsAl 1(Collection<?> other) #: 如果这个集合包含other 集合中的所有元素, 返回trueo # boolean add(Object element) #: 将一个元素添加到集合中。如果由于这个调用改变了集合,返回true。 # boolean addAl1(Col1ection<? extends E> other) #: 将other 集合中的所有元素添加到这个集合。如果由于这个调用改变了集合,返回true。 # boolean remove(Object obj) #: 从这个集合中删除等于obj 的对象。如果有匹配的对象被删除, 返回true。 # boolean removeAl 1(Col 1ection<?> other) #: 从这个集合中删除other 集合中存在的所有元素。如果由于这个调用改变了集合,返回true。 # default boolean removelf(Predicate<? super E> filter)8 #: 从这个集合删除filter 返回true 的所有元素。如果由于这个调用改变了集合,则返回true。 # void clear() #: 从这个集合中删除所有的元素。 # boolean retainAl1(Collection<?> other) #: 从这个集合中删除所有与other 集合中的元素不同的元素。如果由于这个调用改变了集合, 返回true。 # Object[]toArray() #: 返回这个集合的对象数组。 # <T> T[]toArray(T[]arrayToFi11) #: 返回这个集合的对象数组。如果arrayToFill 足够大,就将集合中的元素填入这个数组中。剩余空间填补null; 否则,分配一个新数组,其成员类型与arrayToFill的成员类型相同,其长度等于集合的大小,并填充集合元素。 ==== java.util.Iterator<E> ==== # boolean hasNext() #: 如果存在可访问的元素, 返回true。 # E next() #: 返回将要访问的下一个对象。如果已经到达了集合的尾部, 将拋出一个NoSuchElementException。 # void remove( ) #: 删除上次访问的对象。这个方法必须紧跟在访问一个元素之后执行。如果上次访问之后,集合已经发生了变化, 这个方法将抛出一个IllegalStateException。 === 集合框架中的接口 === [[File:集合框架的接口.png|800px]] # 集合有两个基本接口:Collection 和Map。 #: 可以用以下方法在集合中插人元素: #: <syntaxhighlight lang="java"> boolean add(E element) </syntaxhighlight> #: 不过,由于映射包含键/ 值对,所以要用put 方法来插人: #: <syntaxhighlight lang="java"> V put(K key, V value) </syntaxhighlight> #: 要从集合读取元素, 可以用迭代器访问元素。不过,从映射中读取值则要使用get 方法: #: <syntaxhighlight lang="java"> V get (K key) </syntaxhighlight> ==== List ==== List 是一个有序集合(ordered collection),元素会增加到容器中的特定位置。可以采用两种方式访问元素: # 迭代器访问 # 随机访问(random access) * List 接口定义了多个用于随机访问的方法: *: void add(int index, E element) *: void remove(int index) *: E get(int index) *: E set(int index, E element) ==== Set ==== Set 接口等同于Collection 接口,不过其方法的行为有更严谨的定义:集( set ) 的add 方法不允许增加重复的元素。 * 要适当地定义集的equals 方法:只要两个集包含同样的元素就认为是相等的,而不要求这些元素有同样的顺序。 * hashCode 方法的定义要保证包含相同元素的两个集会得到相同的散列码。 * '''SortedSet''' 和'''SortedMap'''接口会提供用于排序的比较器对象,这两个接口定义了可以得到集合子集视图的方法。 * Java SE 6 引人了接口'''NavigableSet'''和'''NavigableMap''',其中包含一些用于搜索和遍历有序集和映射的方法。 == 具体的集合 == {| class="wikitable" |+ Java 库中的具体集合 ! 集合类型 !! 描述 |- ! colspan="2"| AbstractCollection |- | ArrayList || 一种可以'''动态增长和缩减'''的索引序列 |- | LinkedList || 一种可以在任何位置进行'''高效地插人和删除'''操作的有序序列 |- | ArrayDeque || 一种用循环数组实现的'''双端队列''' |- | HashSet || 一种没有重复元素的'''无序集合''' |- | TreeSet || —种'''有序集''' |- | EnumSet || 一种包含'''枚举类型值的集''' |- | LinkedHashSet || 一种可以记住元素'''插人次序的集''' |- | PriorityQueue || 一种允许高效'''删除最小元素的集合''' |- ! colspan="2"| AbstractMap |- | HashMap || 一种存储'''键/值关联'''的数据结构 |- | TreeMap || —种键值'''有序排列的映射表''' |- | EnumMap || 一种键值属于'''枚举类型的映射表''' |- | LinkedHashMap || 一种可以记住键/值项'''添加次序的映射表''' |- | WeakHashMap || 一种其值无用武之地后可以被垃圾回收器回收的映射表 |- | IdentityHashMap || 一种用==而不是用equals比较键值的映射表 |} [[File:集合框架中的类.png|800px]] === 链表(LinkedList)=== * 链表不支持随机方法,但便于插入删除; * Java中的所有链表实际上都是双向链接的(双向链表); ==== 添加 ==== # LinkedList.add:将对象添加到链表的尾部; # ListIterator.add:将对象添加到当前位置; ==== 删除 ==== *(Java中删除链表元素不需要考虑内部指向,调用方法即可) # 在调用 next 之后 remove,会删除迭代器左侧的元素(类似键盘的“删除”) # 在调用 previous 之后 remove,删除迭代器右侧的元素(类似键盘的“DEL”) ==== ListItetator ==== * ListItetator:Iterator的子类,专门用于LinkedList的迭代; * 迭代器的位置,并不直接指向元素,而是在元素之间; * 迭代器的 add 方法只依赖于迭代器的位置,而 remove 方法依赖于迭代器的状态(调用了next还是previous); * 同一个LinkedList的多个迭代器之间操作冲突的话(一个修改了集合,一个正在读),会抛出“ConcurrentModificationException”异常。 *: 所以:可以更具需要给容器附加多个迭代器,但只有一个可以进行修改操作; * 尽量使用迭代器,而不使用 LinkedList.get方法来进行读取(效率太低) ==== 常用方法 ==== ===== java.util.List<E> ===== * ListIterator<E> 1 istIterator ( ) *: 返回一个列表迭代器, 以便用来访问列表中的元素。 * ListIterator <E> 1 istIterator( int index ) *: 返回一个列表迭代器, 以便用来访问列表中的元素, 这个元素是第一次调用next 返回的给定索引的元素。 * void add( int i , E element ) *: 在给定位置添加一个元素。 * void addAl 1 ( int i , Col 1ection<? extends E> elements ) *: 将某个集合中的所有元素添加到给定位置。 * E remove( int i ) *: 删除给定位置的元素并返回这个元素。 * E get ( int i ) *: 获取给定位置的元素。 * E set ( int i , E element ) *: 用新元素取代给定位置的元素, 并返回原来那个元素。 * int indexOf (Object element ) *: 返回与指定元素相等的元素在列表中第一次出现的位置, 如果没有这样的元素将返回-1。 * int 1 astlndexOf(Object element) *: 返回与指定元素相等的元素在列表中最后一次出现的位置, 如果没有这样的元素将返回-1。 ===== java.util.Listlterator<E> ===== * void add(E newElement) 在当前位置前添加一个元素。 * void set(E newElement) *: 用新元素取代next 或previous 上次访问的元素。如果在next 或previous 上次调用之后列表结构被修改了, 将拋出一个IllegalStateException 异常。 * boolean hasPrevious() *: 当反向迭代列表时, 还有可供访问的元素, 返回true。 * E previous() *: 返回前对象。如果已经到达了列表的头部, 謝te出一hNoSuchElementException 异常。 * int nextlndex() *: 返回下一次调用next 方法时将返回的元素索引。 * int previousIndex() *: 返回下一次调用previous 方法时将返回的元素索引。 ===== java.util.LinkedList<E> ===== * LinkedList() *: 构造一个空链表。 * LinkedList(Col 1ection<? extends E> elements) *: 构造一个链表, 并将集合中所有的元素添加到这个链表中。 * void addFirst(E element) * void addLast(E element) *: 将某个元素添加到列表的头部或尾部。 * E getFirst() * E getLast() *: 返回列表头部或尾部的元素。 * E removeFirst() * E removeLast() *: 删除并返回列表头部或尾部的元素。 === 数组列表(ArrayList)=== List的数组实现,支持随机读取,不便于插入删除(需要移动元素)。 ==== ArrayList 与 Vector ==== '''Vector 类的所有方法都是同步的''',可以由两个线程安全地访问一个 Vector 对象,但也比 ArrayList 更耗时; * Vector 的方法中,用到了'''synchronized'''来实现同步锁; === 散列集(HashSet)=== 利用对象的hashcode,来将对象存放在散列集的不同位置。 * HashSet 的迭代器将依次访问所有的桶。由于元素散列分布,所以迭代器访问元素是无序的; * 修改元素时,如果元素的散列码变化,则其在数据结构中的位置也会变化 HashSet的实现: * 由其构造函数可以看出,'''HashSet 是由 HashMap 来实现的''': *: <syntaxhighlight lang="java"> public HashSet() { map = new HashMap<>(); } public HashSet(Collection<? extends E> c) { map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16)); addAll(c); } public HashSet(int initialCapacity, float loadFactor) { map = new HashMap<>(initialCapacity, loadFactor); } public HashSet(int initialCapacity) { map = new HashMap<>(initialCapacity); } HashSet(int initialCapacity, float loadFactor, boolean dummy) { map = new LinkedHashMap<>(initialCapacity, loadFactor); } </syntaxhighlight> ==== 散列表 ==== 散列表,用于保存集合中对象的散列码。Java中,散列表用链表数组实现: :[[File:散列表.png|400px]] (每个列表被称为一个桶,bucket) 要想査找表中对象的位置,就要先计算它的散列码,然后与桶的总数取余,所得到的结果就是保存这个元素的桶的索引。<br/> : 例如, 如果某个对象的散列码为76268, 并且有128个桶, 对象应该保存在第108号桶中(76268除以128余108) ==== 散列冲突 ==== 插入对象时,遇到桶被占满的情况,即为散列冲突。<br/> 这时, 需要用新对象与桶中的所有对象进行比较, 査看这个对象是否已经存在。如果散列码是合理且随机分布的, 桶的数目也足够大, 需要比较的次数就会很少。 * 在JavaSE 8 中, 桶满时会从链表变为平衡二叉树(便于查找?)。 ==== 桶数 ==== 桶数,是指用于收集具有相同散列值的桶的数目。通常,将桶数设置为预计元素个数的75% ~ 150%。<br/> *标准类库使用的桶数是2 的幂, 默认值为16 ==== 再散列 ==== 如果散列表太满,就需要再散列(rehashed):创建一个桶数更多的表,并将所有元素插入到这个新表中,然后丢弃原来的表。 装填因子(load factor) 决定何时对散列表进行再散列。<br/> 例如, 如果装填因子为0.75 (默认值),而表中超过75%的位置已经填人元素, 这个表就会用双倍的桶数自动地进行再散列。 * 对于大多数应用程序来说, 装填因子为 0.75 是比较合理的; ==== 常用方法 ==== ===== jjava.util.HashSet<E> ===== * HashSet ( ) *: 构造一个空散列表。 * HashSet (Col 1ection<? extends E> elements ) *: 构造一个散列集, 并将集合中的所有元素添加到这个散列集中。 * HashSet ( int initialCapacity) *: 构造一个空的具有指定容量(桶数)的散列集。 * HashSet ( int initialCapacity , float 1oadFactor ) *: 构造一个具有指定容量和装填因子(一个0.0 ~ 1.0 之间的数值, 确定散列表填充的百分比, 当大于这个百分比时, 散列表进行再散列)的空散列集。 ===== java.Iang.Object ===== * int hashCode( ) *: 返回这个对象的散列码。散列码可以是任何整数,包括正数或负数。equals 和hashCode的定义必须兼容,即如果x.equals(y) 为true, x.hashCodeO 必须等于y.hashCodeO。 === 树集(TreeSet)=== 树集是一个有序集合(sorted collection)。元素的排序是用树结构完成的(当前使用的是'''[[红黑树]]'''); * '''使用树集,元素必须实现 Comparable 接口,或者构造集时必须提供一个 Comparator'''。 * Java6 之后,TreeSet类实现了 NavigableSet 接口。 *: 这个接口增加了几个便于定位元素以及反向遍历的方法。 每次将一个元素添加到树中时,都被放置在正确的排序位置上。因此: # 迭代器总是以排好序的顺序访问每个元素。 # 添加插入较HashSet慢,但查找更快。 # 如果树中包含 n 个元素,査找新元素的正确位置平均需要“log2 n”次比较。 TreeSet的实现: * 由其构造函数可以看出,'''TreeSet 是由 TreeMap 来实现的''': *: <syntaxhighlight lang="java"> public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>, Cloneable, java.io.Serializable { /** * The backing map. */ private transient NavigableMap<E,Object> m; // Dummy value to associate with an Object in the backing Map private static final Object PRESENT = new Object(); TreeSet(NavigableMap<E,Object> m) { this.m = m; } public TreeSet() { this(new TreeMap<E,Object>()); } public TreeSet(Comparator<? super E> comparator) { this(new TreeMap<>(comparator)); } public TreeSet(Collection<? extends E> c) { this(); addAll(c); } public TreeSet(SortedSet<E> s) { this(s.comparator()); addAll(s); } public boolean addAll(Collection<? extends E> c) { // Use linear-time version if applicable if (m.size()==0 && c.size() > 0 && c instanceof SortedSet && m instanceof TreeMap) { SortedSet<? extends E> set = (SortedSet<? extends E>) c; TreeMap<E,Object> map = (TreeMap<E, Object>) m; Comparator<?> cc = set.comparator(); Comparator<? super E> mc = map.comparator(); if (cc==mc || (cc != null && cc.equals(mc))) { map.addAllForTreeSet(set, PRESENT); return true; } } return super.addAll(c); } </syntaxhighlight> ==== 常用方法 ==== ===== java.util.TreeSet<E> ===== TreeSet() * TreeSet(Comparator<? super E> comparator) *:构造一个空树集。 * TreeSet(Col 1ection<? extends E> elements) * TreeSet(SortedSet<E> s) *:构造一个树集, 并增加一个集合或有序集中的所有元素(对于后一种情况,要使用同样的顺序)。 ===== java.util.SortedSet<E> ===== * Comparator<? super E> comparator() *:返回用于对元素进行排序的比较器。如果元素用Comparable 接口的compareTo方法进行比较则返回null。 * E firstO * E 1ast() *:返回有序集中的最小元素或最大元素。 ===== java.util.NavigableSet<E> ===== * E higher(E value) * E 1ower(E value) *:返回大于value 的最小元素或小于value 的最大元素,如果没有这样的元素则返回null。 * E cei1ing(E value) * E floor(E value) *:返回大于等于vaiue 的最小元素或小于等于value 的最大元素, 如果没有这样的元素则返回null。 * E pollFirst() * E pol1Last() *:删除并返回这个集中的最大元素或最小元素, 这个集为空时返回null。 * Iterator<E> descend *ngIterator() *:返回一个按照递减顺序遍历集中元素的迭代器。 === 队列(Queue)双端队列(Deque)=== 在Java SE 6 中引人了Deque 接口,'''ArrayDeque'''和'''LinkedList'''类实现了Deque接口。这两个类都提供了双端队列, 而且在必要时可以增加队列的长度。 <syntaxhighlight lang="java"> public interface Deque<E> extends Queue<E> { </syntaxhighlight> <syntaxhighlight lang="java"> public interface Queue<E> extends Collection<E> { </syntaxhighlight> * (在第14章将会看到有限队列和有限双端队列。) * (看看消息中间件的实现?) ==== 阻塞队列(BlockingQueue) ==== BlockingQueue<E> 接口扩展了 Queue<E>接口,用于阻塞队列的实现。 '''[[Java中的阻塞队列]]''' ==== 常用方法 ==== ===== java.utii.Queue<E> ===== * boolean add(E element ) * boolean offer(E element ) *:如果队列没有满,将给定的元素添加到这个双端队列的尾部并返回true。如果队列满了,第一个方法将拋出一个IllegalStateException, 而第二个方法返回false。 * E remove( ) * E poll ( ) *:假如队列不空, 删除并返回这个队列头部的元素。如果队列是空的,第一个方法抛出NoSuchElementException, 而第二个方法返回null。 * E elementC ) * E peek( ) *:如果队列不空,返回这个队列头部的元素, 但不删除。如果队列空,第一个方法将拋出一个NoSuchElementException, 而第二个方法返回null。 ===== java.util.Deque<E> ===== * void addFirst(E element ) * void addLast(E element ) * boolean offerFirst(E element ) * boolean offerLast( E element ) *:将给定的对象添加到双端队列的头部或尾部。如果队列满了,前面两个方法将拋出一个IllegalStateException,而后面两个方法返回false。 * E removeFirst( ) * E removeLast( ) * E pollFirstO * E pollLastO *:如果队列不空,删除并返回队列头部的元素。如果队列为空,前面两个方法将拋出一个NoSuchElementException, 而后面两个方法返回null。 * E getFirstO * E getLastO * E peekFirstO * E peekLast( ) *:如果队列非空,返回队列头部的元素, 但不删除。如果队列空,前面两个方法将拋出一个NoSuchElementException, 而后面两个方法返回null。 ===== java.util.ArrayDeque<E> ===== * ArrayDeque( ) * ArrayDeque( 1nt initialCapacity) *:用初始容量16 或给定的初始容量构造一个无限双端队列。 === 优先级队列(priority queue)=== 优先级队列(priority queue) 中的元素可以按照任意的顺序插人,却总是按照排序的顺序进行检索。 * '''无论何时调用remove 方法, 总会获得当前优先级队列中最小的元素'''。 * 优先级队列'''使用堆(heap),对树执行添加(add) 和删除(remore) 操作''',让最小的元素移动到根,而不必花费时间对元素进行排序。 *: 堆是一个可以自我调整的二叉树; * 与TreeSet—样,一个优先级队列既可以保存实现了Comparable接口的类对象,也可以保存在构造器中提供的Comparator对象。 * 使用优先级队列的典型示例是任务调度。 ==== 常用方法 ==== ===== java.util.PriorityQueue ===== * PriorityQueue() * PriorityQueue(int initialCapacity) *: 构造一个用于存放Comparable 对象的优先级队列。 * Pr1orityQueue(int initialCapacity, Comparator ? super E> c) *: 构造一个优先级队列, 并用指定的比较器对元素进行排序。 == 映射 == === 基本映射操作 === Java 类库为映射提供了两个通用的实现:HashMap 和TreeMap。这两个类都实现了Map 接口。 # 散列映射对键进行散列, # 树映射用键的整体顺序对元素进行排序, 并将其组织成搜索树。 * 散列或比较函数只能作用于键。与键关联的值不能进行散列或比较。 ==== 常用方法 ==== ===== java.util.Map<K, V> ===== * V get(Object key) *: 获取与键对应的值;返回与键对应的对象,如果在映射中没有这个对象则返回null。键可以为null。 * default V getOrDefault(Object key, V defaultValue) *: 获得与键关联的值; 返回与键关联的对象, 或者如果未在映射中找到这个键, 则返回defaultValue。 * V put(K key, V value) *: 将键与对应的值关系插入到映射中。如果这个键已经存在, 新的对象将取代与这个键对应的旧对象。这个方法将返回键对应的旧值。如果这个键以前没有出现过则返回null。键可以为null, 但值不能为null。 * void putAl 1(Map<? extends K , ? extends V> entries) *: 将给定映射中的所有条目添加到这个映射中。 * boolean containsKey(Object key) *: 如果在映射中已经有这个键, 返回true。 * boolean containsValue(Object value) *: 如果映射中已经有这个值, 返回true。 *default void forEach(BiConsumer<? super K ,? super V> action)8 *: 对这个映射中的所有键/ 值对应用这个动作。 ===== java.utii.HashMap<K,V> ===== *HashMap() *HashMap(int initialCapacity) *HashMap(int initialCapacity, float 1oadFactor) *: 用给定的容量和装填因子构造一个空散列映射(装填因子是一个0.0 〜1.0 之间的数值。这个数值决定散列表填充的百分比。一旦到了这个比例,就要将其再散列到更大的表中)。默认的装填因子是0.75。 ===== java.util.TreeMap<K,V> ===== *TreeMap() *: 为实现Comparable 接口的键构造一个空的树映射。 *TreeMap(Comparator<? super K> c) *: 构造一个树映射, 并使用一个指定的比较器对键进行排序。 *TreeMap(Map<? extends K, ? extends V> entries) *: 构造一个树映射, 并将某个映射中的所有条目添加到树映射中。 *TreeMap(SortedMap<? extends K, ? extends V> entries) *: 构造一个树映射, 将某个有序映射中的所有条目添加到树映射中, 并使用与给定的有序映射相同的比较器。 ===== java.util.SortedMap<K, V> ===== *Comparator< ? super K> comparator() *: 返回对键进行排序的比较器。如果键是用Comparable 接口的compareTo 方法进行比较的,返回null。 * K firstKeyO * K 1astKey() *: 返回映射中最小元素和最大元素。 === 更新映射项 === 以“使用一个映射统计一个单词在文件中出现的频度”为例: # 看到一个单词(word) 时,将计数器增1, 如下所示: #:<syntaxhighlight lang="java"> counts.put(word, counts.get(word)+ 1); </syntaxhighlight> #: 这是可以的, 不过有一种情况除外: 就是第一次看到word 时。在这种情况下,get 会返回null , 因此会出现一个NullPointerException 异常。 #: 作为一个简单的补救, 可以使用'''getOrDefault''' 方法: #:<syntaxhighlight lang="java"> counts,put(word, counts.getOrDefault(word, 0)+ 1); </syntaxhighlight> # 另一种方法是首先调用'''putlfAbsent''' 方法。只有当键原先存在时才会放入一个值。 #:<syntaxhighlight lang="java"> counts.putlfAbsent(word, 0); counts.put(word, counts.get(word)+ 1); // Now we know that get will succeed </syntaxhighlight> # 不过还可以使用 '''merge''' 方法简化这个常见的操作。如果键原先不存在,下面的调用: #:<syntaxhighlight lang="java"> counts.merge(word, 1, Integer::sum); </syntaxhighlight> #: 将把word 与1 关联,否则使用Integer::sum 函数组合原值和1 (也就是将原值与1 求和)。 === 映射视图(键集、键/值对集) === <pre> 集合框架不认为映射本身是一个集合。(其他数据结构框架认为映射是一个 键/值 对集合,或者是由键索引的值集合)。不过,可以得到映射的视图( View )——这是实现了Collection 接口或某个子接口的对象。 </pre> 三种视图如下: {| class="wikitable" ! 视图 !! 命令 !! 示例 |- | 键集 | <syntaxhighlight lang="java" inline>Set<K> keySet()</syntaxhighlight> | <syntaxhighlight lang="java"> Set<String> keys = map.keySet(); for (String key : keys) { // do something with key } </syntaxhighlight> |- | 值集合 | <syntaxhighlight lang="java" inline>Collection<V> values()</syntaxhighlight> *(不是一个集,因为可以重复) | <syntaxhighlight lang="java"> </syntaxhighlight> |- | 键/值对集 | <syntaxhighlight lang="java" inline>Set<Map.Entry<K, V>> entrySet()</syntaxhighlight> | <syntaxhighlight lang="java"> for (Map.Entry<String, Employee〉entry : staff.entrySet()) { String k = entry.getKeyO; Employee v = entry.getValueO; // do something with k, v } </syntaxhighlight> |} * 条目集的元素是实现Map.Entry 接口的类的对象。 * 如果在“键集”视图上调用迭代器的remove方法,会从映射中删除这个键和与它关联的值;如果试图调用add 方法, 它会抛出一个UnsupportedOperationException。 * 原先“键/值对集”是访问所有映射条目的最高效的方法。如今,只需要使用'''forEach'''方法(使用lambda表达式): <syntaxhighlight lang="java"> counts.forEach((k, v) -> { // do something with k, v }); </syntaxhighlight> ==== 常用方法 ==== ===== java.util.Map<K, V> ===== * Set<Map.Entry<K, V>> entrySet() *: 返回Map.Entry 对象(映射中的键/ 值对)的一个集视图。可以从这个集中删除元素,它们将从映射中删除,但是不能增加任何元素。 * Set<K> keySetO *: 返回映射中所有键的一个集视图。可以从这个集中删除元素,键和相关联的值将从映射中删除, 但是不能增加任何元素。 * Col 1ection<V> values() *: 返回映射中所有值的一个集合视图。可以从这个集合中删除元素, 所删除的值及相应的键将从映射中删除, 不过不能增加任何元素。 ===== java.util.Map.Entry<K, V> ===== * K getKey() * V getValueC) *: 返回这一条目的键或值。 * V setVa1 ue(V newValue) *: 将相关映射中的值改为新值, 并返回原来的值。 === 弱散列映射 === 【???】 === 链接散列集与映射(LinkedHashSet 与 LinkedHashMap) === <pre> LinkedHashSet 和 LinkedHashMap 类用来记住插人元素项的顺序。这样就可以避免在散列表中的项从表面上看是随机排列的。当条目插入到表中时,就会并人到双向链表中。 </pre> * LinkedHashSet 由 LinkedHashMap 实现; * LinkedHashMap在HashMap的基础上,'''采用双向链表(doubly-linked list)的形式将所有entry连接起来''',这样是为保证元素的迭代顺序跟插入顺序相同。 [[File:LinkedHashMap.png|600px]] 特点: * 散列表(HashMap)用于元素的存储结构; * '''双向链(List)表用于维护元素的迭代顺序,用于元素遍历'''(可以是插入顺序,也可以是访问顺序); ==== 关于“迭代顺序”:“插入顺序”“访问顺序” ==== 如上所述,迭代(遍历) LinkedHashMap 元素的时候,会按照链接顺序(即:迭代顺序)进行。<br/> 而迭代顺序: # 可以是“插入顺序”:按插入顺序维护链表; # 也可以是“访问顺序”:按访问顺序维护链表(即链表头元素是“最近最少使用”); 而这取决于其“'''accessOrder'''”值域: # 构造对象时,确定 accessOrder: #: <syntaxhighlight lang="java"> public LinkedHashMap(int initialCapacity, float loadFactor) { super(initialCapacity, loadFactor); accessOrder = false; } public LinkedHashMap(int initialCapacity) { super(initialCapacity); accessOrder = false; } public LinkedHashMap() { super(); accessOrder = false; } public LinkedHashMap(Map<? extends K, ? extends V> m) { super(); accessOrder = false; putMapEntries(m, false); } public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) { super(initialCapacity, loadFactor); this.accessOrder = accessOrder; } </syntaxhighlight> # 调用相关方法时,用accessOrder值判断,是否将被访问的元素放到链表末尾; #: <syntaxhighlight lang="java"> public V get(Object key) { Node<K,V> e; if ((e = getNode(hash(key), key)) == null) return null; if (accessOrder) afterNodeAccess(e); return e.value; } ... void afterNodeAccess(Node<K,V> e) { // move node to last LinkedHashMap.Entry<K,V> last; if (accessOrder && (last = tail) != e) { LinkedHashMap.Entry<K,V> p = (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after; p.after = null; if (b == null) head = a; else b.after = a; if (a != null) a.before = b; else last = b; if (last == null) head = p; else { p.before = last; last.after = p; } tail = p; ++modCount; } } </syntaxhighlight> === 枚举集与映射(EnumSet 与 EnumMap) === ==== EnumSet ==== <pre> EmimSet 是一个枚举类型元素集的高效实现。由于枚举类型只有有限个实例,所以EnumSet 内部用位序列实现。如果对应的值在集中,则相应的位被置为1。 </pre> * EnumSet 类没有公共的构造器。可以使用静态工厂方法构造这个集: *: <syntaxhighlight lang="java"> enum Weekday { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY }; EnumSet<Weekday> always = EnumSet.allOf(Weekday.class); EnumSet<Weekday> never = EnumSet.noneOf(Weekday.class); EnumSet<Weekday> workday = EnumSet.range(Weekday.MONDAY, Weekday.FRIDAY); EnumSet<Weekday> mwf = EnumSet.of(Weekday.MONDAY, Weekday.WEDNESDAY, Weekday.FRIDAY); </syntaxhighlight> * 可以使用Set 接口的常用方法来修改EnumSet。 ==== EnumMap ==== EnumMap 是一个'''键类型为枚举类型的映射'''。它可以直接且高效地用一个值数组实现。 * 在使用时, 需要在构造器中指定键类型: *: <syntaxhighlight lang="java"> EnumMap<Weekday, Employee〉personlnCharge = new EnumMap<>(Weekday.class); </syntaxhighlight> * 所有的枚举类型都扩展于泛型Enum 类。 *: 例如,Weekday 扩展于 Enum<Weekday〉。 === 标识散列映射(IdentityHashMap) === <pre> 类 IdentityHashMap 有特殊的作用。在这个类中, 键的散列值不是用hashCode 函数计算的, 而是用System.identityHashCode 方法计算的。这是Object.hashCode 方法根据对象的内存地址来计算散列码时所使用的方式。而且, 在对两个对象进行比较时,IdentityHashMap 类使用==, 而不使用equals。 也就是说,不同的键对象,即使内容相同,也被视为是不同的对象。在实现对象遍历算法(如对象串行化)时,这个类非常有用,可以用来跟踪每个对象的遍历状况。 </pre> 特点: # 使用“'''System.identityHashCode'''”方法计算其 hashcode(即,使用对象内存地址计算 hashcode); # 使用“==”来比较对象; 即,每个对象 hashcode (几乎)唯一,(几乎)不会出现冲突。<br/> 用法??? == 视图与包装器 == <pre> 通过使用视图(views)可以获得其他的实现了Collection 接口和Map 接口的对象。 </pre> 以“HashMap<K,V>”为例: <syntaxhighlight lang="java"> public Set<K> keySet() { Set<K> ks = keySet; if (ks == null) { ks = new KeySet(); keySet = ks; } return ks; } final class KeySet extends AbstractSet<K> { public final int size() { return size; } public final void clear() { HashMap.this.clear(); } public final Iterator<K> iterator() { return new KeyIterator(); } public final boolean contains(Object o) { return containsKey(o); } public final boolean remove(Object key) { return removeNode(hash(key), key, null, false, true) != null; } public final Spliterator<K> spliterator() { return new KeySpliterator<>(HashMap.this, 0, -1, 0, 0); } public final void forEach(Consumer<? super K> action) { Node<K,V>[] tab; if (action == null) throw new NullPointerException(); if (size > 0 && (tab = table) != null) { int mc = modCount; for (int i = 0; i < tab.length; ++i) { for (Node<K,V> e = tab[i]; e != null; e = e.next) action.accept(e.key); } if (modCount != mc) throw new ConcurrentModificationException(); } } } </syntaxhighlight> “keySet()”方法返回了一个实现了“Set<K>”接口的类(“KeySet”)的对象,这个类(“KeySet”)的方法可以对原HashMap进行操作,这个集合(“KeySet”)就是视图,这个对象(“ks”)就是视图对象。<br/> '''视图可以理解为一个受限制的(非原集合的)集合。(类比于数据库视图)''' === 轻量级集合包装器 === 在Arrays类中有一个静态方法--asList方法,这个方法作用是:将普通的Java数组包装成一个List集合。<br/> 例如:<syntaxhighlight lang="java"> String []strings = new String[10]; strings[0] = "pby"; strings[1] = "pby1"; strings[2] = "pby2"; List<String> stringList = Arrays.asList(strings); </syntaxhighlight> 返回的对象不是一个ArrayList对象。它就是一个视图对象: # 这个对象带有底层数组的get和set方法。 # 视图对象不能操作所有改变数组大小的方法: #: 在调用这些方法的时候,程序会抛出一个UnsupportedOperationException异常;但是普通的List对象能够正常的调用改变数组大小的方法。 与Arrays.asList方法类似的另一个方法那就是在Collection中的nCopies方法。例如: <syntaxhighlight lang="java"> List<String> stringList = Collections.nCopies(100, "pby"); </syntaxhighlight> 上面的代码将创建一个包含100个"pby"字符串的List集合对象。'''这样的操作优势在于存储代价很小,因为这个对象不能修改大小'''。 * 区分Collections类和Collection接口:'''Collections类包含了很多实用的方法,这些方法的参数和返回值都是集合'''; *:【如“'''synchronizedCollection(Collection<T> c)'''”可用于:用普通集合实现一个同步集合】; === 子范围 === 可以给很多的集合建立子范围视图。<br/> 例如,假设有一个集合对象list,要从中取出第10个~第19个元素,就可以使用subList方法来获得一个List集合对象的子范围视图: <syntaxhighlight lang="java"> List<String> list2 = list.subList(10, 19); </syntaxhighlight> * Java SE 6 引人的'''NavigableSet'''接口赋予子范围操作更多的控制能力。 === 不可修改的视图 === Collections还有几个方法,用于产生集合的不可修改视图。这些视图对现有的集合增加了一个运行时的检查。如果发现对集合进行修改的话(这里不仅仅是改变数组的大小,并且包括set之类的方法),就会抛出一个异常,同时这个集合将保持未修改的状态。<br/> 可以使用如下8种方法来获得不可修改的视图: # Collections.unmodifiableCollection # Collections.unmodifiableList # Collections.unmodifiableSet # Collections.unmodifiableSortedSet # Collections.unmodifiableNavigableSet # Collections.unmodifiableMap # Collections.unmodifiableSortedMap # Collections.unmodifiableNavigableMap 每个方法都定义于一个接口。例如,Collections.unmodifiableList方法定义于List接口,与ArrayList、LinkedList或者任何实现了List接口的其他类一起协同工作。 * 由于视图只是包装了接口而不是实际的集合对象, 所以只能访问接口中定义的方法。 <pre> unmodifiableCollection 方法(与本节稍后讨论的synchronizedCollection 和checkedCollection 方法一样)将返回一个集合, 它的equals 方法不调用底层集合的equals 方法。相反, 它继承了Object 类的equals 方法, 这个方法只是检测两个对象是否是同一个对象。如果将集或列表转换成集合, 就再也无法检测其内容是否相同了。视图就是以这种方式运行的, 因为内容是否相等的检测在分层结构的这一层上没有定义妥当。视图将以同样的方式处理hashCode 方法。 然而,unmodifiableSet 类和unmodifiableList 类却使用底层集合的equals 方法和hashCode 方法。 </pre> === 同步视图 === 类库的设计者使用视图机制来确保常规集合的线程安全, 而不是实现线程安全的集合类。<br/> 例如, Collections 类的静态'''synchronizedMap'''方法可以将任何一个映射表转换成具有同步访问方法的Map: <syntaxhighlight lang="java"> Map<String, Employee〉map = Collections.synchronizedMap(new HashMap<String, Employee>0); </syntaxhighlight> === 受查视图 === 受査” 视图用来对泛型类型发生问题时提供调试支持。 下面定义了一个安全列表“safestrings”: <syntaxhighlight lang="java"> List<String> safestrings = Collections.checkedList(strings,String,class); ArrayList rawList = safestrings; rawList.add(new DateO);// checked list throws a ClassCastException </syntaxhighlight> 视图的 add 方法将检测插人的对象是否属于给定的类。如果不属于给定的类, 就立即抛出一个“ClassCastException”。这样做的好处是错误可以在正确的位置得以报告: === 关于可选操作的说明 === == 算法 == 泛型集合接口有一个很大的优点, 即算法只需要实现一次。 === 排序与混排 === Collections 类中的sort 方法可以对实现了List 接口的集合进行排序。 # 这个方法假定列表元素实现了Comparable 接口。 # 如果想采用其他方式对列表进行排序,可以使用List接口的sort方法并传入一个Comparator 对象。 sort方法对列表所采用的排序手段:将所有元素转人一个数组,对数组进行排序,然后,再将排序后的序列复制回列表。【???】 [[常用排序算法]] Collections 类有一个算法shuffle, 其功能与排序刚好相反, 即随机地混排列表中元素的顺序。 ==== 常用方法 ==== ===== java.util.CoIiections ===== * static <T extends Comparable<? super T» void sort( List<T> elements ) *: 使用稳定的排序算法, 对列表中的元素进行排序。这个算法的时间复杂度是0(n logn), 其中n 为列表的长度。 * static void shuff1e( List<?> elements ) * static void shuff1e( List <?> elements , Random r ) *: 随机地打乱列表中的元素。这个算法的时间复杂度是0(n a(n)), n 是列表的长度,a(n)是访问元素的平均时间。 ===== java.util.List<E> ===== * default void sort(Comparator<? super T> comparator ) *: 使用给定比较器对列表排序。 ===== java.utii.Comparator<T> ===== * static <T extends Comparable<? super T» Comparator<T> reverseOrder( ) *: 生成一个比较器, 将逆置Comparable 接口提供的顺序。【?】 * default Comparator<T> reversed( ) *: 生成一个比较器, 将逆置这个比较器提供的顺序。【?】 === 二分查找 === 使用二分查找,集合必须是已排好序的。 ==== 常用方法 ==== ===== java.util.Collections ===== * static <T extends Comparable<? super T» int binarySearch( List<T>elements , T key) * static <T> int binarySearch(List<T> elements, T key, Conparator<? super T> c) *: 从有序列表中搜索一个键, 如果元素扩展了AbstractSequentialList 类, 则采用线性查找,否则将采用二分查找。这个方法的时间复杂度为0 (a(n) logn), n 是列表的长度,a(n) 是访问一个元素的平均时间。这个方法将返回这个键在列表中的索引, 如果在列表中不存在这个键将返回负值i。 在这种情况下,应该将这个键插人到列表索引一i— l的位置上,以保持列表的有序性。 === 简单算法 === === 批操作 === === 集合与数组的转换 === == 遗留的集合 ==
返回至“
核心技术:集合
”。
导航菜单
个人工具
登录
命名空间
页面
讨论
大陆简体
已展开
已折叠
查看
阅读
查看源代码
查看历史
更多
已展开
已折叠
搜索
导航
首页
最近更改
随机页面
MediaWiki帮助
笔记
服务器
数据库
后端
前端
工具
《To do list》
日常
阅读
电影
摄影
其他
Software
Windows
WIKIOE
所有分类
所有页面
侧边栏
站点日志
工具
链入页面
相关更改
特殊页面
页面信息