您的位置:首页 > 产品设计 > UI/UE

Java集合框架官方教程(4):Set/List/Map/Queue/Deque实现

2016-07-29 00:00 531 查看

Lesson: Implementations

Implementations are the data objects used to store collections, which implement the interfaces described inthe Interfaces section. This lesson describes the following kinds of implementations:General-purpose implementations are the most commonly used implementations, designed for everyday use. They are summarized in the table titled General-purpose-implementations.Special-purpose implementations are designed for use in special situations and display nonstandard performance characteristics, usage restrictions, or behavior.Concurrent implementations are designed to support high concurrency, typically at the expense of single-threaded performance. These implementations are part of the
java.util.concurrent
package.Wrapper implementations are used in combination with other types of implementations, often the general-purpose ones, to provide added or restricted functionality.Convenience implementations are mini-implementations, typically made available via static factory methods, that provide convenient, efficient alternatives to general-purpose implementations for special collections (for example, singleton sets).Abstract implementations are skeletal implementations that facilitate the construction of custom implementations — described later in theCustom Collection Implementations section. An advanced topic, it's not particularly difficult, but relatively few people will need to do it.The general-purpose implementations are summarized in the following table.General-purpose Implementations
InterfacesHash table ImplementationsResizable array ImplementationsTree ImplementationsLinked list ImplementationsHash table + Linked list Implementations
Set
HashSet
TreeSet
LinkedHashSet
List
ArrayList
LinkedList
Queue
Deque
ArrayDeque
LinkedList
Map
HashMap
TreeMap
LinkedHashMap
As you can see from the table, the Java Collections Framework provides several general-purpose implementations of the
Set
,
List
, and
Map
interfaces. In each case, one implementation —
HashSet
,
ArrayList
, and
HashMap
— is clearly the one to use for most applications, all other things being equal.
Note that the
SortedSet
and the
SortedMap
interfaces do not have rows in the table. Each of those interfaces has one implementation(
TreeSet
and
TreeMap
) and is listed in the
Set
and the
Map
rows. There are two general-purpose
Queue
implementations —
LinkedList
, which is also a
List
implementation, and
PriorityQueue
, which is omitted from the table. These two implementations provide very different semantics:
LinkedList
provides FIFO semantics, while
PriorityQueue
orders its elements according to their values.Each of the general-purpose implementations provides all optional operations contained in its interface. All permit
null
elements, keys, and values. None are synchronized (thread-safe). All havefail-fast iterators
, which detect illegal concurrent modification during iteration and fail quickly and cleanly rather than risking arbitrary, nondeterministic behavior at an undetermined time in the future.All are
Serializable
and all support a public
clone
method.
The fact that these implementations are unsynchronized represents a break with the past: The legacy collections
Vector
and
Hashtable
are synchronized. The present approach was taken because collections are frequently used when the synchronization is of no benefit. Such uses include single-threaded use, read-only use, and use as part of a larger data object that does its own synchronization. In general, it is good API design practice not to make users pay for a feature they don't use. Furthermore, unnecessary synchronization can result in deadlock under certain circumstances.If you need thread-safe collections, the synchronization wrappers, described in theWrapper Implementations section, allow any collection to be transformed into a synchronized collection. Thus, synchronization is optional for general-purpose implementations, whereas it is mandatory for legacy implementations. Moreover, the
java.util.concurrent
package provides concurrent implementations of the
BlockingQueue
interface, which extends
Queue
, and of the
ConcurrentMap
interface, which extends
Map
. These implementations offer much higher concurrency than mere synchronized implementations.
As a rule, you should be thinking about the interfaces, not the implementations. That is why there are no programming examples in this section. For the most part, the choice of implementation affects only performance. The preferred style, as mentioned in the Interfaces section, is to choose an implementation when a
Collection
is created and to immediately assign the new collection to a variable of the corresponding interface type (or to pass the collection to a method expecting an argument of the interface type). In this way, the program does not become dependent on any added methods in a given implementation, leaving the programmer free to change implementations anytime that it is warranted by performance concerns or behavioral details.The sections that follow briefly discuss the implementations. The performance of the implementations is described using words such asconstant-time,log, linear, n log(n), and quadratic to refer to the asymptotic upper-bound on the time complexity of performing the operation. All this is quite a mouthful, and it doesn't matter much if you don't know what it means. If you're interested in knowing more, refer to any good algorithms textbook. One thing to keep in mind is that this sort of performance metric has its limitations. Sometimes, the nominally slower implementation may be faster. When in doubt, measure the performance!

