Java基础之《Java核心卷1》第9章 集合

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;
    }
    ...
}

集合框架中的接口

在这里插入图片描述

集合有两个基本接口,CollectionMap

对于键值对的情况,用以下方式插入和读取

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

基本映射操作

映射有两个通用实现:HashMapTreeMap
散列映射对键进行散列, 树映射用键的整体顺序对元素进行排序, 散列或比较函数只能作用于键

示例

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

LinkedHashSetLinkedHashMap 类用来记住插人元素项的顺序。

在这里插入图片描述

  • 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接口对元素序列进行遍历
它有hasMoreElementsnextElement两个函数
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)//清除这个位集中对应另一个位集中设置的所有位
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值