第十七章:容器深入研究

容器深入研究

  • 重新看一下之前的容器图:

这里写图片描述

  • BlockingQueue将在第21章介绍。
  • ConcurrentMap接口及其实现ConcurrentHashMap也是用于多线程机制的,同样会在第21章介绍。
  • CopyOnWriteArrayListCopyOnWriteArraySet,它们也是用于多线程机制的。
  • EnumSetEnumMap,为使用enum而设计的SetMap的特殊实现,将在第19章中介绍。
  • 还有很多的abstract类,这些类只是部分实现了特定接口的工具。比如你要创建自己的Set,那么并不用从Set接口开始并实现其中的全部方法,只需从AbstractSet继承,然后执行一些创建新类必须的工作即可。不过一般类库已经足够强大,通常可以忽略这些abstract类。
  • 我们来看一下Collections中的填充方法:
import java.util.*;

public class People {
    public static void main(String args[]) throws Exception {
        List<String> list1 = Collections.nCopies(5, "hello");//CopiesList
        System.out.println(list1);
        //因为list1的本质是CopiesList(Collections的内部类),所以使用其他方法会报错。
        //list1.add("world");//UnsupportedOperationException
        List<String> list2 = new ArrayList<String>(list1);
        System.out.println(list2);
        Collections.fill(list2, "world");
        System.out.println(list2);
    }
}
-------------------
[hello, hello, hello, hello, hello]
[hello, hello, hello, hello, hello]
[world, world, world, world, world]
  • 接下来我们看看Abstract类的使用例子:
import java.util.*;

public class Test1 {
    public static void main(String args[]) {
        MyMap m = MyMap.getInstance();
        System.out.println(m);
        for (Map.Entry<String, String> entry : m.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
            entry.setValue("change by miaoch");
        }
        for (Map.Entry<String, String> entry : m.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }
        //m.put("1", "2");//因为没有重写这个方法,所以会直接抛异常。
    }
}
class MyMap extends AbstractMap<String, String> {
    //比如说做某个项目,我们就可以通过读取数据库来初始化这个DATA
    //不过更简单的办法当然是直接创建一个HashMap,这里不过是为了了解Abstract类是如何继承并使用的。
    private static final String[][] DATA = {
            {"hello", "World"},
            {"what is", "you name"},
            {"how are", "you"},
            {"you are", "welcome"}
        };
    private MyMap() {}
    private static MyMap map;//单例模式
    public synchronized static MyMap getInstance() {
        if (map == null) {
            map = new MyMap();
        }
        return map;
    }
    //AbstractMap必须实现entrySet()
    public Set<Map.Entry<String, String>> entrySet() {
        //我们返回一个匿名AbstractSet
        return new AbstractSet<Map.Entry<String, String>>() {
            //AbstractSet必须实现iterator()和size()
            public Iterator<Map.Entry<String, String>> iterator() {
                //我们返回一个匿名Iterator
                return new Iterator<Map.Entry<String,String>>() {
                    //定义一个下标,用于判断位置 这里的设计不好,如果想从某个位置开始迭代可以再改一下
                    int index = -1;
                    //创建一个entry对象,用于next()返回
                    private Map.Entry<String,String> entry = new Map.Entry<String,String>() {
                        public String getKey() {
                            return DATA[index][0];
                        }
                        public String getValue() {
                            return DATA[index][1];
                        }
                        public String setValue(String value) {
                            //抛出异常。一般用在必须要重写,但是目前并不支持的方法。
                            //throw new UnsupportedOperationException();
                            DATA[index][1] = value;
                            return value;
                        }
                    };
                    public boolean hasNext() {
                        return index < size() - 1;
                    }
                    public Map.Entry<String, String> next() {
                        index++;
                        return entry;
                    }
                };
            }
            public int size() {
                return DATA.length;
            }
        };
    }
}
----------------------------
{hello=World, what is=you name, how are=you, you are=welcome}
hello: World
what is: you name
how are: you
you are: welcome
hello: change by miaoch
what is: change by miaoch
how are: change by miaoch
you are: change by miaoch

集合扩展

  • 由于第十一章我已经讲了很多关于集合的东西,此处就再补充一些十一章没有说到的。

SortedSet

  • 我们之前聊过三种Set,分别是HashSetTreeSetLinkedHashSet。而SortedSetSet的子接口,TreeSet就是它的实现类。从名字上我们就可以知道,它是一个有序的集合,我们通过一个例子了解一下它的常用方法:
import java.util.*;

