文章目录
9. 集合
9.1 Java集合框架
接口和实现
Java中的集合框架都是:接口和实现是相分离的,以队列Queue为例:
- 队列接口
public interface Queue<E>{ // a simplified form of the interface in the standard library
void add(E element);
E remove();
int size();
}
- 队列实现
队列通常有两种实现方式: 一种是使用循环数组; 另一种是使用链表
每一个实现都可以通过一个实现了Queue接口的类表示,例如
public class CircularArrayQueue<E> implements Queue<E>{}
public class LinkedListQueue<E> implements Queue<E>{}
- AbstractQueue
Java中有另一类以Abstract开头的类, 例如,AbstractQueue;这是为类库实现者而设计。如果想要实现自己的队列类,扩展AbstractQueue 类要比实现Queue 接口中的所有方法轻松得多
Collection接口和迭代器
Collection接口:
在Java 类库中,集合类的基本接口是Collection 接口。这个接口有两个基本方法
public interface Collection<E>{
boolean add(E element);//增加元素
Iterator<E> iterator();//迭代器
...
}
迭代器
public interface Iterator<E>{
E next();//访问下一个元素,越界抛出NoSuchElementException
boolean hasNext();//判断后面是否还有元素
void remove();//删除左边的元素,在使用remove()必须先next(),也就是说删除前必须先访问该元素一次
default void forEachRemaining(Consumer<? super E> action);
}
-
next方法
Iterator<String> iter = staff.iterator(); iter.next();
理解(待验证):iter初始时指向空,每调用next()一次,它就返回下一个元素,并移动指针
-
remove方法
删除当前元素左边的元素
//删除第一个元素 Collection<String> c = . . .; Iterator<String> it = c.iterator(); it.next(); it.remove();
删除当前指向的元素。在remove前必须next,否在将报错IllegalStateException
-
forEachRemaining方法
iterator.forEachRemaining(element -> do something with element);
它将对迭代器的每一个元素调用这个lambda 表达式
示例:
//使用迭代器访问
Collection<String> c = . . .;
Iterator<String> iter = c.iterator();
while (iter.hasNext()){
String element = iter.next();
do something with element
}
//for each语句可以达到同样的效果
for (String element : c){
do something with element
}
Iterable接口
//for each 可以与任何实现了 Iterable 接口的对象一起工作, 这个接口只包含一个抽象方法
public interface Iterable<E>
Iterator<E> iterator();
}
Collection 接口扩展了Iterable 接口
和C++不同,Java只能通过next()访问元素,而不像C++那样重写实现了 c[i] 这种形式的访问
ListIterator接口
实现了add()方法,详见后面的LinkedList小节
泛型实用方法
//检测任意集合是否包含指定元素的泛型方法
public static <E> boolean contains(Collection<E> c, Object obj){
for(E element:c)
if(element.equals(obj))
return true;
return false;
}
像是contains这样的方法很实用,它们被包含在Collection接口声明,要求所有实现类都提供。其他还有
//Collection中的方法
int size()
boolean isEmpty()
boolean contains(Object obj)
boolean containsAll(Collection<?> c)
boolean equals(Object other)
boolean addAll(Collection<? extends E> from)
boolean remove(Object obj)
boolean removeAll(Collection<?> c)
void clear()
boolean retainAll(Collection<?> c)
Object[] toArray()
<T> T[] toArray(T[] arrayToFill)
AbstractCollection
AbstractCollection将基础方法size 和iterator 抽象化了,但将例行方法实现了
public abstract class AbstractCollection<E> implements Collection<E>{
...
public abstract Iterator<E> iterator();
public boolean contains(Object obj){
for(E element:this) //calls iterator()
if(element.equals(obj))
return true;
return false;
}
...
}
集合框架中的接口
集合有两个基本接口,Collection 和 Map
对于键值对的情况,用以下方式插入和读取
V put(K key, V value);//其他使用add方法
V get(K key);//其他使用iterator
-
随机访问的问题
RandomAcces可用于测试一个集合是否支持高效的随机访问if (c instanceof RandomAccess)
9.2 具体的集合 Collection
链表
LinkedList
在Java 程序设计语言中, 所有链表实际上都是双向链接的
-
示例:LinkedList
List<String> staff = new LinkedList<>(); staff.add("Amy"); staff.add("Bob"); staff.add("Carl"); Iterator<String> iter = staff.iterator(); String first = iter.next(); String second = iter.next(); iter.remove();//删除的是第2个元素 "Bob" for(String s:staff) System.out.println(s);
子接口 ListIterator
//ListIterator子接口
interface ListIterator<E> extends Iterator<E>{
void add(E elements);
...
}
ListIterator继承自Iterator,前者包含了add方法,后者不包含
Collection中的add方法返回boolean值,而ListIterator没有返回值,因为后者假定添加操作总会改变链表
-
ListIterator有两个方法,可以用来反向遍历
E previous() boolean hasPrevious()
-
示例1:next() 和 add() 方法
LinkedList中有一个 listlterator() 方法,返回一个实现了Listlterator 接口的迭代器对象
可以将元素添加到非末尾的位置List<String> staff = new LinkedList<>(); staff.add("Amy"); staff.add("Bob"); staff.add("Carl"); //1. 使用普通的iterator,此时iter没有add方法,只能staff.add Iterator<String> iter = staff.iterator(); iter.next(); staff.add("Linda");//添加在整个链表的最后面 //2. 使用listIterator,可以跳过iter.add添加到合适位置(迭代器指向的位置的左边) ListIterator<String> liter = staff.listIterator(); liter.next();//执行完毕之后指向第1个元素 liter.add("Juliet");//添加当前元素之后,即Juliet成为第2位 //3. listIterator(n):使迭代器指向第n个元素前面的元素 ListIterator<String> liter = staff.listIterator(1); System.out.println(liter.next());//输出:Bob
-
示例2:set() 方法修改链表的值
//修改第1个元素 ListIterator<String> liter = staff.listIterator(); String oldValue = liter.next(); liter.set("first");
-
示例3:previous()方法
List<String> staff = new LinkedList<>(){{ add("Amy");add("Bob");add("Carl"); }}; ListIterator<String> liter = staff.listIterator(); liter.next(); liter.next(); liter.previous(); liter.remove();
删除的是“Bob”
调用next(),再调用remove(),删除迭代器当前指向的元素 调用previous(),再调用remove(),删除迭代器当前指向元素右边的元素
迭代器的保护机制:并发修改
下述代码将报错:ConcurrentModificationException
List<String> staff = new LinkedList<>(){{ add("Amy");add("Bob");add("Carl"); }};
ListIterator<String> liter1 = staff.listIterator();
ListIterator<String> liter2 = staff.listIterator();
liter1.next();
liter1.remove();
liter2.next();
java中,集合可以跟踪改写操作(如增、删,不包括set)的次数,而每个迭代器又维护一个独立的计数值,当一个迭代器发现自己的计数值和集合的计数值不一致时,就报错:ConcurrentModificationException
LinkedList不支持随机访问
LinkedList不支持随机访问,不缓存位置信息,每次访问都必须从头遍历
for(int i=0; i<list.size(); ++i)
list.get(i);
//这种使用是绝对要避免的,效率非常低
//get有一个微小优化,当索引大于size()/2时,就从尾端开始遍历
nextIndex() 和 previousIndex()
List<String> staff = new LinkedList<>(){{ add("Amy");add("Bob");add("Carl"); }};
ListIterator<String> liter = staff.listIterator();
System.out.println(liter.previousIndex());//输出:-1
System.out.println(liter.nextIndex());//输出:0
API
//java.util.List<E> 1.2
ListIterator<E> listIterator();//返回一个列表迭代器,以便用来访问列表中的元素。
ListIterator<E> listIterator(int index);//使迭代器指向第index个元素的前面的元素,next()即index元素
void add(int i, E element);
void addAll(int i, Collection<? extends E> elements);//将某个集合中的所有元素添加到给定位置。
E remove(int i);//删除给定位置的元素并返回这个元素。
E get(int i);//获取给定位置的元素
E set(int i, E element);//用新元素取代给定位置的元素,并返回原来那个元素。
int indexOf(Object element);//返回与指定元素相等的元素在列表中第一次出现的位置,如果没有这样的元素将返回-1。
int lastIndexOf(Object element);//返回与指定元素相等的元素在列表中最后一次出现的位置,如果没有这样的元素将返回-1
//java.util.ListIterator<E> 1.2
void add(E newElement);
void set(E newElement);//用新元素取代next 或previous 上次访问的元素
boolean hasPrevious();
E previous();
int nextIndex();
int previousIndex();
//java.util.LinkedList<E> 1.2
LinkedList();//构造一个空链表。
LinkedList(Collection<? extends E> elements);//构造一个链表,并将集合中所有的元素添加到这个链表中。
void addFirst(E element)//将某个元素添加到列表的头部
void addLast(E element);//将某个元素添加到列表的尾部
E getFirst();
E getLast();
E removeFirst();
E removeLast();
ArrayList
ArrayList同样也实现了List接口,封装了一个动态再分配的对象数组
Vector是线程安全的,但也因此在同步上耗费较多
建议在需要同步时使用Vector,不需要同步时使用ArrayList
List<String> staff = new ArrayList<String>(){{
add("Amy");add("Bob");add("Carl");
}};
System.out.println(staff.get(1));//输出:Bob
//get的索引从0开始
ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
对于随机访问get和set,ArrayList优于LinkedList,因为LinkedList要移动指针。
对于新增和删除操作add和remove,LinkedList比较占优势,因为ArrayList要移动数据。
ArrayList和linkedList都不是线程安全的。
散列集 HashSet
在Java 中, 散列表用链表数组实现。每个列表被称为桶(bucket)
标准类库使用的桶数是2 的幂, 默认值为16 ( 为表大小提供的任何值都将被自动地转换为2 的下一个幂)
再散列(rehashed):创建一个桶数更多的表,并将所有元素插入到这个新表中,然后丢弃原来的表
默认的装填因子为0.75,表中超过75%的位置已经填人元素, 这个表就会用双倍的桶数自动地进行再散列
Set接口
set 是没有重复元素的元素集合。set 的add 方法首先在集中查找要添加的对象,如果不存在,就将这个对象添加进去
HashSet
HashSet 类,它实现了基于散列表的集
contains 方法已经被重新定义,它只在某个桶中査找元素,而不必查看集合中的所有元素
Set<String> words = new HashSet<>();
words.add("b");words.add("abcdefg");words.add("d");words.add("c");
//for(String s:words) System.out.print(s+" ");
Iterator<String> iter = words.iterator();
while(iter.hasNext())
System.out.print(iter.next()+" ");
System.out.println();
输出的是:“b” “c” “d” “abcdefg”
HashSet是依次遍历每一个bucket,遍历顺序与加入顺序无关
API
//java.util.HashSet<E> 1.2
HashSet();//构造一个空散列表。
HashSet(Collection<? extends E> elements);//构造一个散列集,并将集合中的所有元素添加到这个散列集中。
HashSet(int initialCapacity);//构造一个空的具有指定容量(桶数)的散列集。
HashSet(int initialCapacity, float loadFactor);//构造一个具有指定容量和装填因子的散列集
树集 TreeSet
树集是一个有序集合
SortedSet<String> words = new TreeSet<String>(){{add("b");add("abcdefg");add("d");add("c");}};
for(String s:words) System.out.print(s+" ");
输出:“abcdefg” “b” “c” “d”
树集的底层结构是红黑树
添加等操作慢于散列表,但用于检查重复元素会快很多
树集必须实现Comparable接口,或者构造集时必须提供一个Comparator
public interface Comparable<T>{
default int compareTo(T other) { return 0; }
}
public interface Comparator<T>{
int compare(T first, T second);
}
示例:Comparable和Comparator
class Item implements Comparable<Item>{
private String name;
private int id;
public Item(String name, int id){this.name=name;this.id=id;};
public String getName(){return this.name;}
public int getId(){return this.id;}
@Override
public int compareTo(Item other){
int diff = Integer.compare(id, other.id);
return diff!=0 ? diff : name.compareTo(other.name);
}
@Override
public String toString(){ return "[name="+name+", id="+id+"]"; }
@Override
public boolean equals(Object otherObject){
if(this==otherObject) return true;//引用相同,那么一定相同
if(otherObject == null) return false;
if(getClass() != otherObject.getClass()) return false;
Item other = (Item) otherObject;
return Objects.equals(name, other.name) && id==other.id;
}
}
public class Test{
public static void main(String[] args){
SortedSet<Item> parts = new TreeSet<>();
parts.add(new Item("Toaster", 5234)) ;
parts.add(new Item("Widget", 4562));
parts.add(new Item("Modem", 9912)) ;
System.out.println(parts.toString());
//从JavaSE 6起,TreeSet类实现了NavigableSet接口。这个接口增加了几个便于定位元素以及反向遍历的方法
NavigableSet<Item> sortByName = new TreeSet<>(Comparator.comparing(Item::getName));
sortByName.addAll(parts);
System.out.println(sortByName.toString());
}
}
API
//java.util.TreeSet<E> 1.2
TreeSet()
TreeSet(Comparator<? super E> comparator);//构造一个空树集。
TreeSet(Collection<? extends E> elements);//构造一个树集, 并增加一个集合或有序集中的所有元素
TreeSet(SortedSet<E> s);//构造一个树集, 并增加一个集合或有序集中的所有元素,要使用同样的顺序
//java.util.SortedSet<E> 1.2
Comparator<? super E> comparator);//返回用于对元素进行排序的比较器。如果元素用Comparable 接口的compareTo 方法进行比较则返回null
E first();//返回有序集中的最小元素
E last();//返回有序集中的最大元素
//java.util.NavigableSet<E> 6
E higher(E value);//返回大于value的最小元素,如果没有这样的元素则返回null
E lower(E value);//返回小于value的最大元素,如果没有这样的元素则返回null
E ceiling(E value);//返回大于等于value的最小元素,如果没有这样的元素则返回null
E floor(E value);//返回小于等于value的最大元素,如果没有这样的元素则返回null
E pollFirst();//删除并返回这个集中的第一个元素,这个集为空时返回null
E pollLast();//删除并返回这个集中的最后一个元素,这个集为空时返回null
Iterator<E> descendingIterator();//返回一个按照递减顺序遍历集中元素的迭代器。
队列和双端队列
双端队列, 可以在头部和尾部同时添加或删除元素
在Java SE 6 中引人了Deque接口, 并由ArrayDeque 和LinkedList 类实现。这两个类都提供了双端队列, 而且在必要时可以增加队列的长度
-
Queue
//java.utii.Queue<E> 5.0 boolean add(E element);//如果队列没有满,添加到队列尾部并返回true,否则拋出一个IllegalStateException boolean offer(E element);如果队列没有满,添加到队列尾部并返回true,否则返回false E remove();//队头删除,如果队列为空,则抛出NoSuchElementException E poll();//队头删除,如果队列为空,则返回null E element();//返回队头元素,但不删除。队空,拋出一个NoSuchElementException E peek();//返回队头元素,但不删除。队空,返回null
-
Deque
//java.util.Deque<E> 6 void addFirst(E element) void addLast(E element)//在末尾添加元素 boolean offerFirst(E element) boolean offerLast(E element) E removeFirst() E removeLast() E pollFirst() E pollLast() E getFirst() E getLast() E peekFirst() E peekLast()
-
ArrayDeque
//java.util.ArrayDeque<E> 6 ArrayDeque()//默认容量:16 ArrayDeque(int initialCapacity)//指定容量
优先级队列
无论何时调用remove 方法,总会获得当前优先级队列中最小的元素
优先级队列使用堆作为数据结构,堆是一个可以自我调节的二叉树
与TreeSet—样,优先级队列需要依赖Comparable 接口或者Comparator接口
示例
PriorityQueue<LocalDate> pq = new PriorityQueue<>();
pq.add(LocalDate.of(1906, 12, 9));
pq.add(LocalDate.of(1815, 12, 10));
pq.add(LocalDate.of(1903, 12, 3));
pq.add(LocalDate.of(1910, 6, 22));
//for(LocalDate date : pq) System.out.println(date);
while (!pq.isEmpty()) System.out.println(pq.remove());
每次remove都会选择最小的元素,猜测是用了最小堆
API
//java.util.PriorityQueue 5.0
PriorityQueue()
PriorityQueue(int initialCapacity)//构造一个用于存放Comparable 对象的优先级队列
PriorityQueue(int initialCapacity, Comparator<? super E> c)//构造一个优先级队列,并用指定的比较器对元素进行排序
9.3 映射 Map
基本映射操作
映射有两个通用实现:HashMap和TreeMap
散列映射对键进行散列, 树映射用键的整体顺序对元素进行排序, 散列或比较函数只能作用于键。
示例
Map<Integer, String> test = new HashMap<>();
test.put(11, "second");
test.put(33, "hello");
test.put(44, "push");
test.put(22, "tutor");
System.out.println(test);//输出顺序和输入顺序不同
System.out.println(test.get(33));//输出:hello
//forEach方法
test.forEach((k,v)->System.out.println("key="+k+",value="+v));
get()找不到时,返回null,但get(id, 0)可以设置找不到时返回指定内容,比如0
put()相同键时,新的值会替代旧值,并返回旧值
-
Map
//java.util.Map<K,V> 1.2 V get(Object key)//获取与键对应的值;返回与键对应的对象,如果在映射中没有这个对象则返回null。键可以为null。 default V getOrDefault(Object key, V defaultValue)//获得与键关联的值,如果未找到这个键,则返回defaultValue。 V put(K key, V value)//将键值对插入到映射中。如果这个键已经存在,新的值会替代旧值,并返回旧值,插入的值不能为null。 void putAll(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 对这个映射中的所有键值对应用这个动作。
-
HashMap
//java.utii.HashMap<K,V> 1.2 HashMap() HashMap(int initialCapacity) HashMap(int initialCapacity, float loadFactor)//用给定的容量和装填因子构造一个空散列映射,默认容量16,默认的装填因子是0.75
-
TreeMap
//java.util.TreeMap<K,V> 1.2 TreeMap()//为实现Comparable 接口的键构造一个空的树映射。 TreeMap(Comparator<? super K> c)//构造一个树映射,并使用一个指定的比较器对键进行排序。 TreeMap(Map<? extends K, ? extends V> entries)//构造一个树映射,并将某个映射中的所有条目添加到树映射中。 TreeMap(SortedMap<? extends K, ? extends V> entries)//构造一个树映射,将某个有序映射中的所有条目添加到树映射中,并使用与给定的有序映射相同的比较器
-
SortedMap
//java.util.SortedMap<K, V> Comparator<? super K> comparator()//返回对键进行排序的比较器。如果键是用Comparable 接口的compareTo 方法进行比较的,返回null。 K firstKey() K lastKey()
更新映射
需求:统计一个单词出现的频率
(1)首先想到下面的写法:
counts.put(word, counts.get(word)+1);
问题:对于原本不存在的键,返回的是null,不能+1,否则报错NullPointerException
(2)改进:getOrDefault
counts.put(word, counts.getOrDefault(word, 0)+1);
(3)更好的方法:merge
counts.merge(word, 1, Integer::sum);
//如果word不存在,赋值1,否则执行原值+1
映射视图
集合框架不认为映射本身是一个集合,不过, 可以得到映射的视图(指实现了Collection 接口或某个子接口)
三个视图:键集、值集合(不是Set) 以及键值对集
Set<K> KeySet()
Collecton<V> values()
Set<Map.Entry<K,V>> entrySet()
//返回这3个视图
keySet 不是HashSet 或TreeSet, 而是实现了Set 接口的另外某个类的对象
Set 接口扩展了Collection 接口。因此, 可以像使用集合一样使用keySet
遍历键值对
for(Map.Entry<String, Employee> entry : staff.entrySet()){
String k = entry.getKey();
Employee v = entry.getValue();
}
//在forEach出现前,这是最有效的方式,现在使用forEach替代
键集视图上调用迭代器的remove 方法, 实际上会从映射中删除这个键和与它关联的值
不能向键集视图增加元素, 抛出UnsupportedOperationException
- API
//java.util.Map<K,V> 1.2
Set<Map.Entry<K, V>> entrySet()//返回Map.Entr 对象(映射中的键值对)的一个集视图。可以从这个集中删除元素,它们将从映射中删除,但是不能增加任何元素。
Set<K> keySet()//返回映射中所有键的一个集视图。可以从这个集中删除元素,键和相关联的值将从映射中删除,但是不能增加任何元素。
Collection<V> values()//返回映射中所有值的一个集合视图。可删除,不能增加
//java.util.Map.Entry<K, V> 1.2
K getKey()
V getValue()
V setValue(V newValue)//将相关映射中的值改为新值, 并返回原来的值。
弱散列映射 WeakHashMap
参考:https://blog.csdn.net/QuinnNorris/article/details/74857879
解决的问题:对于有些键值对,它不再被使用,但由于保存它的映射对象是活跃的,该键值对不能被垃圾回收
HashMap:将一个对象a以及他的引用A作为一个key值关联某个Value值后put入HashMap中,那么这个a对象的引用不仅仅有A,而且有一个HashMap中持有的引用,一共两个引用
WeakHashMap:原理相同,此时在WeakHashMap中的a也持有两个引用,一个是A,另一个是WeakHashMap的散列表持有的引用。WeakHashMap散列表持有所有key的引用是弱引用
//弱散列映射
String a = new String("a");//确保a是在堆上,以便gc
Map weakmap = new WeakHashMap();
weakmap.put(a, "aaa");
a = null;
System.gc();
weakmap.forEach((k,v)->System.out.println(k+" "+v));
//无输出
//普通映射
String a = new String("a");//确保a是在堆上,以便gc
Map weakmap = new WeakHashMap();
Map hashmap = new HashMap();
hashmap.put(a, "aa");
weakmap.put(a, "aaa");
a = null;
System.gc();
weakmap.forEach((k,v)->System.out.println(k+" "+v));
//当A变为null时,HashMap中仍然保存着a的一个引用,不能满足回收条件,WeakHashMap中的a不被回收
//输出:a aaa
机制:WeakHashMap 使用弱引用保存键。WeakReference 对象将引用保存到另外一个对象中, 在这里, 就是散列键。WeakHashMap 将周期性地检查队列, 以便找出新添加的弱引用。一个弱引用进人队列意味着这个键不再被他人使用, 并
且已经被收集起来。于是, WeakHashMap 将删除对应的条目。
链接散列集与映射
参考:https://blog.csdn.net/qq_39027486/article/details/99708857
LinkedHashSet 和 LinkedHashMap 类用来记住插人元素项的顺序。
-
LinkedHashSet
由链表保证元素有序(有序只是插入顺序,而不是大小顺序等)
由哈希表保证元素唯一LinkedHashSet<Integer> ls=new LinkedHashSet<>(); ls.add(97); ls.add(28); ls.add(97); ls.add(68); System.out.println(ls); //输出:[97, 28, 68] //如果是HashSet,输出[97, 68, 28]
-
LinkedHashMap
底层数据结构是 链表 + 哈希表
链表保证元素有序,哈希表保证元素唯一LinkedHashMap<String, String> lap = new LinkedHashMap<>(); lap.put("bsd", "opg"); lap.put("a", "zxc"); lap.forEach((k,v)->System.out.println(k+" "+v)); //依次输出键值为:["bsd", "a"] //如果是HashMap,输出:["a", "bsd"]
访问顺序:
链接散列映射将用访问顺序, 而不是插入顺序
使用场景:当需要把访问频率高的放在内存,低的从数据库中读取
LinkedHashMap<K, V>(initialCapacity, loadFactor, true)
示例:被访问的条目将放在链表尾部
LinkedHashMap<String, String> lap = new LinkedHashMap<>(10, 0.75f, true);
lap.put("bsd", "opg");
lap.put("a", "zxc");
lap.forEach((k,v)->System.out.println(k+" "+v));//输出顺序:["bsd", "a"]
lap.get("bsd");
lap.forEach((k,v)->System.out.println(k+" "+v));//输出顺序:["a", "bsd"]
自动化删除最不常访问的元素
//当removeEldestEntry方法返回true时,就添加一个新条目,从而导致删除eldest条目
//下面的高速缓存可以存放100个元素:
Map<String, String> cache = new LinkedHashMap<String, String>(128, 0.75F, true) {
protected boolean removeEldestEntry(Map.Entry<String, String> eldest){
return size() > 100;
}
};
枚举集和映射
EnumSet 内部用位序列实现。如果对应的值在集中, 则相应的位被置为1
EnumSet 类没有公共的构造器。可以使用静态工厂方法构造这个集
- 枚举集
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);
System.out.println(always);//输出周一到周六
System.out.println(never);//输出空集
System.out.println(workday);//输出工作日
System.out.println(mwf);//输出周一、周三、周五
- 映射
EnumMap<Weekday, Employee> personInCharge = new EnumMap<>(Weekday.class);
personInCharge.put(Weekday.FRIDAY, new Employee("liming"));
System.out.println(personInCharge.get(Weekday.FRIDAY).getName());
E extends Enum表示E是一个枚举类型
标识散列映射
IdentityHashMap类中,键的散列值不是用hashCode 函数计算的, 而是用System.identityHashCode
方法计算的
System.identityHashCode 根据对象的内存地址进行映射
在对两个对象进行比较时, IdentityHashMap 类使用==, 而不使用equals
9.4 视图与包装器
映射类的keySet 方法返回一个实现Set接口的类对象, 这个类的方法对原映射进行操作。这种集合称为视图
轻量级集合包装器
-
asList静态方法:返回的是一个视图对象
Card[] cardOeck = new Card[52]; List<Card> cardList = Arrays.asList(cardDeck):
List<String> names = Arrays.asList("Amy", "Bob", "Carl");
示例:改变数组大小的所有方法都将抛出一个UnsupportedOperationException异常
String[] s = {"Amy", "Bob", "Carl"}; List<String> names = Arrays.asList(s); names.set(1, "change"); System.out.println(Arrays.toString(s));//发现s被改变 names.add("last");//报错:UnsupportedOperationException
-
nCopies()
Collections.nCopies(n, anObject) //返回一个实现了List接口的不可修改的对象
例如:
List<String> settings = Collections.nCopies(100, "DEFAULT") ;
存储代价小,因为"DEFAULT"只存储了一次
-
singleton()
Collections.singleton(anObject) //将返回一个视图对象。这个对象实现了Set 接口 //返回的对象实现了一个不可修改的单元素集,而不需要付出建立数据结构的开销
子范围
-
subList():返回一个子范围视图
List group2 = staff.subList(10, 20);//前闭后开 group2.clear(); //删除这个子范围;staff中也被删除,且group2被清空
-
有序集和映射可以根据排序建立视图
SortedSet< E> subSet(E from, E to) SortedSet< E> headSet(E to) SortedSet< E> tailSet(E from) SortedMap<K, V> subMap(K from, K to) SortedMap<K, V> headMap(K to) SortedMap<K, V> tailMap(K from) //NavigableSet 接口赋予子范围操作更多的控制能力。可以指定是否包括边界 NavigableSet<E> subSet(E from, boolean fromInclusive, E to, boolean toInclusive) NavigableSet<E> headSet(E to, boolean toInclusive) Navigab1eSet<E> tailSet(E from, boolean fromInclusive)
不可修改的视图
可以使用下面8 种方法获得不可修改视图:
Collections.unmodifiableCollection
Collections.unmodifiableList
Collections.unmodifiableSet
Collections.unmodifiableSortedSet
Collections.unmodifiableNavigableSet
Collections.unmodifiableMap
Collections.unmodifiableSortedMap
Collections.unmodifiableNavigableMap
示例
List<String> list = new LinkedList<>();
list.add("a");
List<String> l = Collections.unmodifiableList(list);
l.set(0, "aa");//修改将报错UnsupportedOperationException
同步视图
类库的设计者使用视图机制来确保常规集合的线程安全
例如, Collections 类的静态synchronizedMap 方法可以将任何一个映射表转换成具有同步访问方法的Map
Map<String, Employee> map = Collections.synchronizedMap(new HashMap<String, Employee>());
这样就可以多线程访问map 对象
等学习并发之后再回来看这一小节
受查视图
ArrayList<String> strings = new ArrayList();
ArrayList rawList = strings;
rawList.add(new Date());
//能顺利编译运行,这将导致rawList里包含一个Date对象
这样的错误无法在运行时被检测,只有在稍后的另一部分代码中调用get方法,并将结果转化为String 时,这个类才会抛出异常
checkedList
ArrayList<String> strings = new ArrayList();
List<String> safestrings = Collections.checkedList(strings, String.class);
safestrings.add(new Date());//报错
对于ArrayList < Pair< String>>,由于有原始Pair 类,无法阻止插入Pair< Date>
9.5 算法
示例:求最大值
public static <T extends Comparable> T max(Collection<T> c){
if(c.isEmpty()) throw new NoSuchElementException();
Iterator<T> iter = c.iterator();
T largest = iter.next();
while(iter.hasNext()){
T next = iter.next();
if(largest.compareTo(next) < 0)
largest = next;
}
return largest;
}
排序和混排
-
**sort():**对实现了List接口的集合排序
List<Integer> l = new ArrayList<Integer>(){{add(23);add(75);add(12);add(5);}}; Collections.sort(l);//Collections.sort,默认升序排列 Collections.sort(l, Comparator.comparingInt(Integer::intValue)); Collections.sort(l, (t1, t2)->t2.compareTo(t1));//反向 l.sort(Comparator.comparingInt(Integer::intValue));//object.sort(...),比如传入一个Comparator对象 l.sort(Comparator.comparingInt(Integer::intValue).reversed());//反向 l.sort(Comparator.naturalOrder()); l.sort(Comparator.reverseOrder());
-
**shuffle():**随机打乱集合顺序
Collections.shuffle(l);
二分查找
要求:1. 集合必须是已经排好序的;2. 集合需要采用Comparable 接口或者提供一个比较器对象
i = Collections.binarySearch(c, element);
i = Collections.binarySearch(c, element, comparator);
//返回值>=0,表示已找到,返回索引;返回值<0,标识未找到
插入元素的位置:
if (i < 0)
c.add(-i - 1, element) ;
使用二分查找只有在可以随机访问的结构中有意义
常用算法
- Collections
//java.util.Collections 1.2
static <T extends Comparab1e<? super T>> T min(Collection<T> elements )
static <T extends Comparable<? super T>> T max(Collection<T> elements )
static <T> min(Collection<T> elements, Comparator<? super T> c)
static <T> max(Collection<T> elements, Comparator<? super T> c)
static <T> void copy(List<? super T> to, List<T> from)
static <T> void fill(List<? super T> l, T value)//将列表中所有位置设置为相同的值。
static <T> boolean addAll(Collection<? super T> c, T... values)//将所有的值添加到集合中。如果集合改变了,则返回true
static <T> boolean replaceAll(List<T> l, T oldValue, T newValue)// 1.4 用newValue 取代所有值为oldValue 的元素。
static int indexOfSubList(List<?> l, List<?> s)// 1.4
static int lastlndexOfSubList(List<?> l , List<?> s)// 1.4
//返回l中第一个或最后一个等于s子列表的索引。如果l中不存在等于s的子列表,则返回-1。例如,l为[s,t,a,r] , s为[t, a, r], 两个方法都将返回索引1。
static void swap(List<?> l, int i, int j)// 1.4
static void reverse(List<?> l)//逆置列表中元素的顺序。这个方法的时间复杂度为O(n)
static void rotate(List<?> l, int d)// 1.4 旋转列表中的元素,时间复杂度为O(n)
static int frequency(Collection<?> c, Object o)// 5.0 返回c中与对象o相同的元素个数。
boolean disjoint (Collection<?> c1, Collection<?> c2)// 5.0 如果两个集合没有共同的元素,则返回true
- Collection
//java.util.Collection<T> 1.2
default boolean removeIf (Predicate<? super E> filter)// 8 删除所有匹配的元素。
- List
//java.util.List<E> 1.2
default void replaceAll(UnaryOperator<E> op)// 8 对这个列表的所有元素应用这个操作。
<T extends Comparable< T>> 表示类型 T 必须实现 Comparable 接口,并且这个接口的类型是 T
<T extends Comparable<? super T>> 表示类型 T 必须实现 Comparable 接口,并且这个接口的类型是 T 或 T 的任一父类。这样声明后,T 的实例之间,T 的实例和它的父类的实例之间,可以相互比较大小。
批操作
coll1.removeAll(coll2);//从coll1中删除coll2中出现的所有元素
coll1.retainAll(coll2);//从coll1中删除所有未在coll2中出现的元素
staffMap.keySet().removeAll(terminatedIDs);//建立映射删除已解除聘用的员工
//批操作和子范围
relocated.addAll(staff.subList(0, 10)):
staff.subList(0, 10).clear();
集合与数组的转换
-
数组转为集合:asList
String[] values = . . HashSet<String> staff = new HashSet<>(Arrays.asList(values));
-
集合转为数组
Object[] values = staff.toArray();//只能得到对象数组 String[] values = (String[]) staff.toArray();// Error!
可以采用如下方法
String[] values = staff.toArray(new Stringt[0]); String[] values = staff.toArray(new String[staff.size()]);//可以指定大小
9.6 遗留的集合
HashTable
Hashtable 类与HashMap 类的作用一样, 实际上, 它们拥有相同的接口
与Vector 类的方法一样。Hashtable 的方法也是同步的。如果对同步性或与遗留代码的兼容性没有任何要求,就应该使用HashMap。如果需要并发访问, 则要使用ConcurrentHashMap
枚举
遗留集合使用Enumeration
接口对元素序列进行遍历
它有hasMoreElements
和nextElement
两个函数
Hashtable 类的elements 方法将产生一个用于描述表中各个枚举值的对象:
Enumeration<Employee> e = staff.elements();
whi1e (e.HasMoreElements()){
Employee e = e.nextElement();
}
静态方法Collections.enumeration也可以产生枚举类型
属性映射
特点:键与值都是字符串;表可以保存到一个文件中, 也可以从文件中加载;使用一个默认的辅助表
实现属性映射的Java 平台类称为Properties,属性映射通常用于程序的特殊配置选项
Properties()//创建一个空的属性映射
Properties(Properties defaults)//创建一个带有一组默认值的空的属性映射
String getProperty(String key)//返回与键对应的字符串。如果在映射中不存在,返回默认表中与这个键对应的字符串
String getProperty(String key, String defaultValue)//获得在键没有找到时具有的默认值属性
void load(InputStream in)//从InputStream加载属性映射
void store(OutputStream out, String commentstring)//把属性映射存储到OutputStream。
栈
Stack,后拓展为Vector,但Vector使用不太令人满意
E push(E item)//将item 压人桟并返回item。
E pop()//弹出并返回栈顶的item。如果栈为空,请不要调用这个方法。
E peek()//返回栈顶元素,但不弹出。如果栈为空,请不要调用这个方法。
位集
由于位集将位包装在字节里, 所以,使用位集要比使用Boolean 对象的ArrayList 更加高效。
bucketOfBits.get(i)//如果第i位处于“开” 状态,就返回true; 否则返回false
bucketOfBits.set(i)//将第i位置为“开” 状态
bucketOfBits.clear(i)//将第i位置为“关” 状态
API
//java.util.BitSet 1.0
BitSet(int initialCapacity)//创建一个位集。
int length()//返回位集的“逻辑长度”,即1加上位集的最高设置位的索引。
boolean get(int bit)
void set(int bit)
void clear(int bit)
void and(BitSet set)//这个位集与另一个位集进行逻辑“AND”
void or(BitSet set)//这个位集与另一个位集进行逻辑“OR”
void xor(BitSet set)//这个位集与另一个位集进行逻辑“X0R”
void andNot(BitSet set)//清除这个位集中对应另一个位集中设置的所有位