笔记:集合
Java集合类库将接口(interface)和实现(implementation)分离,首先对集合接口进行说明。
- 集合接口
集合类的基本接口是 Collection 接口,该接口有两个基本方法:
public interface Collection<E>{
boolean add(E element);
Iterator<E> iterator();
…
}
add方法用于向集合增加元素,如果增加元素改变了集合则返回 true,集合没有发生变化则返回false;iterator方法用于返回一个实现类 Iterator 接口的对象,这个迭代器对象依次访问集合中的元素,接口定义如下:
public interface Iterator<E>{
E next();
boolean hasNext();
void remove();
}
next方法用于逐个访问集合的元素,如果到达集合末尾则会抛出 NoSuchElementException 异常,因此需要通过 hasNext方法来判断是否还有元素,也可以使用 for(String ele : iterator) 来循环访问元素;remove方法将删除上次调用next方法时返回的元素,如果需要删除指定位置的元素,需要越过这个元素。
对于链表,Java集合库增加了ListIterator接口,该接口是 Iterator 的子接口,这个接口额外增加了反向遍历的previous 和 hasPrevious 方法,还增加了 add 方法,这个add方法不反回boolean类型的值,他假定添加操作总会改变链表,接口定义如下:
public interface ListIterator<E> extends Iterator<E> {
boolean hasNext();
E next();
boolean hasPrevious();
E previous();
int nextIndex();
int previousIndex();
void remove();
void set(E e);
void add(E e);
}
如果某个迭代器修改集合时,另一个迭代器对其进行遍历,一定会出现混乱的情况,例如,一个迭代器指向另一个迭代器刚刚删除的元素前面,现在这个迭代器就是无效的,并且不应该被使用,如果迭代器发现他的集合时被另一个迭代器修改了,或者集合本身被修改了,就会抛出一个 ConcurrentModificationException 异常。
- 队列(queue)
队列接口是指可以在队列的尾部增加元素,在队列的头部删除元素,并且可以查找队列中的元素个数,如果需要按照先进先出的规则检索对象,就应该使用队列,队列的接口定义如下:
interface Queue<E> extends Collection<E> {
boolean add(E element);
E remove();
E poll();
int size();
E element();
E peek();
…
}
实现队列的数据结构类有 ArrayDeque(循环数组队列)、LinkedList(链表队列)和 PriorityQueue(优先级队列)
- PriorityQueue(优先级队列)
优先级队列中地元素可以按照任意地顺序插入,却总是按照排序地顺序进行检索,也就是所无论何时调用 remove方法,总会获得当前优先级队列中最小地元素,优先级队列并没有对所有地元素进行排序,而是使用迭代地方式处理,优先级队列使用堆(heap)数据结构,堆是一个可以自我调整地二叉树,对数执行添加和删除操作,可以让最小地元素移动到根,而不必花费时间对元素进行排序。
- 对象比较
PriorityQueue使用Comparable接口来比较对象进行排序地,接口定义如下:
public interface Comparable<T>{
int compareTo(T other);
}
如果a和b相等,调用 a.compareTo(b)一定返回0;如果排序后a位于b之前,则返回负值;如果a位于b之后,则返回正值。
除了实现 Comparable接口外,还可以将 Comparator 接口对象传递给 PriorityQueue构造器来告诉树集使用不同地比较方法,Comparator接口定义如下:
public interface Comparator<T>{
int compare(T a,T b);
}
compare方法和 compareTo方法地含义一样
- 链表(LinkedList)
链表是将每个对象存放在独立的节点中,每个节点存放着指向序列中下一个节点的引用,在Java集合库中所有的链表实际上都是双向链表结构,每个节点存放着向前和下一个节点的引用,链表的接口定义如下:
interface List<E> extends Collection<E>{
boolean add(E element);
void add(int index, E element);
boolean remove(Object obj);
E remove(int index);
int size();
ListIterator<E> listIterator();
…
}
链表是有序集合,add方法是将元素增加到链表的尾部,如果需要将元素增加到链表的中间,可以使用add的带index的重载。
实现链表的数据结构类有 ArrayList(数组链表)、LinkedList(双向链表)
- 散列集(Hashtable)
散列集可以快速地查找需要地对象,散列表为每个对象计算一个整数,称为散列码,散列码时由对象地实例域产生地一个整数,是通过对象地 hashCode 方法产生地,如果是自定义类就需要负责实现类地 hashCode方法,并且还需要与 equals 方法兼容,即如果a.equals(b)为true,则a与b必须具有相同地散列码。
实现散列集地数据结构类由 HashSet(散列集合)、HashMap(散列映射)
- 树集(TreeSet)
TreeSet类与散列集类似,不过,树集比散列集有所改进,树集是一个有序集合,可以任意顺序将元素插入集合,在对集合进行遍历时,每个值将自动地按照排序后地顺序呈现,TreeSet类排序是使用树结构完成地,具体实现是红黑树,每次将元素添加到树中时,都被放置在正确地排序位置,因此,将元素添加到树中要比添加到散列表中慢。
- 对象比较
TreeSet使用Comparable接口来比较对象进行排序地,接口定义如下:
public interface Comparable<T>{
int compareTo(T other);
}
如果a和b相等,调用 a.compareTo(b)一定返回0;如果排序后a位于b之前,则返回负值;如果a位于b之后,则返回正值。
除了实现 Comparable接口外,还可以将 Comparator 接口对象传递给 TreeSet构造器来告诉树集使用不同地比较方法,Comparator接口定义如下:
public interface Comparator<T>{
int compare(T a,T b);
}
compare方法和 compareTo方法地含义一样。
- 映射表(Map)
映射表数据结构用来存储键/值对,如果提供了键,就能够查询到值,键必须是唯一地,不能对同一个键存放多个值,如果对同一个键两次调用put方法,第二个值会替换第一个值,put会返回用这个键参数存储地上一个值,映射表地接口定义如下:
public interface Map<K,V>{
int size();
V get(Object key);
V put(K key, V value);
V remove(Object key);
…
}
实现映射表数据结构地类有HashMap(散列映射)、TreeMap(二叉搜索树映射),散列映射表对键进行散列,树映射表用键的整体顺序对元素进行排序,并将其组织成搜索二叉树。
- 枚举集(EnumSet)和枚举映射表(EnumMap)
枚举集是一个枚举类型元素集的高效实现,由于枚举类型只有有限个实例,所以EnumSet内部用位序列实现,如果对应的值在集合中,则相应的位被置为1,EnumSet没有构造函数,使用静态工厂来创建实例,示例代码如下:
public enum Weekday {
MONDAY,TUESDAY,WEDNESDAY,THURSDAY,FRIDAY,SATURDAY,SUNDAY
}
EnumSet<Weekday> weekdays = EnumSet.allOf(Weekday.class);
for (Weekday w : weekdays) {
System.out.println("EnumValue=" + w);
}
枚举映射表是一个键类型为枚举类型的映射表,可以直接且高效的用一个值数组实现,在使用时,需要在构造器中指定键类型,示例代码如下:
EnumMap<Weekday, Integer> emEnumMap = new EnumMap<Weekday, Integer>(Weekday.class);
emEnumMap.put(Weekday.FRIDAY, 23);
emEnumMap.put(Weekday.MONDAY, 233);
emEnumMap.put(Weekday.SATURDAY, 123);
emEnumMap.put(Weekday.SUNDAY, 2323);
emEnumMap.put(Weekday.WEDNESDAY, 21233);
Set<Map.Entry<Weekday, Integer>> set = emEnumMap.entrySet();
for (Map.Entry<Weekday, Integer> item : set) {
System.out.println("Key=" + item.getKey() + "\tValue=" + item.getValue());
}
- 同步视图
如果由多个线程访问集合,就必须保证集合不会被意外破坏,类库的设计者,使用视图机制来确保常规集合的线程安全,而不是使用线程安全的集合类,例如,Collections类的静态synchronizedMap方法可以键任何一个映射表转换成具有同步访问方法的Map,示例代码如下:
Map<String,Integer> syncMap = Collections.synchronizedMap(new HashMap<String, Integer>());
List<String> syncList = Collections.synchronizedList(new LinkedList<String>());
- 集合与数组之间的转换
如果有一个数组需要转换为集合,Arrays.asList 的包装器就可以实现这个目的,示例代码如下:
String[] values=…;
HashSet<String> staff = new HashSet<>(Arrays.asList(values));
如果需要将集合转换为数组可以使用toArray方法,该方法有二个重载,第一个重载是返回 Object数组,无法改变类型;第二个重载是返回希望的元素类型的数组,示例代码如下:
// 返回 Object 类型数组
Object[] objValues = staff.toArray();
// 返回 String 类型的数组
String[] StrValues = staff.toArray(new String[0]);