public class People {
    public static void main(String args[]) throws Exception {
        SortedSet<String> set = new TreeSet<String>();
        Collections.addAll(set, ("one two three four five "
                + "six seven eight nine ten").split("\\s"));
        System.out.println(set);
        String low = set.first();//获得第一个元素
        String high = set.last();//获得最后一个元素
        System.out.println(low + " " + high);
        //由于set是用于查找的,随机访问的功能没有那么必要。
        Iterator<String> it = set.iterator();
        for (int i = 0; i <= 6; i++) {
            if (i == 3) low = it.next();
            else if (i == 6) high = it.next();
            else it.next();
        }
        System.out.println(low + " " + high);
        System.out.println(set.subSet(low, high));//子set 包括low 不包括high
        System.out.println(set.subSet(low, "ninf"));//如果该元素不存在也没事,但必须要大于等于low否则会抛异常
        System.out.println(set.headSet(high));//子set,相当于set.subSet(set.first(), high)
        System.out.println(set.tailSet(low));//子set,相当于set.subSet(low, set.last())
    }
}
------------------执行结果
[eight, five, four, nine, one, seven, six, ten, three, two]
eight two
nine six
[nine, one, seven]
[nine]
[eight, five, four, nine, one, seven]
[nine, one, seven, six, ten, three, two]

双向队列

  • 之前讲过LinkedListPriorityQueue可以作为队列的实现类。而DequeQueue的子接口,LinkedList同样也可以作为双向队列Deque的实现类(事实上它就是直接实现Deque的)。
import java.util.*;