Set Implementations

The
Set
implementations are grouped into general-purpose and special-purpose implementations.

General-Purpose Set Implementations

There are three general-purpose
Set
implementations —
HashSet
,
TreeSet
, and
LinkedHashSet
. Which of these three to use is generally straightforward.
HashSet
is much faster than
TreeSet
(constant-time versus log-time for most operations) but offers no ordering guarantees.
If you need to use the operations in the
SortedSet
interface, or if value-ordered iteration is required, use
TreeSet
; otherwise, use
HashSet
. It's a fair bet that you'll end up using
HashSet
most of the time.
LinkedHashSet
is in some sense intermediate between
HashSet
and
TreeSet
. Implemented as a hash table with a linked list running through it, it providesinsertion-ordered iteration (least recently inserted to most recently) and runs nearly as fast as
HashSet
. The
LinkedHashSet
implementation spares its clients from the unspecified, generally chaotic ordering provided by
HashSet
without incurring the increased cost associated with
TreeSet
.One thing worth keeping in mind about
HashSet
is that iteration is linear in the sum of the number of entries and the number of buckets (thecapacity).
Thus, choosing an initial capacity that's too high can waste both space and time. On the other hand, choosing an initial capacity that's too low wastes time by copying the data structure each time it's forced to increase its capacity. If you don't specify an initial capacity, the default is 16. In the past, there was some advantage to choosing a prime number as the initial capacity. This is no longer true.Internally, the capacity is always rounded up to a power of two. The initial capacity is specified by using the
int
constructor. The following line of code allocates a
HashSet
whose initial capacity is 64.
Set<String> s = newHashSet<String>(64);
The
HashSet
class has one other tuning parameter called the load factor. If you care a lot about the space consumption of your
HashSet
, read the
HashSet
documentation for more information. Otherwise, just accept the default; it's almost always the right thing to do.If you accept the default load factor but want to specify an initial capacity, pick a number that's about twice the size to which you expect the set to grow. If your guess is way off, you may waste a bit of space, time, or both, but it's unlikely to be a big problem.
LinkedHashSet
has the same tuning parameters as
HashSet
, but iteration time is not affected by capacity.
TreeSet
has no tuning parameters.

Special-Purpose Set Implementations

There are two special-purpose
Set
implementations —
EnumSet
and
CopyOnWriteArraySet
.
EnumSet
is a high-performance
Set
implementation for enum types. All of the members of an enum set must be of the same enum type. Internally, it is represented by a bit-vector, typically a single
long
.
Enum sets support iteration over ranges of enum types. For example, given the enum declaration for the days of the week, you can iterate over the weekdays. The
EnumSet
class provides a static factory that makes it easy.
for (Day d : EnumSet.range(Day.MONDAY, Day.FRIDAY))
System.out.println(d);
Enum sets also provide a rich, typesafe replacement for traditional bit flags.
EnumSet.of(Style.BOLD, Style.ITALIC)
CopyOnWriteArraySet
is a
Set
implementation backed up by a copy-on-write array. All mutative operations, such as
add
,
set
, and
remove
, are implemented by making a new copy of the array; no locking is ever required. Even iteration may safely proceed concurrently with element insertion and deletion. Unlike most
Set
implementations, the
add
,
remove
, and
contains
methods require time proportional to the size of the set.This implementation is only appropriate for sets that are rarely modified but frequently iterated. It is well suited to maintaining event-handler lists that must prevent duplicates.

List Implementations

List
implementations are grouped into general-purpose and special-purpose implementations.

General-Purpose List Implementations

There are two general-purpose
List
implementations —
ArrayList
and
LinkedList
. Most of the time, you'll probably use
ArrayList
, which offers constant-time positional access and is just plain fast.
It does not have to allocate a node object for each element in the
List
, and it can take advantage of
System.arraycopy
when it has to move multiple elements at the same time. Think of
ArrayList
as
Vector
without the synchronization overhead.If you frequently add elements to the beginning of the
List
or iterate over the
List
to delete elements from its interior, you should consider using
LinkedList
. These operations require constant-time in a
LinkedList
and linear-time in an
ArrayList
. But you pay a big price in performance. Positional access requires linear-time in a
LinkedList
and constant-time in an
ArrayList
. Furthermore, the constant factor for
LinkedList
is much worse. If you think you want to use a
LinkedList
, measure the performance of your application with both
LinkedList
and
ArrayList
before making your choice;
ArrayList
is usually faster.
ArrayList
has one tuning parameter — the initial capacity, which refers to the number of elements the
ArrayList
can hold before it has to grow.
LinkedList
has no tuning parameters and seven optional operations, one of which is
clone
. The other six are
addFirst
,
getFirst
,
removeFirst
,
addLast
,
getLast
, and
removeLast
.
LinkedList
also implements the
Queue
interface.

