第17章 容器深入研究

2024-03-04 10:20
文章标签 容器 17 深入研究

本文主要是介绍第17章 容器深入研究,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

第17章 容器深入研究

完整的容器分类法

容器可以分为四大类:List/SetMap/Queue,常用的具体实现是:ArrayList/HashSet/HashMap。

17.2 填充容器

public static void main(String[] args) {List<StringAddress> list = new ArrayList<>();// 只能替换已有的元素,不能新增元素Collections.fill(list, new StringAddress("b"));System.out.println(list);// []// 新增一个List,元素都指向同一个位置list = Collections.nCopies(3, new StringAddress("a"));System.out.println(list);// [com.container.StringAddress@27c170f0 a, com.container.StringAddress@27c170f0 a, com.container.StringAddress@27c170f0 a]Collections.fill(list, new StringAddress("b"));System.out.println(list);// [com.container.StringAddress@5451c3a8 b, com.container.StringAddress@5451c3a8 b, com.container.StringAddress@5451c3a8 b]}

一种Generator解决方案

class CollectionData<T> extends ArrayList<T>{// 填充进CollectionDatapublic CollectionData(Generator<T> generator,int num) {for (int i = 0; i < num; i++) {add(generator.next());}}// 注意:类泛型不能用于静态方法public static <E>CollectionData<E> list(Generator<E> generator,int num){return new CollectionData<>(generator,num);}
}class Proverb implements Generator<String>{// 日常的单调很容易使人精疲力尽String[] strings = "It’s easy to get burned out by the daily grind".split(" ");int next = 0;@Overridepublic String next() {return strings[next++];}
}public static void main(String[] args) {LinkedHashSet/*维持插入的顺序*/<String> set = new LinkedHashSet<>(new CollectionData<>(new Proverb(),5));System.out.println(set); // [It’s, easy, to, get, burned]set.addAll(CollectionData.list(new Proverb(),5));System.out.println(set); // [It’s, easy, to, get, burned]}

Map生成器


class MapData<K,V> extends LinkedHashMap<K,V>{// 一对值public MapData(Generator<Pair<K,V>> generator,int num){for (int i = 0; i < num; i++) {Pair<K, V> next = generator.next();put(next.k,next.v);}}// key和valuepublic MapData(Generator<K> kGenerator,Generator<V> vGenerator,int num){for (int i = 0; i < num; i++) {put(kGenerator.next(),vGenerator.next());}}// 系列key,单一valuepublic MapData(Iterable<K> iterable,V value){iterable.forEach(key -> put(key,value));}
}class Pair<K,V>{public final K k;public final V v;public Pair(K k, V v) {this.k = k;this.v = v;}
}class Letters implements Generator<Pair<Integer,String>>,Iterable<Integer>{private int number = 65;private char aChar = 'A';@Overridepublic Pair<Integer, String> next() {return new Pair<>(number++,String.valueOf(aChar++));}@Overridepublic Iterator<Integer> iterator() {return new Iterator<Integer>() {@Overridepublic boolean hasNext() {return number < 70;}@Overridepublic Integer next() {return number++;}};}
}public static void main(String[] args) {Map<Integer, String> map = new HashMap<>(new MapData<>(new Letters(),5));System.out.println(map);// {65=A, 66=B, 67=C, 68=D, 69=E}map.putAll(new MapData<>(new RandomGenerator.Int(),new RandomGenerator.Str(),5));System.out.println(map);// {65=A, 66=B, 67=C, 68=D, 69=E, -151806374=bc, 1239779180=EF, -735006902=cdE, -95006371=cdEF, 118002227=cdE}map.putAll(new MapData<>(new Letters(),"单一"));System.out.println(map);// {65=单一, 66=单一, 67=单一, 68=单一, 69=单一, -151806374=bc, 1239779180=EF, -735006902=cdE, -95006371=cdEF, 118002227=cdE}}

使用Abstract类

实现容器对应的Abstract容器,并仅产生只读的容器,可以使实现的方法最少。

在该实例中同时使用了享元模式,容器中共享元素。享元模式使对象的一部分具体化,减少了占用空间。

注意:Countries类中的关系

Countries内部类:MyMap
MyMap内部类:Entity	EntrySet
EntrySet内部类:Iter
capitals()方法:产生包含国家和首都的Map容器
names()方法:产生包含国家的List容器
class Countries {public static final String[][] DATA = {{"中国", "北京"}, {"日本", "东京"},{"泰国", "曼谷"}, {"印度", "新德里"}};// 实现抽象Mapprivate static class MyMap extends AbstractMap<String, String> {// 单个实体类// 仅包含对静态数组的引用,而不包含具体的数据static class Entity implements Map.Entry<String, String> {// 通过index控制对DATA的数据访问int index;public Entity(int index) {this.index = index;}@Overridepublic String getKey() {// 实现共享DATA中的数据return DATA[index][0];}@Overridepublic String getValue() {return DATA[index][1];}@Overridepublic String setValue(String value) {throw new RuntimeException("不允许存入");}}// 实现Set类static class EntrySet extends AbstractSet<Map.Entry<String,String>> {// 控制Map的大小int size;public EntrySet(int size) {// 确定Set的大小if (size < 0)this.size = 0;else if (size > DATA.length)this.size = DATA.length;elsethis.size = size;}// 实现自己的迭代器private  class Iter implements Iterator<Map.Entry<String, String>>{// 每个迭代器仅包含一个Entity实体类,其起到视窗的作用,迭代器通过对index的操作,使其指向下一个元素private Entity entity = new Entity(-1);@Overridepublic boolean hasNext() {return entity.index<size-1;}@Overridepublic Entry<String, String> next() {// 移动指针entity.index++;return entity;}}@Overridepublic Iterator<Map.Entry<String,String>> iterator() {return new Iter();}@Overridepublic int size() {return size;}}// 创建静态Setprivate static Set<Map.Entry<String, String>> entrySet = new EntrySet(DATA.length);@Overridepublic Set<Entry<String, String>> entrySet() {return entrySet;}}// 返回指定数量的Mapstatic Map<String,String> select(final int size){return new MyMap(){@Overridepublic Set<Entry<String, String>> entrySet() {return new EntrySet(size);}};}// 所有国家和首都static Map<String, String> map = new MyMap();public static Map<String, String> capitals(){return map;}public static Map<String, String> capitals(int size){return select(size);}// 所有国家名字static List<String> list = new ArrayList<>(map.keySet());public static List<String> names(){return list;}public static List<String> names(int size){return new ArrayList<>(select(size).keySet());}
}public static void main(String[] args) {System.out.println(capitals(3)); // {中国=北京, 日本=东京, 泰国=曼谷}System.out.println(new HashMap<>(capitals(1))); // {中国=北京}System.out.println(names(2)); // [中国, 日本]System.out.println(new ArrayList<>(names(1))); // [中国]System.out.println(capitals().get("中国")); // 北京}

不受尺寸限制的实现

class MyList extends AbstractList<Integer>{private int size;public MyList(int size) {this.size = size;}@Overridepublic Integer get(int index) {return index;}@Overridepublic int size() {return size;}
}class MyMap extends AbstractMap<Integer,String>{private int size;private static char[] chars ={'a','b','c','d'};public MyMap(int size) {this.size = size;}private  class MyEntry implements Entry<Integer,String>{private int index;public MyEntry(int index) {this.index = index;}@Overridepublic Integer getKey() {return index;}@Overridepublic String getValue() {return Integer.toString(index)+chars[index%chars.length];}@Overridepublic String setValue(String value) {throw new UnsupportedOperationException();}}@Overridepublic Set<Entry<Integer, String>> entrySet() {Set<Entry<Integer,String>> set = new LinkedHashSet<>();for (int i = 0; i < size; i++) {set.add(new MyEntry(i));}return set;}
}public static void main(String[] args) {System.out.println(new MyList(20));// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]System.out.println(new MyMap(10));// {0=0a, 1=1b, 2=2c, 3=3d, 4=4a, 5=5b, 6=6c, 7=7d, 8=8a, 9=9b}}

17.3 Collection的功能方法

方法名说明
boolean add(E e)向集合添加元素e,若指定集合元素改变了则返回true
boolean addAll(Collection<? extends E> c)把集合C中的元素全部添加到集合中,若指定集合元素改变返回true
void clear()清空所有集合元素
boolean contains(Object o)判断指定集合是否包含对象o
boolean containsAll(Collection<?> c)判断指定集合是否包含集合c的所有元素
boolean isEmpty()判断指定集合的元素size是否为0
boolean remove(Object o)删除集合中的元素对象o,若集合有多个o元素,则只会删除第一个元素
boolean removeAll(Collection<?> c)删除指定集合包含集合c的元素
boolean retainAll(Collection<?> c)从指定集合中保留包含集合c的元素,其他元素则删除
int size()集合的元素个数
T[] toArray(T[] a)将集合转换为T类型的数组
   public static void main(String[] args) {Collection<String> c1 = new ArrayList<>();Collection<String> c2 = new ArrayList<>(Arrays.asList("e","f","g"));// 添加boolean flag = c1.add("a");System.out.println(flag); // trueSystem.out.println(c1); // [a]flag = c1.addAll(c2);System.out.println(c1); // [a, e, f, g]flag = c1.remove("a"); // trueSystem.out.println(flag); // [e, f, g]System.out.println(c1);flag = c1.removeAll(c2);System.out.println(c1); // []// 包含flag = c1.contains("a");System.out.println(flag); // falseflag = c1.containsAll(c2);System.out.println(flag); // false// 是否为空flag = c1.isEmpty();System.out.println(flag); // true// 长度c1.add("b");int size = c1.size();System.out.println(size); // 1// 转数组String[] strings1 = new String[5];System.out.println(strings1); // [Ljava.lang.String;@27c170f0String[] strings2 = c1.toArray(strings1);System.out.println(strings2); // [Ljava.lang.String;@27c170f0 指向同一个地址System.out.println(Arrays.toString(strings1)); // [b, null, null, null, null]// 清除元素c1.clear();System.out.println(c1); // []}

17.4 可选操作

注意: 这里可能有些绕。

在Collection接口中添加和移除的方法都是可选操作;

什么是可选操作:实现类并没有提供对应方法的功能定义,就Collection接口而言,就是在实现中抛出UnsupportOperationException异常;而该定义,只是在实现层次的操作,并没有提供语法层次的操作。

为什么会出现可选操作?防止接口爆炸,如容器可能分为只读的,可修改的,可添加的,可删除的等,但是大家除add()和remove()外有很多共同点,现在就需要一个抽象实现类,来实现这些方法,但是在抽象实现类中的add()方法并不能有具体所指,所以就通过抛异常的方式来表现(为什么不使用抽象方法呢?多个子类可能add()方法不能使用,使用抽象方法,就不能代码复用)。

大部分可选操作都发生在抽象实现类中,如AbstractList

  public void add(int index, E element) {throw new UnsupportedOperationException();}

最常见的未获支持的操作,都是背后的容器尺寸固定问题。

 public static void main(String[] args) {List<String> list = Arrays.asList("a","b");list.set(1,"c");list.add("d"); // Exception in thread "main" java.lang.UnsupportedOperationException}

asList()方法返回的List是Arrays的内部类,该类没有重写add()方法,只能修改,不能添加。

   public static void main(String[] args) {List<String> list = Arrays.asList("a","b");list = Collections.unmodifiableList(list);list.set(1,"c"); // Exception in thread "main" java.lang.UnsupportedOperationException}
// 只读的List

17.5 List的功能方法

  // List的基本方法public static void basicTest(List<String> list){// 添加元素list.add("a");list.add(1,"b");list.addAll(Arrays.asList("c","d"));list.addAll(4,Arrays.asList("e","f"));// 是否包含boolean contains = list.contains(1);contains = list.containsAll(Arrays.asList("c","d"));// 获得元素String s = list.get(0);int index = list.indexOf("a");// 是否为空boolean isEmpty =list.isEmpty();// 获得迭代器Iterator<String> iterator = list.iterator();// listIterator针对List的迭代器,有更多的功能ListIterator<String> listIterator = list.listIterator();// 从指定索引处迭代集合listIterator = list.listIterator(3);// 前面是否有元素boolean flag = listIterator.hasPrevious();// 下一个索引listIterator.nextIndex();// 前一个元素listIterator.previous();// 前一个索引listIterator.previousIndex();// 删除list.remove(1);list.remove("c");list.removeAll(Arrays.asList("e","f"));// 修改list.set(0,"z");// 移除不在目标集合中的元素list.retainAll(Arrays.asList("c","d"));int size = list.size();list.clear();}// LinkedList的特殊用法public static void testLinkedList(LinkedList<String> linkedList){// 在头部操作元素linkedList.addFirst("a");linkedList.getFirst();linkedList.removeFirst();// 在尾部操作元素linkedList.addLast("b");linkedList.removeLast();}

17.6 Set和存储顺序

类型描述
Set(interface)存入Set的每个元素都必须是唯一的,因为set不保存重复元素。加入Set的元素必须定义equals()方法以确保对象的唯一性。Set和Collection有完全一样的接口。Set接口不保证维护元素的次序。
HashSet *为快速查找而设计的Set。存入HashSet的元素必须定义hashCode()。
TreeSet保持次序的Set,底层为树结构。使用它可以从Set中提取有序的序列。元素必须实现Comparable接口。
LinkedHashSet具有HashSet的查询速度,且内部使用链表维护元素顺序(插入顺序)。于是在使用迭代器遍历Set时,结果会按元素插入顺序显示,元素也必须实现hashCode()方法。

必须为Set创建equals()方法,只有HashSet和LinkHashSet需要hashCode()方法。但是良好的编程风格应该是重写equals()时跟着重写hashCode()。


class SetType{int i;public SetType(int i) {this.i = i;}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;SetType setType = (SetType) o;return i == setType.i;}@Overridepublic String toString() {return Integer.toString(i);}
}class HashSetType extends SetType{public HashSetType(int i) {super(i);}@Overridepublic int hashCode() {return i;}
}class TreeSetType extends SetType implements Comparable<TreeSetType>{public TreeSetType(int i) {super(i);}@Overridepublic int compareTo(TreeSetType o) {// 为什么不使用加减运算?// 因为当正数-负数时可能会超过int的范围return i > o.i?-1:(i == o.i?0:1);}
}// 工具类
class TypesForSet{// 填充setprivate static <T> Set<T> fill(Set<T> set,Class<T> c){try {for (int i = 0; i < 10; i++) {set.add(c.getConstructor(int.class).newInstance(i));}}catch(Exception e){e.printStackTrace();}return set;}public static <T> void test(Set<T> set,Class<T> c){fill(set,c);fill(set,c);System.out.println(set);}public static void main(String[] args) {// 按照hash函数计算后的排序test(new HashSet<>(),HashSetType.class); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]// 按照比较后的排序test(new TreeSet<>(),TreeSetType.class); // [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]// 没有重写hashcode(),可以看到Set认为他们不相等test(new HashSet<>(),SetType.class); // [0, 8, 9, 3, 8, 2, 6, 9, 3, 6, 4, 5, 4, 5, 7, 2, 7, 1, 0, 1]// 没有实现Comparable接口而存入TreeSet会报错test(new TreeSet<>(),SetType.class); // ClassCastException: com.container.SetType cannot be cast to java.lang.Comparable}
}

SortedSet

SortedSet中的元素保证处于排序状态,它是按对象的比较函数排序。

    public static void main(String[] args) {SortedSet<String> sortedSet = new TreeSet<>();// 返回SortedSet的比较函数Comparator<? super String> comparator = sortedSet.comparator();Collections.addAll(sortedSet,"one two three four five six".split(" "));System.out.println(sortedSet); // [five, four, one, six, three, two]String first = sortedSet.first();String last = sortedSet.last();System.out.println("first:"+first+" "+"last:"+last); // first:five last:twoIterator<String> iterator = sortedSet.iterator();// 返回子SetSortedSet<String> subSet = sortedSet.subSet("four", "six");// 返回到。。为止的SetSortedSet<String> headSet = sortedSet.headSet("one");// 返回从。。开始的SetSortedSet<String> tailSet = sortedSet.tailSet("one");}

17.7 队列

除了并发应用Queue目前在JDK5中仅有两个实现:LinkedList和PriorityQueue,他们的差异在于排序行为而不是性能。

public class Test1 {public static void main(String[] args) {test(new LinkedList<>()); // one two three four test(new PriorityQueue<>()); // four one three two // 按优先级排序test(new ArrayBlockingQueue<>(5)); // one two three four test(new ConcurrentLinkedQueue<>()); // one two three four}public static void test(Queue<String> queue){for(String s:"one two three four".split(" ")){queue.offer(s);}while (queue.peek() != null){System.out.print(queue.remove()+" ");}System.out.println();}
}

优先级队列

// 待做列表
class ToDoList extends PriorityQueue<ToDoList.ToDoItem>{static class ToDoItem implements Comparable<ToDoItem>{// 做的事private String str;// 主优先级private char priority;// 次优先级private int secondPriority;public ToDoItem(String str, char priority, int secondPriority) {this.str = str;this.priority = priority;this.secondPriority = secondPriority;}@Overridepublic int compareTo(ToDoItem o) {if (priority > o.priority){return 1;}else if (priority == o.priority){if (secondPriority > o.secondPriority){return 1;}else if (secondPriority == o.secondPriority){return 0;}}return -1;}@Overridepublic String toString() {return priority +" "+secondPriority+" "+str;}}// 添加待做事项public boolean add(String str,char priority,int secondPriority) {return super.add(new ToDoItem(str,priority,secondPriority));}public static void main(String[] args) {ToDoList toDoList = new ToDoList();toDoList.add("学英语",'A',3);toDoList.add("敲代码",'A',1);toDoList.add("玩游戏",'B',1);toDoList.add("睡觉",'B',2);while (!toDoList.isEmpty()){System.out.println(toDoList.remove());}}
}

双向队列

双向队列就像是一个队列,但是可以在两端添加或移除元素,LinkedList支持双向队列的方法,在JDK6中引入了Deque接口来表示双向队列,但是该接口并不常用。

17.8 理解Map

映射表的基本思想是维护键-值关联,其有多种实现,实现的行为特性各不相同,表现在效率/键值对的保存/呈现次序/对象的保存周期/在多线程中的工作/如何判定键的等价。


public class AssociativeArray<K,V> {public static void main(String[] args) {AssociativeArray<String, String> map = new AssociativeArray<String, String>(3);map.put("a", "1");map.put("b", "2");System.out.println(map.get("b"));System.out.println(map);}private Object[][] pairs;private int index;public  AssociativeArray(int length) {pairs = new Object[length][2];}public void	put(K key,V value) {if (index > pairs.length) {throw new RuntimeException("超长");}pairs[index++] = new Object[]{key,value};}public V get(K key) {for(int i = 0;i<pairs.length;i++){if (key.equals(pairs[i][0])) {return (V)pairs[i][1];}}return null;}@Overridepublic String toString() {StringBuilder sb = new StringBuilder();sb.append("[");for(int i = 0;i<pairs.length;i++){sb.append(pairs[i][0]).append(":").append(pairs[i][1]);if (i<pairs.length-1) {sb.append(",");}}sb.append("]");return sb.toString();}
}

性能

当在get()中使用线性搜索时,执行速度会相当地慢,而HashMap使用散列码,来取代对键的缓慢搜索。

public class Test1 {public static void main(String[] args) {test(add(new HashMap<>()));System.out.println("---");test(add(new ConcurrentHashMap<>()));}public static Map<Integer, String> add(Map<Integer, String> map){map.put(0,"a");map.put(1,"b");return map;}public static void test(Map<Integer, String> map){System.out.println(map.getClass().getSimpleName()); // HashMap// 添加集合map.putAll(Collections.singletonMap(2,"c"));// 获得keySystem.out.println(map.keySet()); // [0, 1, 2]// 获得values// 注意:values()返回一个Collection,其中的值有任何变化,也会反映到map中System.out.println(map.values()); // [a, b, c]// 是否包含keySystem.out.println(map.containsKey(1)); // trueSystem.out.println(map.get(1)); // b// 是否包含valueSystem.out.println(map.containsValue("c")); // true// key迭代器Iterator<Integer> iterator = map.keySet().iterator();System.out.println(map.remove(2)); // cmap.clear();System.out.println(map); // {}}
}

SortedMap

SortedMap可以确保键处于排序状态,目前仅有一个实现:TreeMap,其也是Map中可以获得子Map的实现。

 public static void main(String[] args) {SortedMap<String,String> sortedMap = new TreeMap<>();sortedMap.put("one","a");sortedMap.put("two","b");sortedMap.put("four","d");// 排序函数Comparator<? super String> comparator = sortedMap.comparator();// 首尾KeyString firstKey = sortedMap.firstKey();String lastKey = sortedMap.lastKey();// 获得子MapsortedMap.subMap("four","one");sortedMap.headMap("two");sortedMap.tailMap("four");}

LinkedHashMap

为了提高速度LinkedHashMap会散列化所有元素,但是在遍历键值对时,却以插入顺序返回;另外,在构造时可以设定基于最少使用算法。

public class Test1 {public static void main(String[] args) {Map<Integer, String> map = add(new LinkedHashMap<>());System.out.println(map); // {3=c, 1=a, 2=b}map = add(new LinkedHashMap<>(16,0.75f,true));System.out.println(map); // {3=c, 1=a, 2=b}map.get(3);// 在使用后,就会被排到后边System.out.println(map); // {1=a, 2=b, 3=c}}public static Map<Integer, String> add(Map<Integer, String> map){map.put(3,"c");map.put(1,"a");map.put(2,"b");return map;}}

17.9 散列和散列码

// 土拨鼠
class Groundhog{private int number;public Groundhog(int number) {this.number = number;}@Overridepublic String toString() {return "土拨鼠"+number+"号";}
}// 天气预报
class Prediction{private Random random = new Random();private boolean flag = random.nextDouble()>0.5;@Overridepublic String toString() {return flag?"温暖的春天":"寒冷的冬天";}
}public static void main(String[] args) {Map<Groundhog,Prediction> map = new HashMap<>();for (int i = 0; i < 5; i++) {map.put(new Groundhog(i),new Prediction());}System.out.println(map);// {土拨鼠0号=温暖的春天, 土拨鼠1号=温暖的春天, 土拨鼠2号=寒冷的冬天, 土拨鼠3号=寒冷的冬天, 土拨鼠4号=寒冷的冬天}System.out.println(map.containsKey(new Groundhog(2))); // false}

注意: 可以看到通过new Groundhog(2)并不能找到对应的value,因为这里使用的是Object的hashChode()生成散列码,默认使用对象的地址计算,这样,当然不会找到。

同时,我们还需要重写equals()方法。

正确的 equals()方法必须满足下列5 个条件:

自反性:对任意 x,x.equals\(x\)一定返回true。
对称性:对任意x 和y,如果y.equals\(x\)返回true,则x.equals\(y\)也返回true。
传递性:对任意x,y,z,如果有x.equals\(y\)返回ture,y.equals\(z\)返回true,则x.equals\(z\)一定返回true。
一致性:对任意 x 和y,如果对象中用于等价比较的信息没有改变,那么无论调用x.equals\(y\)多少次,返回的结果应该保持一致,要么一直是true,要么一直是false。
对任何不是 null 的x,x.equals\(null\)一定返回false。
// 重写equals和hashCode
class Groundhog{private int number;public Groundhog(int number) {this.number = number;}@Overridepublic boolean equals(Object o) {return o instanceof/*隐含检查了o是否为null*/ Groundhog && ((Groundhog) o).number == number;}@Overridepublic int hashCode() {return number;}@Overridepublic String toString() {return "土拨鼠"+number+"号";}
}
17.9.1 理解hashCode()

使用散列的目的:使用一个对象查找另一个对象。

// 使用一对ArrayList实现一个Map
class SlowMap<K,V> extends AbstractMap<K,V>{private List<K> keys = new ArrayList<>();private List<V> values = new ArrayList<>();@Overridepublic V get(Object key) {if (keys.contains(key)) {return values.get(keys.indexOf(key));}else {return null;}}// 返回旧值或null@Overridepublic V put(K key, V value) {V oldValue = get(key);if (!keys.contains(key)){keys.add(key);values.add(value);}else {values.set(keys.indexOf(key),value);}return oldValue;}// 完整的Map实现需要实现entrySet方法,该方法返回值是一个Entry的Set,需要实现Entry接口@Overridepublic Set<Entry<K, V>> entrySet() {Set<Map.Entry<K,V>> set = new HashSet<>();Iterator<K> kIterator = keys.iterator();Iterator<V> vIterator = values.iterator();while (kIterator.hasNext()){set.add(new MapEntry(kIterator.next(),vIterator.next()));}return set;}private class MapEntry implements Map.Entry<K,V>{private K k;private V v;public MapEntry(K k, V v) {this.k = k;this.v = v;}@Overridepublic K getKey() {return k;}@Overridepublic V getValue() {return v;}@Overridepublic V setValue(V value) {v = value;return v;}@Overridepublic boolean equals(Object o) {// 必须检查键和值,o可能不是该类的实例if (!(o instanceof Map.Entry)) {return false;}Entry entry = (Entry) o;return (k == null ? entry.getKey() == null:k.equals(entry.getKey()))&&(v == null ? entry.getValue() == null : v.equals(entry.getValue()));}// 这里提供了一个hashCode简单的实现// 虽然可以使用,但是并不恰当@Overridepublic int hashCode() {return (k == null?0:k.hashCode()) ^ (v == null?0:v.hashCode());}}
}
17.9.2 为速度而散列

SlowMap使用简单的线性查询,而线性查询是最慢的。

散列码: 数组并不保存键本身,而是通过键对象生成一个数字,将其作为数组的下标,这个数字就是散列码。

冲突: 为解决数组容量被固定的问题,不同的键可以产生相同的下标,这可能会产生冲突。

// 简单的散列Map
class SimpleHashMap<K, V> extends AbstractMap<K, V> {// 确定数组大小private final int SIZE = 997;// 底层储存结构,数组+链表private LinkedList<MapEntry>[] buckets/*水桶*/ = new LinkedList[SIZE];@Overridepublic V get(Object key) {// 获得数组下标int index = key.hashCode() % SIZE;// 该位置是否有链表if (buckets[index] == null) {return null;}// 遍历链表for (MapEntry entry : buckets[index]) {if (entry.getKey().equals(key)) {return entry.getValue();}}return null;}// 返回旧值或null@Overridepublic V put(K key, V value) {// 获得数组下标int index = key.hashCode() % SIZE;// 保证该位置有链表if (buckets[index] == null) {buckets[index] = new LinkedList<>();}V oldValue = null;// 修改或添加的标识boolean flag = false;for (MapEntry entry : buckets[index]) {// 如果含有该值if (entry.getKey().equals(key)) {oldValue = entry.getValue();// 修改值并标识为修改entry.setValue(value);flag = true;break;}}// 如果非修改,就添加元素if (!flag){buckets[index].add(new MapEntry(key,value));}// 返回旧值或nullreturn oldValue;}@Overridepublic Set<Entry<K, V>> entrySet() {Set<Map.Entry<K, V>> set = new HashSet<>();// 双重遍历,将实体添加到Setfor (LinkedList<MapEntry> bucket : buckets) {// 如果没有链表,跳过if (bucket == null) {continue;}for (MapEntry entry : bucket) {set.add(entry);}}return set;}private class MapEntry implements Map.Entry<K, V> {private K k;private V v;public MapEntry(K k, V v) {this.k = k;this.v = v;}@Overridepublic K getKey() {return k;}@Overridepublic V getValue() {return v;}@Overridepublic V setValue(V value) {v = value;return v;}@Overridepublic boolean equals(Object o) {if (!(o instanceof Map.Entry)) {return false;}Entry entry = (Entry) o;return (k == null ? entry.getKey() == null : k.equals(entry.getKey())) &&(v == null ? entry.getValue() == null : v.equals(entry.getValue()));}@Overridepublic int hashCode() {return (k == null ? 0 : k.hashCode()) ^ (v == null ? 0 : v.hashCode());}}
}public static void main(String[] args) {Map<String,String> map = new SimpleHashMap<>();// map.put(null,"a"); // 这里有一个bug,既即不能以null作键map.put("one","a");map.put("two","b");System.out.println(map); // {one=a, two=b}System.out.println(map.get("two")); // b}

散列表中的槽位通常称为桶位,因此将底层结构命名为buckets。桶的数量通常使用质数,但近来实验表明,2的整数次方才是最优的选择,对处理器而言除法与求余数是最慢的操作,近来的选择,可用掩码代替除法。

17.9.3 覆盖hashCode()

注意事项:

  • 无法控制bucket数组的具体下标值;
  • put和get时得到的hashCode必须相同;
  • 不能依赖于易变的数据,当数据产生变化时,会产生不同的hash值;
  • 不能依赖于唯一的值,否则使用数组+链表就没有什么意义了。
  • hashCode应该产生分布均匀的散列码,否则某些区域负载过重。

计算指导

  1. 给int变量result赋予一个常量;

  2. 为对象内有意义的域f计算出散列码c;
    | 域类型 | 计算 |
    | — | — |
    | boolean | c = (f?0:1) |
    | byte/char/shot/int | c=(int)f |
    | long | c=(int)(f^(f>>>32)) |
    | float | c =Float.floatToIntBits(f) |
    | double | long l = Double.doubleToLongBits(f);c=(int)(f^(f>>>32)) |
    | Object | c =f.hashCode() |
    | 数组 | 对每个元素用上述方法 |

合并计算:result = 37 \*result +c;
返回result;
//简单示例
class CountedString {// 创建的string对象都存储在这里private static List<String> list = new ArrayList<>();private String s;// 创建的第几个该对象private int id;public CountedString(String s) {this.s = s;list.add(s);for (String s1 : list) {if (s1.equals(s)) {id++;}}}@Overridepublic boolean equals(Object o) {return o instanceof CountedString && ((CountedString) o).s.equals(s) && id == ((CountedString) o).id;}@Overridepublic int hashCode() {int result = 17;result = 37 * result + s.hashCode();result = 37 * result + id;return result;}@Overridepublic String toString() {return "String:" + s + " " + "id:" + " " + id + "hashCode:" + hashCode();}
}public static void main(String[] args) {Map<CountedString, Integer> map = new HashMap<>();CountedString[] countedStrings = new CountedString[3];for (int i = 0; i < 3; i++) {countedStrings[i] = new CountedString(new String("hi"));map.put(countedStrings[i],i);}System.out.println(map);// {String:hi id: 2hashCode:146448=1, String:hi id: 3hashCode:146449=2, String:hi id: 1hashCode:146447=0}for (int i = 0; i < countedStrings.length; i++) {System.out.println(countedStrings[i]);System.out.println(map.get(countedStrings[i]));/*String:hi id: 1hashCode:1464470String:hi id: 2hashCode:1464481String:hi id: 3hashCode:1464492*/}}
/*
可以看到,正因为hashcode不同,在map中所有元素都被存储了
这里存在的问题时,不可能产生相同的对象
*/
// 另外一个示例,重点在比较
class Individual implements Comparable<Individual>{private static int counter;private final int id = counter++;private String name;public Individual(String name) {this.name = name;}@Overridepublic String toString() {return "宠物:"+name+id+"号 ";}@Overridepublic boolean equals(Object o) {return o instanceof Individual && id == ((Individual) o).id;}@Overridepublic int hashCode() {int result = 17;if (name != null) {result = result *37+name.hashCode();}result = result*37+id;return result;}// 有一个排序序列,如果name不同则按name排序,name相同就按id排序@Overridepublic int compareTo(Individual o) {if (name != null && o.name != null) {int compare = name.compareTo(o.name);if ( compare != 0) {return compare;}}return id > o.id ? 1 :(id == o.id ? 0:-1);}
}public static void main(String[] args) {Set<Individual> set = new TreeSet<>();set.add(new Individual("cat"));set.add(new Individual("dog"));set.add(new Individual("cat"));System.out.println(set);// [宠物:cat0号 , 宠物:cat2号 , 宠物:dog1号 ]}

17.10 选择接口的不同实现

容器实际上只有四种:Map/List/Set/Queue。

容器之间的区别是背后用什么数据结构实现的。

我们可以根据实际使用的需求选择不同的实现。

17.10.1 性能测试框架
// 测试基类
public abstract class Test<C> {// 测试名字public String name;public Test(String name) {this.name = name;}/*** 每个被测的容器应当覆盖该方法* @param container 测试的容器* @param tp 测试参数* @return 测试的数量*/public abstract int test(C container,TestParam tp);
}// 测试参数
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 < result.length; i++) {// 指定容器的长度和迭代的次数// values偶数位是长度,奇数位是迭代次数result[i] = new TestParam(values[n++],values[n++]);}return result;}public static TestParam[] array(String[] values){int[] ints = new int[values.length];for (int i = 0; i < ints.length; i++) {// 将字符串解码为整数,接收八进制和十六进制ints[i] = Integer.decode(values[i]);}return array(ints);}
}// 测试者
// 核心方法
public class Tester<C> {// 字段宽度public static int fieldWidth = 8;// 被测容器长度宽度private static int sizeWidth = 5;// 长度的格式化private static String sizeField = "%" + sizeWidth + "s";// 测试方法的格式化private static String stringField() {return "%" + fieldWidth + "s";}// 测试数据的格式化private static String numberField() {return "%" + fieldWidth + "d";}// 默认测试参数对象集public static TestParam[] defaultParams = TestParam.array(10, 5000, 100, 5000, 1000, 5000, 10000, 500);// 测试参数集private TestParam[] paramList = defaultParams;// 测试集合private List<Test<C>> tests;// 测试容器protected C container;// 标题private String headline = "";// 初始化函数protected C initialize(int size) {return container;}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 headline) {this.headline = headline;}// 打印头部信息protected void displayHeader() {// 标题总宽度int width = fieldWidth * tests.size() + sizeWidth;// 除标题文字的宽度int dashLength = width - headline.length() - 1;// 组合标题StringBuilder head = new StringBuilder(width);for (int i = 0; i < dashLength / 2; i++) {head.append("-");}head.append(" ").append(headline).append(" ");for (int i = 0; i < dashLength / 2; i++) {head.append("-");}// 打印标题System.out.println(head);// 打印类目System.out.format(sizeField, "size");for (Test<C> test : tests) {System.out.format(stringField(), test.name);}System.out.println();}// 核心测试方法protected void timedTest() {// 打印头部displayHeader();// 遍历测试参数集for (TestParam testParam : paramList) {System.out.format(sizeField, testParam.size);// 遍历测试集for (Test<C> test : tests) {// 初始化C c = initialize(testParam.size);// 以纳秒为单位long start = System.nanoTime();int reps = test.test(c, testParam);long duration = System.nanoTime() - start;long timePerRep = duration / reps;System.out.format(numberField(), timePerRep);}// 注意换行位置System.out.println();}}// 对外提供的方法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[] testParams) {new Tester<C>(cntnr, tests, testParams).timedTest();}
}
17.10.2 对List的选择
public class ListPerformance {static Random random = new Random();static int reps = 1000;// ArrayList测试集static List<Test<List<Integer>>> tests = new ArrayList<>();// LinkedList测试集static LinkedList<Test<LinkedList<Integer>>> qtests = new LinkedList<>();static {tests.add(new Test<List<Integer>>("add") {@Overridepublic int test(List<Integer> list, TestParam tp) {for (int i = 0; i < tp.loops; i++) {list.clear();for (int j = 0; j < tp.size; j++) {list.add(j);}}return tp.loops * tp.size;}});tests.add(new Test<List<Integer>>("get") {@Overridepublic int test(List<Integer> list, TestParam tp) {for (int i = 0; i < tp.loops; i++) {list.get(random.nextInt(list.size()));}return tp.loops;}});tests.add(new Test<List<Integer>>("set") {@Overridepublic int test(List<Integer> list, TestParam tp) {for (int i = 0; i < tp.loops; i++) {list.set(random.nextInt(list.size()),i);}return tp.loops;}});tests.add(new Test<List<Integer>>("remove") {@Overridepublic int test(List<Integer> list, TestParam tp) {for (int i = 0; i < list.size(); i++) {if (list.size() >2){list.remove(1);}}return list.size();}});tests.add(new Test<List<Integer>>("insert") {@Overridepublic int test(List<Integer> list, TestParam tp) {for (int i = 0; i < tp.loops; i++) {list.add(8,i);}return tp.loops;}});qtests.add(new Test<LinkedList<Integer>>("addFirst") {@Overridepublic int test(LinkedList<Integer> list, TestParam tp) {for (int i = 0; i < tp.loops; i++) {list.clear();for (int j = 0; j < tp.size; j++) {list.addFirst(i);}}return tp.loops*tp.size;}});qtests.add(new Test<LinkedList<Integer>>("addLast") {@Overridepublic int test(LinkedList<Integer> list, TestParam tp) {for (int i = 0; i < tp.loops; i++) {list.clear();for (int j = 0; j < tp.size; j++) {list.addLast(i);}}return tp.loops*tp.size;}});}
}// List测试
public class ListTester extends Tester<List<Integer>> {public ListTester(List<Integer> container, List<Test<List<Integer>>> tests) {super(container, tests);}// 重写初始化方法// 本方法也很重要,timedTest每次在执行Test.test时都必须初始化,否则,前次执行的结果会影响后执行的结果@Overrideprotected List<Integer> initialize(int size) {container.clear();for (int i = 0; i < size; i++) {container.add(i);}return container;}public static void run(List<Integer> list,List<Test<List<Integer>>> tests){new ListTester(list,tests).timedTest();}public static void main(String[] args) {ListTester.run(new ArrayList<>(),ListPerformance.tests);/*----------------- ArrayList -----------------size     add     get     set  remove  insert10      63     340     224    3880     392100      16     138      83     516     3411000      10      53      78     467     29310000       7      78     217     917    1034100000      11     392     410    8623   10762*/ListTester.run(new LinkedList<>(),ListPerformance.tests);/*----------------- LinkedList -----------------size     add     get     set  remove  insert10      81     133     124    2060     199100      37      88     120     266      581000      15     387     405     272      6210000       9    6323    6115      53      39100000      25  105806   75586      29     162*/ListTester.run(new LinkedList<>(),ListPerformance.qtests);/*----- LinkedList -----sizeaddFirst addLast10      59      41100      15      201000      16      1810000      31      16100000      10      10*/}
}
/*
从测试数据看到,随着容器的长度变大,LinkedList查询的性能明显下降,而ArrayList则是插入的性能明显下降;同时也可以看到,其实在万级以内,差距并不明显
*/
17.10.3 微基准测试的危险

微基准测试理解:对一些小的东西,小数据量的测试。

做这类测试时,应当关注点要单一,但是不要从结果中获得太多的结论,这类结果很容易受其他因素的影响。

17.10.4 对Set的选择
public class SetPerformance {static List<Test<Set<Integer>>> list = new ArrayList<>();static {list.add(new Test<Set<Integer>>("add") {@Overridepublic int test(Set<Integer> set, TestParam tp) {for (int i = 0; i < tp.loops; i++) {set.clear();for (int j = 0; j < tp.size; j++) {set.add(j);}}return tp.loops*tp.size;}});list.add(new Test<Set<Integer>>("contains") {@Overridepublic int test(Set<Integer> set, TestParam tp) {for (int i = 0; i < tp.loops; i++) {set.clear();for (int j = 0; j < tp.size; j++) {set.contains(j);}}return tp.loops*tp.size;}});}public static void main(String[] args) {Tester.run(new HashSet<>(),SetPerformance.list);Tester.run(new TreeSet<>(),SetPerformance.list);Tester.run(new LinkedHashSet<>(),SetPerformance.list);}/*------ HashSet ------size     addcontains10      92      58100      36      231000      20       710000      18       2100000      22       2------ TreeSet ------size     addcontains10     194      57100      53       21000      51       310000      89       4100000     145       3--- LinkedHashSet ---size     addcontains10     140      99100      34       91000      14      1310000      31       8100000      15       7*/
}

本地测试的结果和书上的差距比较大;总体而言,一般选择HashSet,需要排序,则选择TreeSet,如果需要按插入排序,则选择LinkedHashSet。

17.10.5 对Map的选择
public class MapPerformance {static List<Test<Map<Integer,Integer>>> tests = new ArrayList<>();static {tests.add(new Test<Map<Integer, Integer>>("put") {@Overridepublic int test(Map<Integer, Integer> map, TestParam tp) {for (int i = 0; i < tp.loops; i++) {for (int j = 0; j < tp.size; j++) {map.put(j,i);}}return tp.loops * tp.size;}});tests.add(new Test<Map<Integer, Integer>>("get") {@Overridepublic int test(Map<Integer, Integer> map, TestParam tp) {for (int i = 0; i < tp.loops; i++) {for (int j = 0; j < tp.size; j++) {map.put(j,i);}}return tp.loops * tp.size;}});}
}public static void main(String[] args) {Tester.run(new HashMap<>(),MapPerformance.tests);/*------ HashMap ------size     put     get10     119      63100      24      271000      21      1510000      17      12100000      15      19*/Tester.run(new TreeMap<>(),MapPerformance.tests);/*------ TreeMap ------size     put     get10     222     192100      38      331000      86      9610000      86      79100000     123     112*/Tester.run(new LinkedHashMap<>(),MapPerformance.tests);/*--- LinkedHashMap ---size     put     get10      75      99100      29      121000      13      1210000      14      13100000      13      12*/}

相关术语

容量:容器中存放元素的最大数;

尺寸:容器中当前存储的项数;

负载因子:尺寸/容量。

17.11 实用方法

Collections工具类中的实用方法

方法说明
checkedCollection(Collection,Class)检查类型是否符合
max/min(Collection)返回最大/最小元素
max/min(Collection,Comparator)指定比较器,返回
indexOfSubLsit(List source,List target)target在source第一次出现的位置
lastIndexOfSubList(List source,List target)最后一次位置
replaceAll(List,T oldVal,T newVal)新的替换所有旧的元素
reverse(List)反转元素次序
reverseOrder()返回逆转集合排序的比较器
rotate(List,int distance)元素向后移动distance个位置,末尾循环到前面
shuffle(List)打乱排序
sort(Lsit)排序
copy(Lsit dest,List src)src中的元素复制到dest
swap(List,int i,int j)交换指定位置的元素
fill(List,T x)用x替换所有
nCopies(int n,T x)返回n大小的List,不可改变,元素指向x
disjoint(Collection,Collection)没有相同的元素,返回true
frequency(Collection,Object x)包含x的个数
emptyLsit()返回不可变空List
singleton(T x)返回不可变Set,元素是x
list(Enumeration)转换旧式代码为Array List
enumeration(Collection)转为旧式代码
import static java.util.Collections.*;public class Test1 {static <T> void print(T t) {System.out.println(t);}static List<String> list = Arrays.asList("one Two three Four five six one".split(" "));public static void main(String[] args) {print(list); // [one, Two, three, Four, five, six, one]print(disjoint(list, singletonList("Four"))); // falseprint(max(list)); // threeprint(max(list, String.CASE_INSENSITIVE_ORDER)); // Twoprint(indexOfSubList(list, singletonList("one"))); // 0print(lastIndexOfSubList(list, singletonList("one"))); // 6replaceAll(list, "one", "YO");print(list); // [YO, Two, three, Four, five, six, YO]reverse(list);print(list); // [YO, six, five, Four, three, Two, YO]rotate(list, 2);print(list); // [Two, YO, YO, six, five, Four, three]copy(list, Arrays.asList("1 2".split(" ")));print(list); // [1, 2, YO, six, five, Four, three]swap(list, 0, list.size() - 1);print(list); // [three, 2, YO, six, five, Four, 1]shuffle(list, new Random());print(list); // [Four, YO, five, 1, 2, three, six]fill(list, "X");print(list); // // [X, X, X, X, X, X, X]List<String> nCopies = nCopies(3, "no");print(nCopies); // [no, no, no]Enumeration<String> enumeration = enumeration(nCopies);Vector<String> vector = new Vector<>();// 演示枚举的用法while (enumeration.hasMoreElements()){vector.addElement(enumeration.nextElement());}print(list(vector.elements())); // [no, no, no]}
}

List的排序和查询

public class Test1 {static <T> void print(T t) {System.out.println(t);}// 注意:asList()返回的List不可增删static List<String> list = new ArrayList<>(Arrays.asList("one two three four five six one".split(" ")));public static void main(String[] args) {shuffle(list);print(list); // [two, three, one, one, four, six, five]ListIterator<String> listIterator = list.listIterator(list.size()-3);while (listIterator.hasNext()){listIterator.next();listIterator.remove();}print(list); // [two, three, one, one]sort(list,String.CASE_INSENSITIVE_ORDER);int index = binarySearch(list, list.get(2), String.CASE_INSENSITIVE_ORDER);print(index); // 2}
}

设定collection或Map不可更改

public class Test1 {static <T> void print(T t) {System.out.println(t);}static List<String> list = new ArrayList<>(Arrays.asList("one two three four five six one".split(" ")));public static void main(String[] args) {Collection<String> collection = unmodifiableCollection(list);List<String> unmodifiableList = unmodifiableList(Test1.list);Set<Object> unmodifiableSet = unmodifiableSet(new HashSet<>(list));SortedSet<String> unmodifiableSortedSet = unmodifiableSortedSet(new TreeSet<>(list));Map<Object, Object> unmodifiableMap = unmodifiableMap(new HashMap<>());}
}

Collection或Map的同步控制

  public static void main(String[] args) {// 同步容器Collection<Object> synchronizedCollection = Collections.synchronizedCollection(new ArrayList<>());List<Object> synchronizedList = Collections.synchronizedList(new ArrayList<>());Set<Object> synchronizedSet = Collections.synchronizedSet(new HashSet<>());Map<Object, Object> synchronizedMap = Collections.synchronizedMap(new HashMap<>());// 快速报错:当多个进程同时修改同一个容器内容时会报错List<String> list = new ArrayList<>();Iterator<String> it = list.iterator();list.add("a");while (it.hasNext()) {it.next(); // java.util.ConcurrentModificationException}}

17.12 持有引用

对于某些对象,当没有普通引用指向它时,我们希望以后还可以继续使用它,但同时当内存耗尽时,也允许释放该对象;Reference就是解决该问题的类,其有三个实现类SoftReference/WeakReference/PhantomReference,其强度由强到弱。

class VeryBig{private static final int SIZE = 10000;private long[] la = new long[SIZE];private String flag;public VeryBig(String flag){this.flag = flag;}@Overridepublic String toString() {return flag;}@Overrideprotected void finalize() throws Throwable {System.out.println("清理:"+flag);}
}class References{private static ReferenceQueue<VeryBig> rq = new ReferenceQueue<>();public static void checkQueue(){Reference<? extends VeryBig> poll = rq.poll();if (poll != null) {System.out.println("在队列中:"+poll.get());}}public static void main(String[] args) throws InterruptedException {LinkedList<SoftReference<VeryBig>> sa = new LinkedList<>();for (int i = 0; i < 5; i++) {sa.add(new SoftReference<>(new VeryBig(String.valueOf(i)),rq));System.out.println(sa.getLast());/*java.lang.ref.SoftReference@27c170f0java.lang.ref.SoftReference@5451c3a8java.lang.ref.SoftReference@2626b418java.lang.ref.SoftReference@5a07e868java.lang.ref.SoftReference@76ed5528*/checkQueue();}LinkedList<WeakReference<VeryBig>> wa = new LinkedList<>();for (int i = 0; i < 5; i++) {wa.add(new WeakReference<>(new VeryBig(String.valueOf(i)),rq));System.out.println(sa.getLast());/*java.lang.ref.SoftReference@76ed5528java.lang.ref.SoftReference@76ed5528java.lang.ref.SoftReference@76ed5528java.lang.ref.SoftReference@76ed5528java.lang.ref.SoftReference@76ed5528*/checkQueue();}System.gc();/*清理:4清理:3清理:2清理:1清理:0*/Thread.sleep(1000);LinkedList<PhantomReference<VeryBig>> pa = new LinkedList<>();for (int i = 0; i < 5; i++) {pa.add(new PhantomReference<>(new VeryBig(String.valueOf(i)),rq));System.out.println(sa.getLast());checkQueue();/*java.lang.ref.SoftReference@76ed5528在队列中:nulljava.lang.ref.SoftReference@76ed5528在队列中:nulljava.lang.ref.SoftReference@76ed5528在队列中:nulljava.lang.ref.SoftReference@76ed5528在队列中:nulljava.lang.ref.SoftReference@76ed5528在队列中:null*/}}
}
/*
可以看到,当对象被清理后,仍然可以通过Reference访问到,但是此时对象已为null
*/

WeakHashMap


class Element{private String flag;public Element(String flag) {this.flag = flag;}@Overridepublic String toString() {return flag;}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Element element = (Element) o;return flag != null ? flag.equals(element.flag) : element.flag == null;}@Overridepublic int hashCode() {return flag.hashCode();}@Overrideprotected void finalize() throws Throwable {System.out.println(getClass().getSimpleName()+" "+flag);}
}class Key extends Element{public Key(String flag) {super(flag);}
}class Value extends Element{public Value(String flag) {super(flag);}
}public static void main(String[] args) throws InterruptedException {// Key[] keys = new Key[10];List<Key> keys = new ArrayList<>();WeakHashMap<Key,Value> map = new WeakHashMap<>();for (int i = 0; i < 10; i++) {Key k = new Key(Integer.toString(i));Value v = new Value(Integer.toString(i));if (i % 3 == 0) {keys.add(k);}map.put(k, v);}System.gc();/*Key 8Key 7Key 5Key 4Key 2Key 1*/Thread.sleep(1000);}
// 可以看到,没回收三个会跳过一个,因为跳过的被普通引用了。而普通引用的对象,在该方法结束前都不会被清理。
// WeakHashMap用来保存WeakReference,其允许垃圾回收机制在该方法未结束前就清理键和值

17.13 Java1.0/1.1的容器

Vector和Enumeration

  public static void main(String[] args) {// 老版本唯一可以自我扩展的序列Vector<String> vector = new Vector<>(Arrays.asList("a","b","c"));// 老版本迭代器Enumeration<String> elements = vector.elements();while (elements.hasMoreElements()){System.out.println(elements.nextElement());/*abc*/}}

HashTable

与HashMap非常相似,直接使用HashMap即可。

Stack

 public static void main(String[] args) {Stack<String> stack = new Stack<>();for (String s : "a b c".split(" ")) {// 进栈stack.push(s);}// 继承自Vector,具有其的行为// 这是不合理的,因该是组合,而不是继承System.out.println(stack.elementAt(2)); // cwhile (!stack.empty()) {// 出栈System.out.println(stack.pop());/*cba*/}}

这篇关于第17章 容器深入研究的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/772876

相关文章

Spring核心思想之浅谈IoC容器与依赖倒置(DI)

《Spring核心思想之浅谈IoC容器与依赖倒置(DI)》文章介绍了Spring的IoC和DI机制,以及MyBatis的动态代理,通过注解和反射,Spring能够自动管理对象的创建和依赖注入,而MyB... 目录一、控制反转 IoC二、依赖倒置 DI1. 详细概念2. Spring 中 DI 的实现原理三、

K8S(Kubernetes)开源的容器编排平台安装步骤详解

K8S(Kubernetes)是一个开源的容器编排平台,用于自动化部署、扩展和管理容器化应用程序。以下是K8S容器编排平台的安装步骤、使用方式及特点的概述: 安装步骤: 安装Docker:K8S需要基于Docker来运行容器化应用程序。首先要在所有节点上安装Docker引擎。 安装Kubernetes Master:在集群中选择一台主机作为Master节点,安装K8S的控制平面组件,如AP

Spring框架5 - 容器的扩展功能 (ApplicationContext)

private static ApplicationContext applicationContext;static {applicationContext = new ClassPathXmlApplicationContext("bean.xml");} BeanFactory的功能扩展类ApplicationContext进行深度的分析。ApplicationConext与 BeanF

容器编排平台Kubernetes简介

目录 什么是K8s 为什么需要K8s 什么是容器(Contianer) K8s能做什么? K8s的架构原理  控制平面(Control plane)         kube-apiserver         etcd         kube-scheduler         kube-controller-manager         cloud-controlle

17 通过ref代替DOM用来获取元素和组件的引用

重点 ref :官网给出的解释是: ref: 用于注册对元素或子组件的引用。引用将在父组件的$refs 对象下注册。如果在普通DOM元素上使用,则引用将是该元素;如果在子组件上使用,则引用将是组件实例: <!-- vm.$refs.p will be the DOM node --><p ref="p">hello</p><!-- vm.$refs.child will be the c

react笔记 8-17 属性绑定 class绑定 引入图片 循环遍历

1、绑定属性 constructor(){super()this.state={name:"张三",title:'我是一个title'}}render() {return (<div><div>aaaaaaa{this.state.name}<div title={this.state.title}>我是一个title</div></div></div>)} 绑定属性直接使用花括号{}   注

C++ STL关联容器Set与集合论入门

1. 简介 Set(集合)属于关联式容器,也是STL中最实用的容器,关联式容器依据特定的排序准则,自动为其元素排序。Set集合的底层使用一颗红黑树,其属于一种非线性的数据结构,每一次插入数据都会自动进行排序,注意,不是需要排序时再排序,而是每一次插入数据的时候其都会自动进行排序。因此,Set中的元素总是顺序的。 Set的性质有:数据自动进行排序且数据唯一,是一种集合元素,允许进行数学上的集合相

Spring容器上下文

目录 一 什么是spring容器上下文 二 spring容器上下文可以做什么 三 如何使用 1.实现ApplicationContextAware接口 2.代码测试 一 什么是spring容器上下文 你可以把它理解成就是spring容器,它主要用于管理Bean对象,包括bean的生命周期,bean的注入等等。 二 spring容器上下文可以做什么 我们刚刚上面

Java 入门指南:Java 并发编程 —— 并发容器 ConcurrentLinkedDeque

文章目录 ConcurrentLinkedDeque特点构造方法常用方法使用示例注意事项 ConcurrentLinkedDeque ConcurrentLinkedDeque 是 Java 并发工具包(java.util.concurrent 包)中的一个线程安全的双端队列(Deque)实现,实现了 Deque 接口。它使用了链表结构,并且针对高并发环境进行了优化,非常适合

Docker 容器技术:简化 MySQL 主从复制部署与优化

Docker 容器技术:简化 MySQL 主从复制部署与优化 引言 随着大数据和云计算的快速发展,数据库的高可用性、可扩展性和易维护性成为了企业IT架构中的重要考量因素。MySQL 作为一款流行的开源数据库管理系统,其主从复制(Master-Slave Replication)功能为实现数据备份、故障恢复、读取扩展和数据分析提供了强有力的支持。然而,传统的 MySQL 主从复制部署过程复杂且容