java 容器

1. 常用容器分类

①在这里,集合类分为了Map和Collection两个大的类别。

java 容器类应用 java容器类都有哪些_java 容器类应用

java 容器类应用 java容器类都有哪些_数组_02

        图片左上角的那一块灰色里面的四个类(Dictionary、HashTable、Vector、Stack)都是线程安全的,但是它们都是JDK的老的遗留类,现在基本都不怎么使用了,都有了对应的取代类。其中Map是用来代替图片中左上角的那个Dictionary抽象类(Map的官方文档里面有说明)。官方推荐使用Map接口来代替它。同样对于HashTable,官方推荐ConcurrentHashMap来代替。接着下面的Vector是List下面的一个实现类。
        图片最上面的粉红色部分是集合类所有接口关系图。其中Map的结构比较简单,而Collection的结构就相对复杂一些。Collection有三个继承接口:List、Queue和Set。
        接下来绿色部分则是集合类的主要实现类了。这也是我们最经常使用的集合类了。
        最下方的一个整块都是java.util.concurrent包里面的类,按照包名我们就可以知道这个包里面的类都是用来处理Java编程中各种并发场景的

②按照实现接口分类

实现Map接口的有:EnumMap、IdentityHashMap、HashMap、LinkedHashMap、WeakHashMap、TreeMap
实现List接口的有:ArrayList、LinkedList
实现Set接口的有:HashSet、LinkedHashSet、TreeSet
实现Queue接口的有:PriorityQueue、LinkedList、ArrayQueue

③根据底层实现的数据结构分类

底层以数组的形式实现:EnumMap、ArrayList、ArrayQueue
底层以链表的形式实现:LinkedHashSet、LinkedList、LinkedHashMap
底层以hash table的形式实现:HashMap、HashSet、LinkedHashMap、LinkedHashSet、WeakHashMap、IdentityHashMap
底层以红黑树的形式实现:TreeMap、TreeSet
底层以二叉堆的形式实现:PriorityQueue

④并发容器

ConcurrentHashMap、ConcurrentSkipListHashMap
LinkedTransferQueue、CopyOnWriteArrayList、ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue、 LinkedBlockDeque、DelayQueue、SynchronusQueue
ConcurrentSkipListSet、CopyOnWriteArraySet

2.collection

void clear()  
boolean isEmpty()  
int size()  
Object[] toArray() 返回一个数组,该数组包含容器中的所有元素  
<T> T[] toArray(T[] a) 返回一个数组,该数组包含容器中的所有元素。返回结果的运行时类型与参数数组a的类型相同,而不是单纯的Object。  
Iterator<T> iterator()  
boolean contains(T)  
boolean containsAll(Collection<?>)  
boolean add(T)  
boolean addAll(Collection<? extends T>)  
boolean remove(Object)  
boolean removeAll(Collection<?>)  
boolean retainAll(Collection<?>) 与参数中的元素取交集,即在两个集合中都有的元素

3.Iterator

boolean hasNext():如果被迭代的集合,元素还有没被遍历完,则返回true。  
Object next():返回集合里下一个元素。  
void remove():删除集合里上一次next方法返回的元素。  
void forEachRemaining(Consumer action),这是Java 8为Iterator新增的默认方法,该方法可使用Lambda表达式来遍历集合元素。

4.未获支持的操作 UnsupportedOperationException

常见特例:

①Arrays.asList()会生成一个List,它基于一个固定大小的数组,仅支持那些不会改变数组大小的操作,

任何对引起底层数据结构的尺寸进行修改的方法都会产生一个UnsupportedOperationException异常,以表示对未获支持操作的调用。
通常的做法是,把Arrays.asList()的结果作为构造方法的参数传递给任何Collection(或者使用addAll()方法或Collections.addAll()静态方法),这样就可以生成允许使用所有的方法的普通容器。

②Collections.unmodifiableList(*)引起的错误

Collections.unmodifiableList 起到了对modifyList设置权限的目的**。
但是如果你如果不改引用,是可以通过引用normalList来更改其所指向的对象的。

private static void testReadOnly(){
        //1.创建一个list。并且这个list的访问权限未进行设置。
        List<String> normalList = new ArrayList<>(); 
        //2.向其中插入相关的数据。【可行】
        normalList.add("you");
        normalList.add("are");
        normalList.add("boy");
        //3.对list进行设置。使之可读。
        List<String> modifyList = Collections.unmodifiableList(normalList);
        //4.在次插入,出现UnsupportedOperationException错误。
        modifyList.add("hello");
    }