Special-Purpose List Implementations

CopyOnWriteArrayList
is a
List
implementation backed up by a copy-on-write array. This implementation is similar in nature to
CopyOnWriteArraySet
.No synchronization is necessary, even during iteration, and iterators are guaranteed never to throw
ConcurrentModificationException
.
This implementation is well suited to maintaining event-handler lists, in which change is infrequent, and traversal is frequent and potentially time-consuming.If you need synchronization, a
Vector
will be slightly faster than an
ArrayList
synchronized with
Collections.synchronizedList
. But
Vector
has loads of legacy operations, so be careful to always manipulate the
Vector
with the
List
interface or else you won't be able to replace the implementation at a later time.If your
List
is fixed in size — that is, you'll never use
remove
,
add
, or any of the bulk operations other than
containsAll
— you have a third option that's definitely worth considering. See
Arrays.asList
in theConvenience Implementations section for more information.

Map Implementations

Map
implementations are grouped into general-purpose, special-purpose, and concurrent implementations.

General-Purpose Map Implementations

The three general-purpose
Map
implementations are
HashMap
,
TreeMap
and
LinkedHashMap
. If you need
SortedMap
operations or key-ordered
Collection
-view iteration, use
TreeMap
; if you want maximum speed and don't care about iteration order, use
HashMap
; if you want near-
HashMap
performance and insertion-order iteration, use
LinkedHashMap
. In this respect, the situation for
Map
is analogous to
Set
. Likewise, everything else in theSet Implementations section also applies to
Map
implementations.
LinkedHashMap
provides two capabilities that are not available with
LinkedHashSet
. When you create a
LinkedHashMap
, you can order it based on key access rather than insertion. In other words, merely looking up the value associated with a key brings that key to the end of the map. Also,
LinkedHashMap
provides the
removeEldestEntry
method, which may be overridden to impose a policy for removing stale mappings automatically when new mappings are added to the map. This makes it very easy to implement a custom cache.
For example, this override will allow the map to grow up to as many as 100 entries and then it will delete the eldest entry each time a new entry is added, maintaining a steady state of 100 entries.
private static final int MAX_ENTRIES = 100;protected booleanremoveEldestEntry(Map.Entry eldest) {return size() > MAX_ENTRIES;}

Special-Purpose Map Implementations

There are three special-purpose Map implementations —
EnumMap
,
WeakHashMap
and
IdentityHashMap
.
EnumMap
, which is internally implemented as an
array
, is a high-performance
Map
implementation for use with enum keys. This implementation combines the richness and safety of the
Map
interface with a speed approaching that of an array.If you want to map an enum to a value, you should always use an
EnumMap
in preference to an array.
WeakHashMap
is an implementation of the
Map
interface that stores only weak references to its keys. Storing only weak references allows a key-value pair to be garbage-collected when its key is no longer referenced outside of the
WeakHashMap
. This class provides the easiest way to harness the power of weak references.It is useful for implementing "registry-like" data structures, where the utility of an entry vanishes when its key is no longer reachable by any thread.
IdentityHashMap
is an identity-based
Map
implementation based on a hash table.This class is useful for topology-preserving object graph transformations, such as serialization or deep-copying. To perform such transformations, you need to maintain an identity-based "node table" that keeps track of which objects have already been seen.Identity-based maps are also used to maintain object-to-meta-information mappings in dynamic debuggers and similar systems. Finally, identity-based maps are useful in thwarting "spoof attacks" that are a result of intentionally perverse
equals
methods because
IdentityHashMap
never invokes the
equals
method on its keys. An added benefit of this implementation is that it is fast.

Concurrent Map Implementations

The
java.util.concurrent
package contains the
ConcurrentMap
interface, which extends
Map
with atomic
putIfAbsent
,
remove
, and
replace
methods, and the
ConcurrentHashMap
implementation of that interface.
ConcurrentHashMap
is a highly concurrent, high-performance implementation backed up by a hash table.This implementation never blocks when performing retrievals and allows the client to select the concurrency level for updates. It is intended as a drop-in replacement for
Hashtable
: in addition to implementing
ConcurrentMap
, it supports all the legacy methods peculiar to
Hashtable
. Again, if you don't need the legacy operations, be careful to manipulate it with the
ConcurrentMap
interface.