public class People {
    public static void main(String args[]) throws Exception {
        Deque<String> queue = new LinkedList<String>();
        Collections.addAll(queue, ("1 2 3 4 5 "
                + "6 7 8 9 10").split("\\s"));
        System.out.println(queue);
        queue.offer("11");//加在队列后部
        queue.offerLast("12");//加在队列后部
        queue.offerFirst("0");//加在队列前部
        System.out.println(queue);
        System.out.println(queue.poll());//出队 出头部
        System.out.println(queue.pollFirst());//出队 出头部
        System.out.println(queue.pollLast());//出队 出尾部
        System.out.println(queue);
        System.out.println(queue.peek());//查看头部
        System.out.println(queue.peekFirst());//查看头部
        System.out.println(queue.peekLast());//查看尾部
        System.out.println(queue);
    }
}
---------------执行结果
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
0
1
12
[2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
2
2
11
[2, 3, 4, 5, 6, 7, 8, 9, 10, 11]

Map

  • 标准的Java类库包含了Map的几种基本实现,包括:HashMapTreeMapLinkedHashMapWeakHashMapConcurrentHashMapIdentityHashMap。它们都有同样的基本接口Map,但是行为特性不同,表现在效率键值对的保存呈现次序对象的保存周期映射表如何在多线程次序中工作判定键等价的策略等等。Map是一种非常重要的编程工具。不考虑类库中的Map实现,我们用最简单的方式来实现Map以加深对Map的理解。
import java.util.*;

public class Test {
    public static void main(String args[]) {
        MMap<String, String> map = new MiaochMap<String, String>(5);
        map.put("1", "111");
        map.put("2", "222");
        map.put("3", "333");
        map.put("4", "444");
        map.put("5", "555");
        //map.put("6", "666");
        System.out.println(map.get("1"));
        System.out.println(map.get("11"));//null
        System.out.println(map);
        //这里要想写的更优雅一点,就必须再定义内部类取代Object[]。
        for (Object[] objs : map) {
            System.out.println(objs[0] + " : " + objs[1]);
        }
    }
}

interface MMap<K, V> extends Iterable<Object[]> {
    int size();
    V get(K key);
    V put(K key, V value);
}
/**
 * 不考虑键重复和扩容的功能
 */
class MiaochMap<K, V> implements MMap<K, V> {
    private Object[][] data;
    private static final int DEFAULT_LENGTH = 10;
    private int size;
    private int length;
    public MiaochMap() {
        this(DEFAULT_LENGTH);
    }
    public MiaochMap(int length) {
        data = new Object[length][2];
        size = 0;
        this.length = length;
    }
    public Iterator<Object[]> iterator() {
        return new Iterator<Object[]>() {
            private int index = 0;
            public boolean hasNext() {
                return index < size;
            }
            public Object[] next() {
                return data[index++];
            }
        };
    }
    public int size() {
        return size;
    }
    @SuppressWarnings("unchecked")
    public V get(K key) {
        Iterator<Object[]> it = iterator();
        while (it.hasNext()) {
            Object[] kv = it.next();
            if (kv[0].equals(key)) {
                return (V) kv[1];
            }
        }
        return null;
    }
    public V put(K key, V value) {
        if (size == length) {
            throw new ArrayIndexOutOfBoundsException();
        }
        data[size++] = new Object[]{key, value};
        return value;
    }
    public String toString() {
        StringBuilder result = new StringBuilder("[");
        Iterator<Object[]> it = iterator();
        while (it.hasNext()) {
            Object[] kv = it.next();
            result.append(kv[0]);
            result.append(" : ");
            result.append(kv[1]);
            result.append(", ");
        }
        if (result.length() > 2) {
            result.delete(result.length() - 2, result.length());
        }
        result.append("]");
        return result.toString();
    }
}
  • 在有了自己对Map的思考后,我们再来看看几大Map实现所关注的一些因素。
性能
  • 这个我在第十一章已经讲过了。比如我们实现的这个Map就有性能的问题。例如我们现在定义了一个10000大小的Map,如果我们要getkey刚好在最后一个位置,那我们岂不是要从头到尾再走一遍?而HashMap则使用了散列函数,由key通过hashCode()得到一个int值以此来对应一个下标。这样就可以显著提高速度。一般没有特殊要求,我们常用HashMap来做为Map的实现,以下是几大Map的特点:
实现类备注
HashMapMap基于散列表的实现(取代了Hashtable)。插入和查询“键值对”的开销是固定的。可以通过构造器设置容量负载因子,以调整容器的性能。
LinkedHashMap类似于HashMap,但是迭代遍历它时,取得“键值对”的顺序是其插入次序,或者是最近最少使用(LRU)的次序。只比HashMap慢一点;而在迭代访问时反而更快,因为它使用链表维护内部次序(HashMap需要先数组再链表重复交替)。
TreeMap基于红黑树的实现。查看“键”或“键值对”时,它们会被排序(次序由Comparable或Comparator决定)。TreeMap的特点在于,所得到的结果是经过排序的。TreeMap是唯一的带有subMap()方法的Map,它可以返回一个子树。
WeakHashMap弱键映射,允许释放映射所指的对象。这是为了解决某类特殊问题而设计的。如果映射之外没有引用指向某个,则此可以被垃圾回收器回收。
ConcurrentHashMap一种线程安全的Map,它不涉及同步加锁。并发章节讨论
IdentityHashMap使用==代替equals()对进行比较的散列映射。专为解决特殊问题而设计的
LinkedHashMap
  • 我们来看一下最近最少使用(LRU)的用法:
import java.util.*;

public class Test {
    public static void main(String args[]) {
        Map<Integer, String> data = new HashMap<Integer, String>();
        for (int i = 0; i < 10; i++) {
            data.put(i, "" + i + i + i);
        }
        Map<Integer, String> map1 = new LinkedHashMap<Integer, String>(data);
        Map<Integer, String> map2 = new LinkedHashMap<Integer, String>(16, 0.75f, true);//分别为容量、负载因子和使用LRU算法
        map2.putAll(data);
        System.out.println(map1);
        System.out.println(map2);
        for (int i = 0; i < 5; i++) {
            map1.get(i);
            map2.get(i);
        }
        System.out.println(map1);
        System.out.println(map2);
        map2.get(0);
        System.out.println(map2);
    }
}
-----------------执行结果
{0=000, 1=111, 2=222, 3=333, 4=444, 5=555, 6=666, 7=777, 8=888, 9=999}
{0=000, 1=111, 2=222, 3=333, 4=444, 5=555, 6=666, 7=777, 8=888, 9=999}
{0=000, 1=111, 2=222, 3=333, 4=444, 5=555, 6=666, 7=777, 8=888, 9=999}
{5=555, 6=666, 7=777, 8=888, 9=999, 0=000, 1=111, 2=222, 3=333, 4=444}
{5=555, 6=666, 7=777, 8=888, 9=999, 1=111, 2=222, 3=333, 4=444, 0=000}

散列和散列码

  • 当我们使用自定义的类型做为HashMapkey时,我们需要注意重写它的hashCode()equals()方法。来看下面一个例子:
import java.util.*;

public class Test {
    public static void main(String args[]) {
        Map<Holder, Integer> m = new HashMap<Holder, Integer>();
        for (int i=0; i<10; i++) {
            m.put(new Holder(i), 9 - i);
        }
        System.out.println(m);
        System.out.println(m.containsKey(new Holder(9)));//false
    }
}
class Holder {
    private int i;
    public Holder(int i) {
        this.i = i;
    }
    /*@Override
    public boolean equals(Object o) {
        return o instanceof Holder 
                && i == ((Holder) o).i;
    }
    @Override
    public int hashCode() {
        return new Integer(i).hashCode();
    }*/
    public String toString() {
        return String.valueOf(i);
    }
}
  • 默认情况下,hashCode()计算的是地址值的散列值,所以上述代码执行结果为false。所以我们要重写hashCode()方法。不过还不够,源代码中还会对两个key进行equals()判断。这是因为两个值不同的key也可能会得到相同的hash值。所以以后如要要使用自定义的类型作为HashMap或者HashMap的子类(或者有关HashSet)的key,则必须重写这两个方法。
  • 一个正确的equals()方法必须满足下列5个条件:
    1. 自反性。对任意不为null的x,x.equals(x)一定返回true。
    2. 对称性。对任意不为null的x,y。x.equals(y)==y.equals(x)。
    3. 传递性。对任意不为null的x,y,z。如果x.equals(y)为true,y.equals(z)为true,则x.equals(z)为true。
    4. 一致性。对任意不为null的x和y。只要对象中用于等价比较的信息没有改变,那么无论调用x.equals(y)多少次,返回的结果应该保持一致。
    5. 对任意不为null的x,x.equals(null) == false。

为速度而散列

  • 关于散列函数速度快的原因,我们已经在第十一章分析过了。现在我们模仿HashMap自定义一个新的SimpleHashMap
import java.util.*;

public class Test {
    public static void main(String args[]) {
        SimpleHashMap<String, String> m = new SimpleHashMap<String, String>();
        for (int i = 0 ; i < 5 ; i++) {
            m.put("" + i, "" + i + i + i);
        }
        System.out.println(m);
        System.out.println(m.get("1"));
        System.out.println(m.put("2", "2222"));//返回旧值
        System.out.println(m);
    }
}
/**
 * 不考虑扩容。数量一大LinkedList就会过长,其查询速度就会受到影响
 */
class SimpleHashMap<K, V> extends AbstractMap<K, V> {
    private static final int SIZE = 997;//为了使散列均匀,通常使用一个质数
    private LinkedList<MapEntry<K, V>>[] buckets;
    @SuppressWarnings("unchecked")
    public SimpleHashMap() {
        buckets = new LinkedList[SIZE];
    }
    public Set<Map.Entry<K, V>> entrySet() {
        Set<Map.Entry<K, V>> set = new HashSet<Map.Entry<K, V>>();
        for (LinkedList<MapEntry<K, V>> bucket : buckets) {
            if (bucket == null) continue;
            for (MapEntry<K, V> entry : bucket) {
                set.add(entry);
            }
        }
        return set;
    }
    //如果写成K key,会有The method get(K) of type SimpleHashMap<K,V> must override or implement a supertype method
    //如果不继承父类的get方法。1是没有意义,2是语法上同样不过关。
    public V get(Object key) {//历史遗留原因 不能写成K key,否则会导致编译失败
        int index = Math.abs(key.hashCode()) % SIZE;//一个超简单的映射关系
        if (buckets[index] == null) return null;
        for (MapEntry<K, V> entry : buckets[index]) {
            if (entry.getKey().equals(key)) {
                return entry.getValue();
            }
        }
        return null;
    }
    public V put(K key, V value) {
        V oldValue = null;
        int index = Math.abs(key.hashCode()) % SIZE;
        if (buckets[index] == null) {
            buckets[index] = new LinkedList<MapEntry<K, V>>();
        }
        MapEntry<K, V> entry = new MapEntry<K, V>(key, value);
        ListIterator<MapEntry<K, V>> it = buckets[index].listIterator();
        boolean found = false;
        while (it.hasNext()) {
            MapEntry<K, V> iPair = it.next();
            if (iPair.getKey().equals(key)) {
                oldValue = iPair.getValue();
                it.set(entry);//其实这里只替换value应该也行,不过改变整个MapEntry更符合逻辑
                found = true;
                break;
            }
        }
        if (!found) {
            buckets[index].add(entry);
        }
        return oldValue;
    }
    static class MapEntry<K, V> implements Map.Entry<K, V> {
        private K key;
        private V value;
        public MapEntry(K key, V value) {
            this.key = key;
            this.value = value;
        }
        public K getKey() {
            return key;
        }
        public V getValue() {
            return value;
        }
        public V setValue(V value) {
            V oldValue = this.value;
            this.value = value;
            return oldValue;
        }
    }
}
------------------执行结果
{2=222, 4=444, 0=000, 1=111, 3=333}
111
222
{2=2222, 4=444, 0=000, 1=111, 3=333}
  • 注意到上述SIZE使用了一个质数。虽然此举能使散列分布的更加均匀,但其不一定能使查询更快。主要原因是除法和取余操作(a%b = a-(a / b)*b )是最慢的操作。如果使用2的整数次方作为散列表的长度,可用掩码代替除法(a % b = a & (b - 1 ))。下面是分析为何可以简化取余操作。
当b是2^5时。b可用表示成 100000
此时a对b取余 其实就是直接取后五位。
因为(a / b)相当于就是右移5位,*b就是再左移5位。如此就是置a右边五位数字为0。
比如a原先是11101101,那么(a / b)*b就是11100000
如此 a - (a / b)*b 就是01101了。
也就相当于1110110100011111 的与操作的结果,如此即可简化取余操作

覆盖hashCode()

  • hashCode()用于对应下标,因此编写一个合理的hashCode()显得尤为重要。hashCode()产生的int值不必是唯一的,但是其产生速度必须快,且尽量是均匀的。当然其必须基于对象的内容生成散列码,不保持一致性会使这个方法毫无意义。
  • 如何生成尽量均匀的散列码呢?以下是一些基本指导:
    1. 给int变量result赋予某个非零值常量,例如17
    2. 为对象内每个有意义的域f(即可用做equals操作的域)计算一个int散列码c(见下表),对于基本类型,通常转成包装类型,直接调用c.hashCode()即可。
    3. 合并计算得到的散列码:result = 37 * result + c
    4. 返回result
    5. 检查hashCode()最后生成的结果,确保相同的对象有相同的散列码。
域类型计算
booleanc = (f?0:1)
byte、char、short或intc = (int)f
longc = (int)(f ^ (f>>>32)) (高32位和第32位做异或操作)
floatc = Float.floatToIntBits(f)
doublelong l = Double.doubleToLongBits(f);c = (int)(l ^ (l>>>32))
Object,其equals()调用了这个域的equals()c = f.hashCode() (如果这个类型也是自定义的,那么我们得先完成这个hashCode()方法)
数组对每个元素应用上述规则

选择接口的不同实现

  • 现在已经知道了,尽管实际上只有四种容器:Map、List、Set和Queue,但是每个接口都有不止一个实现版本,应该如何选择使用哪一个实现呢?

性能测试框架

  • 书上搞了一套测试框架,我也简单跟着做了一下,来看下列框架中的主要三个类:
package test;

/**
 * 单元测试类,需要重写test方法
 */
public abstract class Test<C> {
   String name;
   public Test(String name) {
       this.name = name;
   }
   /**
    * @param container 测试容器对象
    * @param tp 测试参数
    * @return 测试次数
    */
   abstract int test(C container, TestParam tp);
}
package test;

/**
 * 测试参数类
 */
public class TestParam {
    public final int size;//容器大小
    public final int loops;//测试次数
    public TestParam(int size, int loops) {
        this.size = size;
        this.loops = loops;
    }
    public static TestParam[] array(int... values) {
        int size = values.length / 2;
        TestParam[] result = new TestParam[size];
        int n = 0;
        for (int i = 0; i < size; i++) {
            result[i] = new TestParam(values[n++], values[n++]);
        }
        return result;
    }
    public static TestParam[] array(String... values) {
        int[] vals = new int[values.length];
        for (int i = 0; i < vals.length; i++) {
            vals[i] = Integer.parseInt(values[i]);
        }
        return array(vals);
    }
}
package test;

import java.util.List;

/**
 * 测试控制器
 * 里面有很多关于格式化输出的代码,不用细读
 */
public class Tester<C> {
    //默认的size 和 loops
    public static TestParam[] defaultParams = TestParam.array(
            10, 5000, 100, 5000, 1000, 5000, 10000, 500);
    protected C initalize(int size) {
        return container;
    }
    public static int fieldWidth = 8;
    protected C container;
    private String headline = "";
    private List<Test<C>> tests;
    private static String stringField() {
        return "%" + fieldWidth + "s";
    }
    private static String numberField() {
        return "%" + fieldWidth + "d";
    }
    private static int sizeWidth = 5;
    private static String sizeField = "%" + sizeWidth + "s";
    private TestParam[] paramList = defaultParams;

    public Tester(C container, List<Test<C>> tests) {
        this.container = container;
        this.tests = tests;
        if (container != null) {
            headline = container.getClass().getSimpleName();
        }
    }
    public Tester(C container, List<Test<C>> tests, TestParam[] paramList) {
        this(container, tests);
        this.paramList = paramList;
    }
    public void setHeadline(String newHeadline) {
        headline = newHeadline;
    }
    public static<C> void run(C cntnr, List<Test<C>> tests) {
        new Tester<C>(cntnr, tests).timedTest();
    }
    public static<C> void run(C cntnr, List<Test<C>> tests, TestParam[] paramList) {
        new Tester<C>(cntnr, tests, paramList).timedTest();
    }
    public void timedTest() {
        displayHeader();
        //打印平均测试时间
        for (TestParam param : paramList) {
            System.out.printf(sizeField, param.size);
            for (Test<C> test : tests) {
                C kontainer = initalize(param.size);
                long start = System.nanoTime();
                int reps = test.test(kontainer, param);
                long duration = System.nanoTime() - start;
                long timePerRep = duration / reps;
                System.out.printf(numberField(), timePerRep);
            }
            System.out.println();
        }
    }
    //打印标题
    private void displayHeader() {
        int width = fieldWidth * tests.size() + sizeWidth;
        int dashLength = width - headline.length() - sizeWidth;
        StringBuilder head = new StringBuilder(width);
        for (int i = 0; i < dashLength / 2; i++) {
            head.append('-');
        }
        head.append(' ');
        head.append(headline);
        head.append(' ');
        for (int i = 0; i < dashLength / 2; i++) {
            head.append('-');
        }
        System.out.println(head);
        System.out.printf(sizeField, "size");
        for (Test test : tests) {
            System.out.printf(stringField(), test.name);
        }
        System.out.println();
    }
}
  • 以下是针对List所写的测试类:
package test;

import java.util.*;

public class ListPerformance {
    static Random rand = new Random();
    static int reps = 1000;
    //容器为List的测试类列表
    static List<Test<List<Integer>>> tests = 
            new ArrayList<Test<List<Integer>>>();
    static {
        //增加第一个测试单元,add
        tests.add(new Test<List<Integer>>("add") {
            int test(List<Integer> list, TestParam tp) {
                int loops = tp.loops;
                int listSize = tp.size;
                for (int i = 0; i < loops; i++) {
                    list.clear();
                    for (int j = 0; j < listSize; j++) {
                        list.add(j);
                    }
                }
                return loops * listSize;
            }
        });
        //增加第二个测试单元,get
        tests.add(new Test<List<Integer>>("get") {
            int test(List<Integer> list, TestParam tp) {
                int loops = tp.loops * reps;
                int listSize = list.size();
                for (int i = 0; i < loops; i++) {
                    list.get(rand.nextInt(listSize));//[0,listSize)
                }
                return loops;
            }
        });
        //增加第三个测试单元,set
        tests.add(new Test<List<Integer>>("set") {
            int test(List<Integer> list, TestParam tp) {
                int loops = tp.loops * reps;
                int listSize = list.size();
                for (int i = 0; i < loops; i++) {
                    list.set(rand.nextInt(listSize), 47);//[0,listSize)
                }
                return loops;
            }
        });
        //增加第四个测试单元,iteradd
        tests.add(new Test<List<Integer>>("iteradd") {
            int test(List<Integer> list, TestParam tp) {
                final int LOOPS = 1000000;
                int half = list.size() / 2;
                ListIterator<Integer> it = list.listIterator(half);//在迭代器中间插入数据
                for (int i = 0; i < LOOPS; i++) {
                    it.add(47);
                }
                return LOOPS;
            }
        });
        //增加第五个测试单元,insert
        tests.add(new Test<List<Integer>>("insert") {
            int test(List<Integer> list, TestParam tp) {
                int loops = tp.loops;
                for (int i = 0; i < loops; i++) {
                    list.add(5, 47);
                }
                return loops;
            }
        });
        //增加第六个测试单元,remove
        tests.add(new Test<List<Integer>>("remove") {
            int test(List<Integer> list, TestParam tp) {
                int loops = tp.loops;
                int size = tp.size;
                for (int i = 0; i < loops; i++) {
                    list.clear();
                    list.addAll(Collections.nCopies(size, 47));
                    while (list.size() > 5) {
                        list.remove(5);
                    }
                }
                return loops * size - 5;
            }
        });
    }
    static class ListTester extends Tester<List<Integer>> {
        public ListTester(List<Integer> container, List<Test<List<Integer>>> tests) {
            super(container, tests);
        }
        protected List<Integer> initalize(int size) {
            container.clear();
            container.addAll(Collections.nCopies(size, 47));
            return container;
        }
        public static void run(List<Integer> container, List<Test<List<Integer>>> tests) {
            new ListTester(container, tests).timedTest();
        }
    }
    public static void main(String args[]) {
        ListTester.run(new ArrayList<Integer>(), tests);
        ListTester.run(new LinkedList<Integer>(), tests);
        ListTester.run(new Vector<Integer>(), tests);
    }
}
----------------------运行结果:

------------------- ArrayList -------------------
 size     add     get     set iteradd  insert  remove
   10      63      10      11      19     431      69
  100      11      10      10      17     449      24
 1000       9      10      10      91     566      96
10000       7      10      11     807    1805     883
------------------- LinkedList -------------------
 size     add     get     set iteradd  insert  remove
   10      63      21      23      13     143      64
  100       7      32      31       8      70      18
 1000      11     267     262       9      44      14
10000      13    3012    2920       9      65      10
--------------------- Vector ---------------------
 size     add     get     set iteradd  insert  remove
   10      61      14      13      19     460      52
  100      11      13      12      21     453      19
 1000       8      13      12      95     601     103
10000       7      15      13     829    1881     883
  • 测试结果横向比较是没有意义的。我们通过纵向比较,可以得出LinkedList 对于随机访问的能力很差,但是在addinsert或者remove的时候就比较快。(不过要注意insert和remove都是写死在下标为5的位置。如果设在端点会因为LinkedList自身的优化对比较产生影响,设在任意下标又会因为随机访问而产生影响,所以此处设死在5可以规避这两个问题。)另外注意到size小的时候,平均时间偏大。这可能是测试次数较少的原因。从整体上与我们之前所了解的没有出路。关于其余的集合测试类,我就不写了。如果感兴趣模仿第四个类即可。

实用方法

  • 以下列一些感兴趣的工具方法:
import java.util.*;

public class Test {
    public static void main(String args[]) {
        List<Integer> list = new ArrayList<Integer>();
        //这个主要是用来区别方法的使用范围
        Collection<Integer> collection = list;
        Collections.addAll(list, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0);

        System.out.println(Collections.max(collection));
        System.out.println(Collections.min(collection));
        //一个比较器 用于比较离5的距离
        Comparator<Integer> comparator = new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return Math.abs(o1 - 5) - Math.abs(o2 - 5);
            }
        };
        System.out.println(Collections.max(collection, comparator));
        System.out.println(Collections.min(collection, comparator));
        //如果直接使用子列表会对源列表产生影响,此处重新new一个List
        List<Integer> sublist_1_3 = new ArrayList<Integer>(list.subList(1, 3));
        System.out.println(sublist_1_3);//[2,3]
        System.out.println(Collections.indexOfSubList(list, sublist_1_3));//1
        sublist_1_3.remove(0);
        System.out.println(Collections.indexOfSubList(list, sublist_1_3));//2
        sublist_1_3.add(250);
        System.out.println(Collections.indexOfSubList(list, sublist_1_3));//-1
        //lastIndexOfSubList 差不多此处省略
        Collections.replaceAll(list, 3, 2);
        System.out.println(list);
        Collections.replaceAll(list, 2, 520);
        System.out.println(list);
        Collections.reverse(list);
        System.out.println(list);
        Collections.sort(list);
        System.out.println(list);
        Comparator<Integer> com1 = Collections.reverseOrder();//自然顺序的逆转
        Collections.sort(list, com1);
        System.out.println(list);
        Comparator<Integer> com2 = Collections.reverseOrder(comparator);//获得指定比较器的逆转,离5越近的越大
        Collections.sort(list, com2);
        System.out.println(list);
        Collections.rotate(list, 5);//所有元素循环右移5位
        System.out.println(list);
        Collections.shuffle(list);//用自带随机
        System.out.println(list);
        Collections.shuffle(list, new Random(System.currentTimeMillis()));//用自己的随机
        System.out.println(list);
        System.out.println(sublist_1_3);
        Collections.copy(list, sublist_1_3);//从头开始,会覆盖开头元素
        System.out.println(list);
        //Collections.copy(list, Collections.nCopies(11, 1));//越界异常
        Collections.swap(list, 0, 9);//交换位置 相当于list.set(i, list.set(j, list.get(i)));
        System.out.println(list);
        //Collections.fill(list, 1); //填充,为了方便接下去实验 这段我测试完后就注释了
        //System.out.println(list);

        //这个主要是用来区别方法的使用范围
        Collection<Integer> collection2 = sublist_1_3;
        System.out.println(Collections.disjoint(collection, collection2));//false
        Collections.replaceAll(sublist_1_3, 3, 250);
        Collections.replaceAll(sublist_1_3, 250, 333);
        System.out.println(collection);
        System.out.println(collection2);
        System.out.println(Collections.disjoint(collection, collection2));//没有交集 返回true
        System.out.println(Collections.frequency(collection, 520));//返回个数
        System.out.println(Collections.frequency(collection2, 333));
    }
}
  • 这里基本上所有想的到的都有,想不到的也有。所以遇到类似需求先找找jdk中有没有现成的方法。