5.散列

①散列原理

假如键没有按照一定的顺序进行保存,那么查询的时候就只能按照顺序进行线性查询,然而,线性查询是最慢的查询方式。 所以,将键值按照一定的顺序排序,并且使用二分查找能购有效的提升速度。
散列在此之上,更近一步,他将键保存在数组中(数组的查询速度最快),用数组来表示键的信息,但是由于Map的容量是可变的,而数组的容量是不变的。
要解决这个问题,数组中存的并不是键本身,而是键对象生成散列码,并根据散列码计算出数组的下标。而这种办法所产生的问题就是下标重复。而我们的解决办法就是配合equals来确定键值。
查询的过程首先就是计算散列码,然后用散列码来查询函数(下标),通常,我们的数组中保存的是值的list,因此,我们计算出散列码之后,通过下表取到的对应部分的list,然后通过equals就可以快速找到键值。

②如果要使用我们自己的类作为键,我们必须同时重写hashCode() 和 equals()两个方法。

HashMap使用equals方法来判断当前的键是否与表中的键相同。
equals()方法需要满足以下5个条件:
自反性 x.equals(x) 一定返回true
对称性 x.equals(y)返回true,则y.equals(x) 也返回true
传递性 x.equals(y)返回true,y.equals(z)返回true,则x.equals(y)返回true
一致性 如果对象中的信息没有改变,x.equals(y)要么一直返回true,要么一直返回false
对任何不是null的x,想x.equals(null)一定返回false

6.强引用 软引用 弱引用 虚引用

①以Reference对象作为弱对象与普通引用间的媒介(代理)可以实现弱引用。

但如果普通引用直接指向弱对象,则弱对象不会被释放。

②SoftReference(软引用) WeakReference(弱引用) PhantomReference(虚引用)由弱到强

③soft reference和weak reference一样, 但被GC回收的时候需要多一个条件: 当系统内存不足时, soft reference指向的object才会被回收.

正因为有这个特性, soft reference比weak reference更加适合做cache objects的reference. 因为它可以尽可能的retain cached objects, 减少重建他们所需的时间和消耗.

④PhantomReference的进入队列之后jvm不会自动回收,需要人为调用clear方法后才最终释放,这是使用PhantomReference的关键。

虚引用常用来做对象清理工作,类似finalize方法。

⑤WeakReference的一个特点是它何时被回收是不可确定的, 因为这是由GC运行的不确定性所确定的.

所以, 一般用weak reference引用的对象是有价值被cache, 而且很容易被重新被构建, 且很消耗内存的对象.
在weak reference指向的对象被回收后, weak reference本身其实也就没有用了. java提供了一个ReferenceQueue来保存这些所指向的对象已经被回收的reference.
用法是在定义WeakReference的时候将一个ReferenceQueue的对象作为参数传入构造函数.
WeakHashMap是一种特殊的Map,用来保存WeakReference。

7.并发容器

①CAS 算法

CAS(Compare-And-Swap) 算法是硬件对于并发的支持,针对多处理器操作而设计的处理器中的一种特殊指令,用于管理对共享数据的并发访问;
CAS 是一种无锁的非阻塞算法的实现;

CAS 包含了三个操作数:  
需要读写的内存值: V  
进行比较的预估值: A  
拟写入的更新值: B  
当且仅当 V == A 时, V = B, 否则,将不做任何操作;

②传统同步容器缺陷

1)synchronized关键字并发低。
2)在一些并发安全的复合操作中会出错。
虽然Vector的方法采用了synchronized进行了同步,但是由于Vector是继承的AbstarctList,因此通过Iterator来访问容器的话,事实上是不需要获取锁就可以访问。
那么显然,由于使用iterator对容器进行访问不需要获取锁,在多线程中就会造成当一个线程删除了元素,由于modCount是AbstarctList的成员变量,因此可能会导致在其他线程中modCount和expectedModCount值不等。
初始时,线程1和线程2中的modCount、expectedModCount都为0,
当线程2通过iterator.remove()删除元素时,会修改modCount值为1,并且会修改线程2中的expectedModCount的值为1, 而此时线程1中的expectedModCount值为0,虽然modCount不是volatile变量,但是照样导致线程1中比expectedModCount和modCount不等,而抛出异常。
因此一般有2种解决办法:
1)在使用iterator迭代的时候使用synchronized或者Lock进行同步;
2)使用并发容器CopyOnWriteArrayList代替ArrayList和Vector。

③JUC并发