Queue Implementations

The
Queue
implementations are grouped into general-purpose and concurrent implementations.

General-Purpose Queue Implementations

As mentioned in the previous section,
LinkedList
implements the
Queue
interface, providing first in, first out (FIFO) queue operations for
add
,
poll
, and so on.The
PriorityQueue
class is a priority queue based on the heap data structure.
This queue orders elements according to the order specified at construction time, which can be the elements' natural ordering or the ordering imposed by an explicit
Comparator
.The queue retrieval operations —
poll
,
remove
,
peek
, and
element
— access the element at the head of the queue. Thehead of the queue is the least element with respect to the specified ordering. If multiple elements are tied for least value, the head is one of those elements; ties are broken arbitrarily.
PriorityQueue
and its iterator implement all of the optional methods of the
Collection
and
Iterator
interfaces. The iterator provided in method
iterator
is not guaranteed to traverse the elements of the
PriorityQueue
in any particular order. For ordered traversal, consider using
Arrays.sort(pq.toArray())
.

Concurrent Queue Implementations

The
java.util.concurrent
package contains a set of synchronized
Queue
interfaces and classes.
BlockingQueue
extends
Queue
with operations that wait for the queue to become nonempty when retrieving an element and for space to become available in the queue when storing an element. This interface is implemented by the following classes:
LinkedBlockingQueue
— an optionally bounded FIFO blocking queue backed by linked nodes
ArrayBlockingQueue
— a bounded FIFO blocking queue backed by an array
PriorityBlockingQueue
— an unbounded blocking priority queue backed by a heap
DelayQueue
— a time-based scheduling queue backed by a heap
SynchronousQueue
— a simple rendezvous mechanism that uses the
BlockingQueue
interfaceIn JDK 7,
TransferQueue
is a specialized
BlockingQueue
in which code that adds an element to the queue has the option of waiting (blocking) for code in another thread to retrieve the element.
TransferQueue
has a single implementation:
LinkedTransferQueue
— an unbounded
TransferQueue
based on linked nodes

Deque Implementations

The
Deque
interface, pronounced as "deck", represents a double-ended queue. The
Deque
interface can be implemented as various types of
Collections
. The
Deque
interface implementations are grouped into general-purpose and concurrent implementations.

General-Purpose Deque Implementations

The general-purpose implementations include
LinkedList
and
ArrayDeque
classes. The
Deque
interface supports insertion, removal and retrieval of elements at both ends. The
ArrayDeque
class is the resizable array implementation of the
Deque
interface, whereas the
LinkedList
class is the list implementation.The basic insertion, removal and retieval operations in the
Deque
interface
addFirst
,
addLast
,
removeFirst
,
removeLast
,
getFirst
and
getLast
. The method
addFirst
adds an element at the head whereas
addLast
adds an element at the tail of the
Deque
instance.The
LinkedList
implementation is more flexible than the
ArrayDeque
implementation.
LinkedList
implements all optional list operations.
null
elements are allowed in the
LinkedList
implementation but not in the
ArrayDeque
implementation.
In terms of efficiency,
ArrayDeque
is more efficient than the
LinkedList
for add andremove operation at both ends.
The best operation in a
LinkedList
implementation is removing the current element during the iteration.
LinkedList
implementations are not ideal structures to iterate.The
LinkedList
implementation consumes more memory than the
ArrayDeque
implementation. For the
ArrayDeque
instance traversal use any of the following:

foreach

The
foreach
is fast and can be used for all kinds of lists.
ArrayDeque<String> aDeque = newArrayDeque<String>();. . .for (String str : aDeque) {System.out.println(str);}

Iterator

The
Iterator
can be used for the forward traversal on all kinds of lists for all kinds of data.
ArrayDeque<String> aDeque = newArrayDeque<String>();. . .for (Iterator<String> iter = aDeque.iterator(); iter.hasNext();  ) {System.out.println(iter.next());}
The
ArrayDeque
class is used in this tutorial to implement the
Deque
interface. The complete code of the example used in this tutorial is available in
ArrayDequeSample
.Both the
LinkedList
and
ArrayDeque
classes do not support concurrent access by multiple threads.

Concurrent Deque Implementations

The
LinkedBlockingDeque
class is the concurrent implementation of the
Deque
interface. If the deque is empty then methods such as
takeFirst
and
takeLast
wait until the element becomes available, and then retrieves andremoves the same element.Original: http://docs.oracle.com/javase/tutorial/collections/implementations/index.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: