文章目录
17.1 完整的容器分类法
17.2 填充容器
public class A {
public static void main(String[] args) {
List<Object> objects = new ArrayList<>(Collections.nCopies(4, new B(1)));
System.out.println(objects.toString());
Collections.fill(objects, new B(2));
System.out.println(objects);
}
}
17.3 Collections的功能方法
其中不包含随机访问的get方法,因为Collection中的Set自己维护内部顺序,因此想检查Collection中的元素只能使用迭代器。
17.4 可选操作
执行各种不同的添加和移除的方法在Collection接口中都是可选操作。这意味着实现类并不需要为这些方法提供功能定义,而是抛出一个UnsupportedOperationException。
public class A1 {
public void f(){
throw new UnsupportedOperationException();
}
public static void main(String[] args) {
A1 a1 = new A1();
a1.f();
}
}
为什么会将方法定义为可选的呢?
可以防止在设计中出现接口爆炸的情况。容器应该易学易用。未获支持的操作是一种特例,可以延迟到需要时再实现。
17.4.1 未获支持的操作
(1)最常见的未获支持的操作,都来源于背后由固定尺寸的数据结构支持的容器。
(2)还可以通过使用Collection类中不可修改的方法,选择创建任何会抛出UnsupportedOperationException的容器。
public class A1 {
public void f(){
throw new UnsupportedOperationException();
}
public static void main(String[] args) {
List<String> objects = Arrays.asList("a", "b", "c");
// objects.add("d"); // UnsupportedOperationException
List<Object> objects1 = Collections.unmodifiableList(new ArrayList<>(objects));
// objects1.add("d"); // UnsupportedOperationException
}
}
17.5 List的功能方法
public class A1 {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 5, 6));
ArrayList<Integer> list1 = new ArrayList<>(Arrays.asList(3, 5));
list.addAll(2, list1);
System.out.println(list);
System.out.println(list.contains(1));
System.out.println(list.containsAll(list1));
System.out.println(list.indexOf(5));
System.out.println(list.lastIndexOf(5));
list.remove(0);
list.remove(new Integer(2));
System.out.println(list);
list.retainAll(list1);
System.out.println(list);
list.removeAll(list1);
list.clear();
System.out.println(list);
// 迭代器取数
ArrayList<Integer> list2 = new ArrayList<>(Arrays.asList(1, 2, 3, 4));
ListIterator<Integer> listIterator = list2.listIterator();
while(listIterator.hasNext()) {
System.out.println(listIterator.nextIndex() + "-" + listIterator.next());
}
while(listIterator.hasPrevious()) {
System.out.println(listIterator.previousIndex() + "-" + listIterator.previous());
}
// 迭代器修改元素
listIterator.add(10);
System.out.println(listIterator.previous());
System.out.println(listIterator.next());
System.out.println(list2);
listIterator.next();
listIterator.remove();
listIterator.next();
listIterator.set(99);
listIterator.next();
listIterator.remove();
listIterator.add(100);
System.out.println(list2);
// LinkedList操作
LinkedList<String> strings = new LinkedList<>(Arrays.asList("a", "b", "c"));
strings.getFirst();
strings.getLast();
strings.addFirst("a");
strings.addLast("c");
strings.removeFirst();
strings.removeLast();
System.out.println(strings);
}
}
17.6 Set和存储顺序
Set需要一种方法来维护存储顺序,而存储顺序如何维护,则是在Set的不同实现之间会有所变化。因此,不同的Set实现不仅具有不同的行为,而且它们对于可以在特定的Set中放置的元素的类型也有不同的要求。
(1)HashSet:添加时通过hashCode方法得到一个hash值,通过hash值得到对应的索引,即在数组中的位置,如果发现索引所在的位置没有数据,直接存放进去,如果当前索引已有数据,就进行euqals方法比较【遍历比较】,若比较后都不相同,就加入在后边,否则不加入。
(2)TreeSet:使用compareTo方法比较,如果compareTo方法返回0,说明是重复的,如果是负数,则往前面排,如果是正数,往后面排;
/* 基类,实现equals方法(HashSet和LinkedHashSet需要用到),如果不覆写则会使用Object的方法,比较对象的地址 */
public class SetType {
int i;
public SetType(int n) {
i = n;
}
public boolean equals(Object o) {
return o instanceof SetType && (i == ((SetType) o).i);
}
public String toString() {
return Integer.toString(i);
}
}
/* 实现hashcode方法如果不覆写则会使用Object的方法 */
public class HashType extends SetType {
public HashType(int n) {
super(n);
}
@Override
public int hashCode() {
return i;
}
}
/* 实现Comparable接口,不实现会抛出异常 */
public class TreeType extends SetType implements Comparable<TreeType> {
public TreeType(int n) {
super(n);
}
@Override
public int compareTo(TreeType o) {
return (o.i < i ? -1 : (o.i == i ? 0 : 1));
}
}
public class A1 {
public static void main(String[] args) throws Exception {
HashSet<HashType> hashSet = new HashSet<>();
for (int i = 0; i < 10; i++) {
hashSet.add(HashType.class.getConstructor(int.class).newInstance(i));
}
System.out.println(hashSet);
LinkedHashSet<HashType> linkedHashSet = new LinkedHashSet<>();
for (int i = 0; i < 10; i++) {
linkedHashSet.add(HashType.class.getConstructor(int.class).newInstance(i));
}
System.out.println(linkedHashSet);
TreeSet<TreeType> treeSet = new TreeSet<>();
for (int i = 0; i < 10; i++) {
treeSet.add(TreeType.class.getConstructor(int.class).newInstance(i));
}
System.out.println(treeSet);
}
}
17.6.1 SortedSet
SortedSet中的元素可以保证处于排序状态,它有一些额外的功能。
SortedSet<TreeType> treeSet = new TreeSet<>();
for (int i = 0; i < 10; i++) {
treeSet.add(TreeType.class.getConstructor(int.class).newInstance(i));
}
System.out.println(treeSet);
System.out.println(treeSet.first()); // 第一个元素
System.out.println(treeSet.last()); // 最后一个元素
SortedSet<TreeType> treeTypes1 = treeSet.subSet(new TreeType(7), new TreeType(5)); // 子集
SortedSet<TreeType> treeTypes2 = treeSet.headSet(new TreeType(5));
SortedSet<TreeType> treeTypes3 = treeSet.tailSet(new TreeType(5));
System.out.println(treeTypes1);
System.out.println(treeTypes2);
System.out.println(treeTypes3);
17.7 队列
public class Gen {
public static void f1(Queue queue) {
for (int i = 0; i < 10; i++) {
queue.offer(i);
}
System.out.print(queue + " ");
while (queue.peek() != null) {
System.out.print(queue.remove() + " ");
}
System.out.println();
}
public static void main(String[] args) {
f1(new LinkedList());
f1(new PriorityQueue()); // 按优先级排列
f1(new PriorityBlockingQueue()); // 按优先级排列
f1(new LinkedBlockingQueue());
f1(new ArrayBlockingQueue(10));
f1(new ConcurrentLinkedQueue());
}
}
17.7.1 优先级队列
public class Gen implements Comparable<Gen> {
public int i;
public Gen(int i) {
this.i = i;
}
@Override
public int compareTo(Gen o) {
return (o.i < i ? -1 : (o.i == i ? 0 : 1));
}
@Override
public String toString() {
return "" + i;
}
public static void main(String[] args) {
PriorityQueue<Gen> priorityQueue = new PriorityQueue<>();
priorityQueue.add(new Gen(3));
priorityQueue.add(new Gen(1));
priorityQueue.add(new Gen(2));
System.out.println(priorityQueue);
while (priorityQueue.peek() != null) {
System.out.print(" "+ priorityQueue.remove());
}
}
}
17.7.2 双向队列
可以在任何一端添加或移除元素。LinkedList中包含支持双向队列的方法,但Java中没有双向队列的接口。可以使用组合,利用LinkedList创建一个双向队列类。
17.8 理解Map
关联数组维持键-值关联,因此可以使用键 来查找值。Map的实现有HashMap,TreeMap,LinkedHashMap,WeakHashMap,ConcurrentHashMap,IdentityHashMap。这些实现的差异表现在效率,键值对的保存及呈现次序,对象的保存周期,多线程程序中工作,判定键等价的策略。
/*
* 一个简单的实现
* 固定尺寸,get方法缺乏效率
*/
public class AssociativeArray<K, V> {
private Object[][] pairs;
private int index;
public AssociativeArray(int i) {
pairs = new Object[i][2];
}
public void put(K key, V value) {
if (index >= pairs.length) {
throw new ArrayIndexOutOfBoundsException();
}
pairs[index++] = new Object[]{key, value};
}
public V get(K key) {
for (int i = 0; i < index; i++) {
if (key.equals(pairs[i][0])) {
return (V) pairs[i][1];
}
}
return null;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < index; i++) {
builder.append(pairs[i][0]);
builder.append(":");
builder.append(pairs[i][1]);
builder.append(", ");
}
return builder.toString();
}
public static void main(String[] args) {
AssociativeArray<String, String> associativeArray = new AssociativeArray<String, String>(3);
associativeArray.put("a", "a");
associativeArray.put("b", "b");
associativeArray.put("c", "c");
String d = associativeArray.get("d");
System.out.println(d);
System.out.println(associativeArray);
}
}
17.8.1 性能
上文中get()使用线性搜索速度很慢。HashMap通过Object类的hashCode()得到散列码来取代对键的缓慢搜索。散列码是唯一的,是通过将对象的某些信息转换而生成的。
对Map中使用的键的要求与对Set中的元素的要求一样。任何键都必须具有一个equals方法。HashMap必须具有hashCode方法。TreeMap必须实现Comparable接口。
public class A {
public static void main(String[] args) {
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("a", "1");
hashMap.put("a", "2");
hashMap.put("b", "3");
System.out.println(hashMap.get("a"));
System.out.println(hashMap);
System.out.println(hashMap.size());
System.out.println(hashMap.keySet());
System.out.println(hashMap.values());
System.out.println(hashMap.containsKey("a"));
System.out.println(hashMap.containsValue("1"));
System.out.println(hashMap.keySet().iterator().next());
hashMap.remove("a");
hashMap.clear();
System.out.println(hashMap.isEmpty());
System.out.println("=====================================");
HashMap<Object, Object> hashMap1 = new HashMap<>();
hashMap1.put(1, "a");
hashMap1.put(2, "b");
hashMap1.put(3, "c");
System.out.println(hashMap1);
Set<Object> objects = hashMap1.keySet();
objects.remove(1);
System.out.println(hashMap1);
Collection<Object> values = hashMap1.values();
values.remove("b");
System.out.println(hashMap1);
}
}
17.8.2 SortedMap
TreeMap是SortedMap的唯一实现,键是有序的。
public class A {
public static void main(String[] args) {
TreeMap<Object, Object> treeMap = new TreeMap<>();
treeMap.put(1, "a");
treeMap.put(3, "c");
treeMap.put(2, "b");
System.out.println(treeMap);
System.out.println(treeMap.firstKey());
System.out.println(treeMap.lastKey());
System.out.println(treeMap.comparator());
System.out.println(treeMap.keySet().iterator().next());
}
}
17.8.3 LinkedHashMap
为了提高速度,LinkedHashMap散列化所有的元素,但是在遍历键值对时,却又以元素的插入顺序返回键值对。此外,可以在构造器中设定LinkedHashMap,使之采用基于访问的最近最少使用(LRU)算法,没有被访问过的元素就会出现在队列的前面。
public class A {
public static void main(String[] args) {
// 指定初始容量和负载因子
LinkedHashMap<Object, Object> treeMap = new LinkedHashMap<>(16, 0.75f, true);
for (int i = 0; i < 10; i++) {
treeMap.put(i, i);
}
System.out.println(treeMap);
for (int i = 0; i < 4; i++) {
treeMap.get(i);
}
System.out.println(treeMap);
}
}
只保留最近3条数据
LinkedHashMap<Integer, String> map = new LinkedHashMap<>(Integer, String) {
@Override
protected boolean removeEldestEntry() {
return size() > 3;
}
}
map.put(1, "1");
map.put(2, "2");
map.put(3, "3");
map.put(4, "4");
System.out.println(map);
17.9 散列与散列码
使用自定义的类作为HashMap的键时需要记得覆写Object类的equals方法和hashCode方法。
正确的equals方法必须满足下列5个条件:
(1)自反性,x.equals(x)=true
(2)对称性,x.equals(y)=true,y.equals(x)=true
(3)传递性x.equals(y)=true,y.equals(z)=true,x.equals(z)=true
(4)一致性,x.equals(y)的值不会改变
(5)对任何不是null的x,x.equals(null)一定返回null。
17.9.1 理解hashCode()
下面用一对ArrayList实现一个Map
public class SlowMap<K, V> {
private List<K> keys = new ArrayList<K>();
private List<V> values = new ArrayList<V>();
public void put(K k, V v) {
if (!keys.contains(k)) {
keys.add(k);
values.add(v);
} else {
values.set(keys.indexOf(k), v);
}
}
public V get(K k) {
if (!keys.contains(k))
return null;
return values.get(keys.indexOf(k));
}
public Set<Map.Entry<K, V>> entrySet() {
HashSet<Map.Entry<K, V>> entries = new HashSet<>();
Iterator<K> iterator1 = keys.iterator();
Iterator<V> iterator2 = values.iterator();
while (iterator1.hasNext()) {
entries.add(new MapEntry<K, V>(iterator1.next(), iterator2.next()));
}
return entries;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
Iterator<K> iterator1 = keys.iterator();
Iterator<V> iterator2 = values.iterator();
while (iterator1.hasNext()) {
builder.append(iterator1.next());
builder.append(":");
builder.append(iterator2.next());
if (iterator1.hasNext())
builder.append(",");
}
return builder.toString();
}
public static void main(String[] args) {
SlowMap<Integer, String> slowMap = new SlowMap<Integer, String>();
slowMap.put(1, "a");
slowMap.put(2, "b");
slowMap.put(3, "c");
slowMap.put(3, "d");
System.out.println(slowMap);
System.out.println(slowMap.get(2));
Set<Map.Entry<Integer, String>> entries = slowMap.entrySet();
System.out.println(entries);
}
}
public class MapEntry<K, V> implements Map.Entry<K, V> {
private K key;
private V value;
public MapEntry(K k, V v) {
key = k;
value = v;
}
@Override
public K getKey() {
return key;
}
public K setKey(K key) {
this.key = key;
return key;
}
@Override
public V getValue() {
return value;
}
@Override
public V setValue(V value) {
this.value = value;
return value;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MapEntry<?, ?> mapEntry = (MapEntry<?, ?>) o;
return Objects.equals(key, mapEntry.key) &&
Objects.equals(value, mapEntry.value);
}
@Override
public int hashCode() {
return Objects.hash(key, value);
}
@Override
public String toString() {
return key + "=" + value;
}
}
17.9.2 为速度而散列
SlowMap的问题在于对键的查询,键没有按照任何特定顺序保存,所以只能使用简单的线性查询,而线性查询是最慢的查询方式。
https://blog.csdn.net/weixin_38324954/article/details/107188358
散列:使用hashCode()生成的散列码作为数组下标,将键信息存储于对应的数组单元中。查询一个值的过程:首先计算散列码,然后使用散列码查询数组。如果能够保证没有冲突,就有了一个完美的散列函数。通常,冲突由外部链接处理:数组并不直接保存值,而是保存值得list。然后对list中的值使用equals方法进行线性的查询,这部分的查询比较慢。
实现一个散列Map:
package h;
import org.omg.CORBA.OBJ_ADAPTER;
import java.util.*;
public class SimpeHashMap<K, V> extends AbstractMap<K, V> {
static final int SIZE = 10;
LinkedList<MapEntry<K,V>>[] buckets = new LinkedList[SIZE];
@Override
public V put(K key, V value) {
int index = key.hashCode() % SIZE;
LinkedList<MapEntry<K, V>> bucket = buckets[index];
MapEntry<K, V> addEntry = new MapEntry<>(key, value);
if (bucket == null) {
buckets[index] = new LinkedList<>();
}
ListIterator<MapEntry<K, V>> iterator = buckets[index].listIterator();
while(iterator.hasNext()) {
MapEntry<K, V> next = iterator.next();
if (next.getKey().equals(key)) {
iterator.set(addEntry);
return next.getValue();
}
}
buckets[index].add(addEntry);
return null;
}
@Override
public V get(Object key) {
int index = key.hashCode() % SIZE;
LinkedList<MapEntry<K, V>> bucket = buckets[index];
if (bucket == null) {
return null;
}
for (MapEntry<K, V> mapEntry : bucket) {
if (mapEntry.getKey().equals(key)) {
return mapEntry.getValue();
}
}
return null;
}
@Override
public Set<Entry<K, V>> entrySet() {
HashSet<MapEntry<K, V>> objects = new HashSet<>();
return null;
}
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder();
for (LinkedList linkedList : buckets) {
stringBuilder.append(linkedList + " ");
}
return stringBuilder.toString();
}
public static void main(String[] args) {
SimpeHashMap<T1, String> simpeHashMap = new SimpeHashMap<T1, String>();
simpeHashMap.put(new T1(1), "a");
simpeHashMap.put(new T1(11), "b");
System.out.println(simpeHashMap.get(new T1(1)));
System.out.println(simpeHashMap.get(new T1(11)));
System.out.println(simpeHashMap.get(new T1(111)));
System.out.println(simpeHashMap);
}
}
package h;
public class T1 {
int i;
public T1(int i) {
this.i = i;
}
@Override
public int hashCode() {
return i;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
T1 t1 = (T1) o;
return i == t1.i;
}
@Override
public String toString() {
return "" + i;
}
}
package h;
import java.util.*;
public class MapEntry<K, V> implements Map.Entry<K, V> {
private K key;
private V value;
public MapEntry(K k, V v) {
key = k;
value = v;
}
@Override
public K getKey() {
return key;
}
public K setKey(K key) {
this.key = key;
return key;
}
@Override
public V getValue() {
return value;
}
@Override
public V setValue(V value) {
this.value = value;
return value;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MapEntry<?, ?> mapEntry = (MapEntry<?, ?>) o;
return Objects.equals(key, mapEntry.key) &&
Objects.equals(value, mapEntry.value);
}
@Override
public int hashCode() {
return Objects.hash(key, value);
}
@Override
public String toString() {
return key + "=" + value;
}
}
17.9.3 覆盖hashCode()
System.out.println("a" == "a"); // true,"a"存在常量池中
System.out.println(new String("a") == new String("a")); // false,new String("a")存在堆中
System.out.println("a".hashCode() == "a".hashCode()); // hashCode基于String的内容计算
System.out.println(new String("a").hashCode() == new String("a").hashCode());
17.10 选择接口的不同实现
17.10.5 对Map的选择
HashMap的性能因子:
容量,初始容量,尺寸,负载因子:尺寸/容量
当负载情况达到负载因子的水平时,容器将自动增加其容量(容量加倍,并重新将现有对象分布到新的桶位集中,这称为再散列)。默认的负载因子是0.75,这个因子在时间和空间之间达到了平衡。更高的负载因子可以降低表所需的空间,但是会增加查找代价。
17.11 实用方法
17.11.2 设定Collection或Map为不可修改
私有字段的get方法返回容器不可修改的版本。
package H3;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
public class A<T> {
private List<T> l1 = new LinkedList<T>();
public void add(T t) {
l1.add(t);
}
public List<T> getL1() {
List<T> l2 = Collections.unmodifiableList(l1);
return l2;
}
public static void main(String[] args) {
A<Integer> a = new A<>();
a.add(1);
a.add(2);
a.add(3);
List<Integer> l1 = a.getL1();
System.out.println(l1);
l1.add(4);
}
}
17.11.3 Collection或Map的同步控制
List<Object> objects = Collections.synchronizedList(new ArrayList<>());
快速报错:Java容器有一种保护机制,能够防止多个进程同时修改同一个容器的内容。如果在你迭代某个容器的过程中,另一个进程介入其中,并且插入、删除或修改此容器内的某个对象,那么就会出现问题。Java容器类库采用快速报错机制。它会探查容器上任何除你的进程所进行的操作之外的所有变化,一旦发现其它进程修改了容器,就会立刻抛出ConcurrentModificationException异常。
public class C {
public static void main(String[] args) {
ArrayList<Integer> integers = new ArrayList<>(Arrays.asList(1, 2, 3, 4));
Iterator<Integer> iterator = integers.iterator();
integers.add(5);
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
17.12 持有引用
17.12.1 强引用,软引用,弱引用,虚引用
Java中强、软、弱、虚、终结引用实例详解
https://blog.csdn.net/u011837804/article/details/129471938
get方法获取强引用也需要置为null
https://blog.csdn.net/ScottePerk/article/details/125120183
public class User {
public int targetId;
public User() {
this.targetId = targetId;
}
@Override
public boolean equals(Object o) {
if (o == null) {
return null;
}
User user = (User)o;
if (this.targetId == user.targetId) {
return true;
} else {
return falses;
}
}
@Override
public int hashCode(Object o) {
return targetId;
}
protected void finalize() {
System.out.println("================" + targetId);
}
@Override
public String toString() {
return targetId;
}
}
ReferenceQueue<Object> queue = new ReferenceQueue<>();
WeakReference<Object> pr = new WeakReference<>(new User(111), queue);
System.out.println(queue.poll());
System.gc();
TimeUnit.SECONDS.sleep(1);
System.out.println(queue.poll()); // WeakReference被清理后会被放到ReferenceQueue中,通过poll()方法可以取出来
17.12.2 WeakHashMap
WeakHashMap 详解
https://www.jianshu.com/p/3bf0f420993a
@Configuration
public class BeanConfig {
@Bean(name = "weakHashMap")
public WeakHashMap<Object, Object> weakHashMap() {
return new weakHashMap<>();
}
}
weakHashMap.put(new User(111), new User(111));
weakHashMap.put(new User(222), new User(222));
System.out.println(weakHashMap.get(new User(111))); // 根据hashCode和equals查找对象
System.gc(); // 对象被清理
System.out.println(weakHashMap.get(new User(111)));