java.util.concurrent(简称JUC)包
1)根据具体场景进行设计,尽量避免synchronized,提供并发性。
2)定义了一些并发安全的复合操作,并且保证并发环境下的迭代操作不会出错。

ConcurrentHashMap代替同步的Map(Collections.synchronized(new HashMap())),众所周知,HashMap是根据散列值分段存储的,同步Map在同步的时候锁住了所有的段,而ConcurrentHashMap加锁的时候根据散列值锁住了散列值锁对应的那段,因此提高了并发性能。

ConcurrentHashMap为了提高本身的并发能力,在内部采用了一个叫做Segment的结构,一个Segment其实就是一个类Hash Table的结构,Segment内部维护了一个链表数组,ConcurrentHashMap定位一个元素的过程需要进行两次Hash操作,第一次Hash定位到Segment,第二次Hash定位到元素所在的链表的头部,因此,这一种结构的带来的副作用是Hash的过程要比普通的HashMap要长,但是带来的好处是写操作的时候可以只对元素所在的Segment进行加锁即可,不会影响到其他的Segment, ConcurrentHashMap的key 和 value都不允许null出现。原因在于ConcurrentHashMap不能区分出value是null还是没有map上,相对的HashMap却可以允许null值,在于其使用在单线程环境下,可以使用containKey(key)方法提前判定是否能map上,从而区分这两种情况,但是ConcurrentHashMap在多线程使用上下文中则不能这么判定。

ConcurrentHashMap也增加了对常用复合操作的支持,比如"若没有则添加":putIfAbsent(),替换:replace()。这2个操作都是原子操作。
CopyOnWriteArrayList和CopyOnWriteArraySet分别代替List和Set,主要是在遍历操作为主的情况下来代替同步的List和同步的Set,这也就是上面所述的思路:迭代过程要保证不出错,除了加锁,另外一种方法就是"克隆"容器对象。
ConcurrentLinkedQuerue是一个先进先出的队列。它是非阻塞队列。
ConcurrentSkipListMap可以在高效并发中替代SoredMap(例如用Collections.synchronzedMap包装的TreeMap)。
ConcurrentSkipListSet可以在高效并发中替代SoredSet(例如用Collections.synchronzedSet包装的TreeMap)。

④COW

Copy-On-Write简称COW,是一种用于程序设计中的优化策略。其基本思路是,从一开始大家都在共享同一个内容,当某个人想要修改这个内容的时候,才会真正把内容Copy出去形成一个新的内容然后再改。

通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。
这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。
所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。

JDK提供了CopyOnWriteArrayList和CopyOnWriteArraySet类

CopyOnWrite并发容器用于读多写少的并发场景。
使用CopyOnWriteMap需要注意三件事情:

  1. 减少扩容开销。根据实际需要,初始化CopyOnWriteMap的大小,避免写时CopyOnWriteMap扩容的开销。
  2. 使用批量添加。因为每次添加,容器每次都会进行复制,所以减少添加次数,可以减少容器的复制次数。如使用上面代码里的addBlackList方法。
  3. 数据一致性问题。CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。所以如果你希望写入的的数据,马上能读到,请不要使用CopyOnWrite容器。

8.常用方法

①排序操作(主要针对List接口相关)

reverse(List list):反转指定List集合中元素的顺序
shuffle(List list):对List中的元素进行随机排序(洗牌)
sort(List list):对List里的元素根据自然升序排序
sort(List list, Comparator c):自定义比较器进行排序
swap(List list, int i, int j):将指定List集合中i处元素和j出元素进行交换
rotate(List list, int distance):将所有元素向右移位指定长度,如果distance等于size那么结果不变

public void testSort() {
        System.out.println("原始顺序:" + list);
        
        Collections.reverse(list);
        System.out.println("reverse后顺序:" + list);

        Collections.shuffle(list);
        System.out.println("shuffle后顺序:" + list);
        
        Collections.swap(list, 1, 3);
        System.out.println("swap后顺序:" + list);

        Collections.sort(list);
        System.out.println("sort后顺序:" + list);

        Collections.rotate(list, 1);
        System.out.println("rotate后顺序:" + list);
    }

输出:
原始顺序:[b张三, d孙六, a李四, e钱七, c赵五]
reverse后顺序:[c赵五, e钱七, a李四, d孙六, b张三]
shuffle后顺序:[b张三, c赵五, d孙六, e钱七, a李四]
swap后顺序:[b张三, e钱七, d孙六, c赵五, a李四]
sort后顺序:[a李四, b张三, c赵五, d孙六, e钱七]
rotate后顺序:[e钱七, a李四, b张三, c赵五, d孙六]

