第十七章:容器深入研究
来源:互联网 发布:seo零基础 编辑:程序博客网 时间:2024/06/05 09:39
容器深入研究
- 重新看一下之前的容器图:
- BlockingQueue将在第21章介绍。
- ConcurrentMap接口及其实现ConcurrentHashMap也是用于多线程机制的,同样会在第21章介绍。
- CopyOnWriteArrayList和CopyOnWriteArraySet,它们也是用于多线程机制的。
- EnumSet和EnumMap,为使用enum而设计的Set和Map的特殊实现,将在第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: Worldwhat is: you namehow are: youyou are: welcomehello: change by miaochwhat is: change by miaochhow are: change by miaochyou are: change by miaoch
集合扩展
- 由于第十一章我已经讲了很多关于集合的东西,此处就再补充一些十一章没有说到的。
SortedSet
- 我们之前聊过三种Set,分别是HashSet、TreeSet、LinkedHashSet。而SortedSet是Set的子接口,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 twonine six[nine, one, seven][nine][eight, five, four, nine, one, seven][nine, one, seven, six, ten, three, two]
双向队列
- 之前讲过LinkedList、PriorityQueue可以作为队列的实现类。而Deque是Queue的子接口,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]0112[2, 3, 4, 5, 6, 7, 8, 9, 10, 11]2211[2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
Map
- 标准的Java类库包含了Map的几种基本实现,包括:HashMap、TreeMap、LinkedHashMap、WeakHashMap、ConcurrentHashMap、IdentityHashMap。它们都有同样的基本接口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,如果我们要get的key刚好在最后一个位置,那我们岂不是要从头到尾再走一遍?而HashMap则使用了散列函数,由key通过hashCode()得到一个int值以此来对应一个下标。这样就可以显著提高速度。一般没有特殊要求,我们常用HashMap来做为Map的实现,以下是几大Map的特点:
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}
散列和散列码
- 当我们使用自定义的类型做为HashMap的key时,我们需要注意重写它的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个条件:
- 自反性。对任意不为null的x,x.equals(x)一定返回true。
- 对称性。对任意不为null的x,y。x.equals(y)==y.equals(x)。
- 传递性。对任意不为null的x,y,z。如果x.equals(y)为true,y.equals(z)为true,则x.equals(z)为true。
- 一致性。对任意不为null的x和y。只要对象中用于等价比较的信息没有改变,那么无论调用x.equals(y)多少次,返回的结果应该保持一致。
- 对任意不为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}111222{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了。也就相当于11101101 和 00011111 的与操作的结果,如此即可简化取余操作
覆盖hashCode()
- hashCode()用于对应下标,因此编写一个合理的hashCode()显得尤为重要。hashCode()产生的int值不必是唯一的,但是其产生速度必须快,且尽量是均匀的。当然其必须基于对象的内容生成散列码,不保持一致性会使这个方法毫无意义。
- 如何生成尽量均匀的散列码呢?以下是一些基本指导:
- 给int变量result赋予某个非零值常量,例如17
- 为对象内每个有意义的域f(即可用做equals操作的域)计算一个int散列码c(见下表),对于基本类型,通常转成包装类型,直接调用c.hashCode()即可。
- 合并计算得到的散列码:result = 37 * result + c
- 返回result
- 检查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 9610000 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 1410000 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 10310000 7 15 13 829 1881 883
- 测试结果横向比较是没有意义的。我们通过纵向比较,可以得出LinkedList 对于随机访问的能力很差,但是在add和insert或者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对象。
- SoftReference、WeakReference和PhantomReference由强到弱,对应不同级别的“可获得性”。使用SoftReference和WeakReference时,可以选择是否要将它们放入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@15db9742Just created: java.lang.ref.SoftReference@6d06d69cJust created: java.lang.ref.SoftReference@7852e922Just created: java.lang.ref.SoftReference@4e25154fJust created: java.lang.ref.SoftReference@70dea4eJust created: java.lang.ref.SoftReference@5c647e05Just created: java.lang.ref.SoftReference@33909752Just created: java.lang.ref.SoftReference@55f96302Just created: java.lang.ref.SoftReference@3d4eac69Just created: java.lang.ref.SoftReference@42a57993Just created: java.lang.ref.WeakReference@75b84c92Just created: java.lang.ref.WeakReference@6bc7c054Just created: java.lang.ref.WeakReference@232204a1Just created: java.lang.ref.WeakReference@4aa298b7Just created: java.lang.ref.WeakReference@7d4991adJust created: java.lang.ref.WeakReference@28d93b30Just created: java.lang.ref.WeakReference@1b6d3586Just created: java.lang.ref.WeakReference@4554617cJust created: java.lang.ref.WeakReference@74a14482Just created: java.lang.ref.WeakReference@1540e19dfinalizing Weak 6Just created: java.lang.ref.PhantomReference@677327b6finalizing Weak 3finalizing Weak 2In queue: nullfinalizing Weak 1finalizing Weak 0finalizing Weakfinalizing Weak 9finalizing Weak 8finalizing Weak 7finalizing Weak 5finalizing Weak 4Just created: java.lang.ref.PhantomReference@14ae5a5In queue: nullJust created: java.lang.ref.PhantomReference@7f31245aIn queue: nullJust created: java.lang.ref.PhantomReference@6d6f6e28In queue: nullJust created: java.lang.ref.PhantomReference@135fbaa4In queue: nullJust created: java.lang.ref.PhantomReference@45ee12a7In queue: nullJust created: java.lang.ref.PhantomReference@330bedb4In queue: nullJust created: java.lang.ref.PhantomReference@2503dbd3In queue: nullJust created: java.lang.ref.PhantomReference@4b67cf4dIn queue: nullJust created: java.lang.ref.PhantomReference@7ea987acIn 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掉
阅读全文
0 0
- 第十七章:容器深入研究
- 《Java 编程思想》--第十七章:容器深入研究
- 《java编程思想》 第十七章 容器深入研究
- Thinking in Java——第十七章-容器的深入研究之HashMap篇
- 第17章 容器深入研究
- 《Thinkinginjava》第17章-容器深入研究
- 第17章 容器的深入研究
- 容器深入研究
- 容器的深入研究
- 容器深入研究
- 深入研究容器
- 17 容器深入研究
- 深入java--容器深入研究
- 深入研究 STL Deque 容器
- 深入研究 STL Deque 容器
- 深入研究 STL Deque 容器
- 深入研究 STL Deque 容器
- 深入研究 STL Deque 容器
- 排序算法的时间复杂度和空间复杂度
- P2P中的NAT穿越方案简介
- 从日常开发说起,浅谈HTTP协议是做什么的
- js的image()循环创建,src始终为最近创建的那个解决办法(给image.onload传参)
- Javascript之Object.assign()
- 第十七章:容器深入研究
- codeforces 835C(二维前缀和)
- 算法-array-6-ReshapetheMatrix
- cmd命令行显示中文乱码
- Java学习笔记---实现文件随机读写-RandomAccessFile
- ASP.NET Web Pages – 文件
- RobotFramework 使用远程测试库
- tomcat报错:严重: The required Server component failed to start so Tomcat is unable to start. org.apache.
- js空连接点击不跳转