Java编程思想 第十七章 容器深入研究

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)));  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值