持有引用

  • java.lang.ref类库中包含了一组类,这些类为垃圾回收提供了更大的灵活性。当存在可能会耗尽内存的大对象时,这些类显得格外有用。
  • 如果想继续持有对某个对象的引用,希望以后还能够访问到该对象,但是也希望能够允许垃圾回收器释放它,这时就应该使用Reference对象。
  • SoftReferenceWeakReferencePhantomReference由强到弱,对应不同级别的“可获得性”。使用SoftReferenceWeakReference时,可以选择是否要将它们放入ReferenceQueue。而PhantomReference只能依赖与ReferenceQueue。下面是一个示例:
package test2;

import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.*;

class VeryBig {
    private static final int SIZE = 10000;
    private long[] la = new long[SIZE];
    private String ident;
    public VeryBig(String id) {
        ident = id;
    }
    public String toString() {
        return ident;
    }
    @Override
    protected void finalize() {
        System.out.println("finalizing " + ident);
    }
}
public class Test {
    private static ReferenceQueue<VeryBig> rq = 
            new ReferenceQueue<VeryBig>();
    public static void checkQueue() {
        Reference<? extends VeryBig> inq = rq.poll();
        if (inq != null) {
            System.out.println("In queue: " + inq.get());
        }
    }
    public static void main(String args[]) throws Exception {
        int size = 10;
        LinkedList<SoftReference<VeryBig>> sa = 
                new LinkedList<SoftReference<VeryBig>>();
        for (int i = 0; i < size; i++) {
            sa.add(new SoftReference<VeryBig>(
                    new VeryBig("Soft " + i), rq));
            System.out.println("Just created: " + sa.getLast());
            checkQueue();
        }
        LinkedList<WeakReference<VeryBig>> wa = 
                new LinkedList<WeakReference<VeryBig>>();
        for (int i = 0; i < size; i++) {
            wa.add(new WeakReference<VeryBig>(
                    new VeryBig("Weak " + i), rq));
            System.out.println("Just created: " + wa.getLast());
            checkQueue();
        }
        SoftReference<VeryBig> s = 
                new SoftReference<VeryBig>(new VeryBig("Soft"));
        WeakReference<VeryBig> w = 
                new WeakReference<VeryBig>(new VeryBig("Weak"));
        System.gc();
        LinkedList<PhantomReference<VeryBig>> pa = 
                new LinkedList<PhantomReference<VeryBig>>();
        for (int i = 0; i < size; i++) {
            pa.add(new PhantomReference<VeryBig>(
                    new VeryBig("Phantom " + i), rq));
            System.out.println("Just created: " + pa.getLast());
            checkQueue();
        }
    }
}
--------执行结果:说实话我也不是很懂,看结果可以发现weak是会被回收的,Phantom也是会被回收的,但是没有加入rq队列,这个是我实验出来的。以后再去了解
Just created: java.lang.ref.SoftReference@15db9742
Just created: java.lang.ref.SoftReference@6d06d69c
Just created: java.lang.ref.SoftReference@7852e922
Just created: java.lang.ref.SoftReference@4e25154f
Just created: java.lang.ref.SoftReference@70dea4e
Just created: java.lang.ref.SoftReference@5c647e05
Just created: java.lang.ref.SoftReference@33909752
Just created: java.lang.ref.SoftReference@55f96302
Just created: java.lang.ref.SoftReference@3d4eac69
Just created: java.lang.ref.SoftReference@42a57993
Just created: java.lang.ref.WeakReference@75b84c92
Just created: java.lang.ref.WeakReference@6bc7c054
Just created: java.lang.ref.WeakReference@232204a1
Just created: java.lang.ref.WeakReference@4aa298b7
Just created: java.lang.ref.WeakReference@7d4991ad
Just created: java.lang.ref.WeakReference@28d93b30
Just created: java.lang.ref.WeakReference@1b6d3586
Just created: java.lang.ref.WeakReference@4554617c
Just created: java.lang.ref.WeakReference@74a14482
Just created: java.lang.ref.WeakReference@1540e19d
finalizing Weak 6
Just created: java.lang.ref.PhantomReference@677327b6
finalizing Weak 3
finalizing Weak 2
In queue: null
finalizing Weak 1
finalizing Weak 0
finalizing Weak
finalizing Weak 9
finalizing Weak 8
finalizing Weak 7
finalizing Weak 5
finalizing Weak 4
Just created: java.lang.ref.PhantomReference@14ae5a5
In queue: null
Just created: java.lang.ref.PhantomReference@7f31245a
In queue: null
Just created: java.lang.ref.PhantomReference@6d6f6e28
In queue: null
Just created: java.lang.ref.PhantomReference@135fbaa4
In queue: null
Just created: java.lang.ref.PhantomReference@45ee12a7
In queue: null
Just created: java.lang.ref.PhantomReference@330bedb4
In queue: null
Just created: java.lang.ref.PhantomReference@2503dbd3
In queue: null
Just created: java.lang.ref.PhantomReference@4b67cf4d
In queue: null
Just created: java.lang.ref.PhantomReference@7ea987ac
In queue: null
  • WeakHashMap则是使用了WeakReference包装key,所以WeakHashMap允许垃圾回收器自动清理键和值(Entry继承于WeakReference)。
import java.util.WeakHashMap;

class Element {
    private String ident;
    private long[] ls = new long[10000];
    public Element(String id) {
        ident = id;
    }
    public String toString() {
        return ident;
    }
    public int hashCode() {
        return ident.hashCode();
    }
    public boolean equals(Object o) {
        return o instanceof Element
                && ((Element) o).ident.equals(ident);
    }
    protected void finalize() {
        System.out.println("Finalizing " + 
                getClass().getSimpleName() + " " + ident);
    }
}
class Key extends Element {
    public Key(String id) {
        super(id);
    }
}
class Value extends Element {
    public Value(String id) {
        super(id);
    }
}
public class Test {
    public static void main(String args[]) {
        int size = 1000;
        Key[] keys = new Key[size];
        WeakHashMap<Key, Value> map = 
                new WeakHashMap<Key, Value>();
        for (int i = 0; i < size; i++) {
            Key k = new Key(Integer.toString(i));
            Value v = new Value(Integer.toString(i));
            if (i % 3 == 0) {
                keys[i] = k;
            }
            map.put(k, v);
        }
        System.gc();
    }
}
-----------存在数组中的没有被GC掉
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值