②查找和替换(主要针对Collection接口相关)

binarySearch(List list, Object key):使用二分搜索法,以获得指定对象在List中的索引,前提是集合已经排序
max(Collection coll):返回最大元素
max(Collection coll, Comparator comp):根据自定义比较器,返回最大元素
min(Collection coll):返回最小元素
min(Collection coll, Comparator comp):根据自定义比较器,返回最小元素
fill(List list, Object obj):使用指定对象填充
frequency(Collection Object o):返回指定集合中指定对象出现的次数
replaceAll(List list, Object old, Object new):替换

public void testSearch() {
        System.out.println("给定的list:" + list);
        System.out.println("max:" + Collections.max(list));
        System.out.println("min:" + Collections.min(list));
        System.out.println("frequency:" + Collections.frequency(list, "a李四"));
        Collections.replaceAll(list, "a李四", "aa李四");
        System.out.println("replaceAll之后:" + list);
        
        // 如果binarySearch的对象没有排序的话,搜索结果是不确定的
        System.out.println("binarySearch在sort之前:" + Collections.binarySearch(list, "c赵五"));
        Collections.sort(list);
        // sort之后,结果出来了
        System.out.println("binarySearch在sort之后:" + Collections.binarySearch(list, "c赵五"));

        Collections.fill(list, "A");
        System.out.println("fill:" + list);
    }

输出:
给定的list:[b张三, d孙六, a李四, e钱七, c赵五]
max:e钱七
min:a李四
frequency:1
replaceAll之后:[b张三, d孙六, aa李四, e钱七, c赵五]
binarySearch在sort之前:-4
binarySearch在sort之后:2
fill:[A, A, A, A, A]

③同步控制

Collections工具类中提供了多个synchronizedXxx方法,该方法返回指定集合对象对应的同步对象,从而解决多线程并发访问集合时线程的安全问题。
HashSet、ArrayList、HashMap都是线程不安全的,如果需要考虑同步,则使用这些方法。
这些方法主要有:synchronizedSet、synchronizedSortedSet、synchronizedList、synchronizedMap、synchronizedSortedMap。
特别需要指出的是,在使用迭代方法遍历集合时需要手工同步返回的集合。

Map m = Collections.synchronizedMap(new HashMap());
      ...
  Set s = m.keySet();  // Needn't be in synchronized block
      ...
  synchronized (m) {  // Synchronizing on m, not s!
      Iterator i = s.iterator(); // Must be in synchronized block
      while (i.hasNext())
          foo(i.next());
  }

④设置不可变集合

Collections有三类方法可返回一个不可变集合:
emptyXxx():返回一个空的不可变的集合对象
singletonXxx():返回一个只包含指定对象的,不可变的集合对象。
unmodifiableXxx():返回指定集合对象的不可变视图

public void testUnmodifiable() {
        System.out.println("给定的list:" + list);
        List<String> unmodList = Collections.unmodifiableList(list);
        
        unmodList.add("再加个试试!"); // 抛出:java.lang.UnsupportedOperationException
        
        // 这一行不会执行了
        System.out.println("新的unmodList:" + unmodList);
    }

⑤其它

disjoint(Collection<?> c1, Collection<?> c2) - 如果两个指定 collection 中没有相同的元素,则返回 true。
addAll(Collection<? super T> c, T... a) - 一种方便的方式,将所有指定元素添加到指定 collection 中。示范:
Collections.addAll(flavors, "Peaches 'n Plutonium", "Rocky Racoon");
Comparator<T> reverseOrder(Comparator<T> cmp) - 返回一个比较器,它强行反转指定比较器的顺序。
如果指定比较器为 null,则此方法等同于 reverseOrder()(换句话说,它返回一个比较器,该比较器将强行反转实现 Comparable 接口那些对象 collection 上的自然顺序)。

public void testOther() {
        List<String> list1 = new ArrayList<String>();
        List<String> list2 = new ArrayList<String>();
        
        // addAll增加变长参数
        Collections.addAll(list1, "大家好", "你好","我也好");
        Collections.addAll(list2, "大家好", "a李四","我也好");
        
        // disjoint检查两个Collection是否的交集
        boolean b1 = Collections.disjoint(list, list1);
        boolean b2 = Collections.disjoint(list, list2);
        System.out.println(b1 + "\t" + b2);
        
        // 利用reverseOrder倒序
        Collections.sort(list1, Collections.reverseOrder());
        System.out.println(list1);
    }

输出:
true false
[我也好, 大家好, 你好]
```