java集合collection

collection

java.util
Collection 这个接口extends自 --java.lang.Iterable接口
+List 接口
-ArrayList 类
-LinkedList 类
-Vector 类 此类是实现同步的
+Queue 接口
+不常用,在此不表.
+Set 接口
+SortedSet 接口
-TreeSet 类
-HashSet
+Map 接口
-HashMap 类 (除了不同步和允许使用 null 键/值之外,与 Hashtable 大致相同.)
-Hashtable 类 此类是实现同步的,不允许使用 null 键值
+SortedMap 接口
-TreeMap 类

  1. 什么是java集合框架体系

(1)在java语言中,java语言的设计者对常用的数据结构和算法做了一些规范(接口)和实现(具体实现接口的类),所有抽象出来的数据结构和操作(算法)统称为java集合框架(java Collection Framework)jcf。
(2)Java程序员在具体应用时,不必考虑数据结构和算法的实现细节,只需要用这些类创建出来一些对象,然后直接应用就可以了,这样就大大提高了编程效率

  1. 结构
  • Collection接口
    • List
      该接口的特点:有序的,可重复的
      实现类:

(1)ArrayList

基于数组实现的,可自动改变大小
非线程安全类
增长方式:按原来的1.5倍进行扩容,加载因子为1
初始长度java6为10,java7为0,java8为10
遍历的三种方式:
for循环 提前把长度赋给一个变量。优化代码
Foreach
迭代器迭代

 //iterator迭代器
Iterator i=list.iterator();
while(i.hasNext()){
System.out.prinln(i.next());
}

(2)LinkedList

基于双向链表实现的
非线程安全的
遍历方式
for循环 提前把长度赋给一个变量。优化代码
Foreach
迭代器迭代

两者的区别:
对于查询来说,ArrayList有下标,随机访问元素比LinkedList的效率高
对于添加,删除来说,操作add和remove,LinedList比较占优势,因为ArrayList要移动数据

优点:
: 对于查询来说,使用效率更高
对于添加、删除来说,使用LinkedList效率更高
List.removeAll()删除所有元素
指定添加add(下标,值);
添加add();

(3)Vector

底层是对象数组实现
线程安全的类
是同步的
初始容量(内部数组的大小)为10,两倍扩容,加载因子为1

vector声明如下:

public class Vector<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable

Vector继承于AbstractList,实现了List、RandomAccess、Cloneable、 Serializable等接口。

ArrayList实现了List接口,可以对它进行队列操作;实现了RandmoAccess接口,即提供了随机访问功能;实现了Cloneable接口,能被克隆;实现了Serializable接口,因此它支持序列化,能够通过序列化传输。

Vector源码详解:

Vector内部通过一个Object数组来存储数据:

protected Object[] elementData;

Vector使用elementCount变量来表示实际存储的元素个数:

protected int elementCount;

Vector有四个构造方法:

// 创建一个空的Vector,并且指定了Vector的初始容量和扩容时的增长系数
public Vector(int initialCapacity, int capacityIncrement) {
    super();
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal Capacity: "+
                                            initialCapacity);
    this.elementData = new Object[initialCapacity];
    this.capacityIncrement = capacityIncrement;
}
 
// 创建一个空的Vector,并且指定了Vector的初始容量
public Vector(int initialCapacity) {
    this(initialCapacity, 0);
}
 
// 创建一个空的Vector,并且指定了Vector的初始容量为10
public Vector() {
    this(10);
}
 
// 根据其他集合来创建一个非空的Vector
public Vector(Collection<? extends E> c) {
    elementData = c.toArray();
    elementCount = elementData.length;
    // c.toArray might (incorrectly) not return Object[] (see 6260652)
    if (elementData.getClass() != Object[].class)
        elementData = Arrays.copyOf(elementData, elementCount, Object[].class);
}

add方法
Vector有两个重载的Add方法:

// 在数组elementData尾部添加一个元素
public synchronized boolean add(E e)
// 在数组elementData指定位置index处添加元素
public void add(int index, E element)

add(E e)方法
add(E e)方法源码如下:

// 在数组elementData尾部添加一个元素
public synchronized boolean add(E e) {
    modCount++;
    // 容量大小判断
    ensureCapacityHelper(elementCount + 1);
    elementData[elementCount++] = e;
    return true;
}

该方法首先要判断elementData数组的容量是否能够容纳新的元素,若不能,则需要进行扩容操作,然后将元素e放置在数组的size位置。ensureCapacityHelper(int)方法源码如下:

private void ensureCapacityHelper(int minCapacity) {
    // overflow-conscious code
    // 增加元素后,ArrayList中要存储的元素个数为minCapacity
    // 若此时minCapacity > elementData原始的容量,则要按照minCapacity进行扩容
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

扩容的最终操作是通过grow(int)方法来实现的:

private void grow(int minCapacity) {
    // overflow-conscious code
    // 获取elementData的原始容量
    int oldCapacity = elementData.length;
    // 计算新的容量
    // 如果在构造方法中设置了capacityIncrement > 0,那么新数组长度就是原数组长度 + capacityIncrement
    // 否则,新数组长度就是原数组长度 * 2
    int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                        capacityIncrement : oldCapacity);
    // 若进行扩容后,capacity仍然比实际需要的小,则新容量更改为实际需要的大小,即minCapacity
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    // 如果新数组的长度比虚拟机能够提供给数组的最大存储空间大,则将新数组长度更改为最大正数:Integer.MAX_VALUE
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // 按照新的容量newCapacity创建一个新数组,然后再将原数组中的内容copy到新数组中
    elementData = Arrays.copyOf(elementData, newCapacity);
}

和ArrayList的扩容步骤很相似,这里不再介绍。

add(int index, E element)方法
add(int index, E element)方法源码如下:

public void add(int index, E element) {
    insertElementAt(element, index);
}

该方式其实是调用了insertElementAt方法:

public synchronized void insertElementAt(E obj, int index) {
    // fail-fast机制
    modCount++;
    // 判断index下标的合法性
    if (index > elementCount) {
        throw new ArrayIndexOutOfBoundsException(index
                                                    + " > " + elementCount);
    }
    // 判断容量大小
    ensureCapacityHelper(elementCount + 1);
    // 数组拷贝,将index到末尾的元素拷贝到index + 1到末尾的位置,将index的位置留出来
    System.arraycopy(elementData, index, elementData, index + 1, elementCount - index);
    elementData[index] = obj;
    elementCount++;
}

remove方法
remove方法在Vector中同样有两种实现方式:

// 根据元素删除
public boolean remove(Object o)
// 根据index下标删除元素
public synchronized E remove(int index)
我们先看remove(Object o)方法。

remove(Object o)方法
remove(Object o)方法源码如下:

public boolean remove(Object o) {
    return removeElement(o);
}

其内部通过removeElement方法来删除元素:

public synchronized boolean removeElement(Object obj) {
    // fail-fast机制
    modCount++;
    // 查找元素obj在数组中的下标
    int i = indexOf(obj);
    // 若下标 >= 0
    if (i >= 0) {
        // 调用removeElementAt(int)方法删除元素
        removeElementAt(i);
        return true;
    }
    return false;
}

我们先来看indexOf(Object)方法:

public int indexOf(Object o) {
    return indexOf(o, 0);
}
public synchronized int indexOf(Object o, int index) {
    // 若要查找的元素为null
    if (o == null) {
        for (int i = index ; i < elementCount ; i++)
            if (elementData[i]==null)
                return i;
    } 
    // 若要查找的元素不为null
    else {
        for (int i = index ; i < elementCount ; i++)
            if (o.equals(elementData[i]))
                return i;
    }
    return -1;
}

Vector查找元素时,是分为元素为null和不为null两种方式来判断的,这也说明Vector允许添加null元素;同时,如果这个元素在Vector中存在多个,则只会找出从index开始,最先出现的那个。

找到元素对应的下标,若下标 >= 0,则说明元素在数组中存在,然后通过removeElementAt(int)方法来删除元素,removeElementAt(int)方法源码如下:

public synchronized void removeElementAt(int index) {
    modCount++;
    // index下标合法性检验
    if (index >= elementCount) {
        throw new ArrayIndexOutOfBoundsException(index + " >= " +
                                                    elementCount);
    }
    else if (index < 0) {
        throw new ArrayIndexOutOfBoundsException(index);
    }
    // 要移动的元素个数
    int j = elementCount - index - 1;
    if (j > 0) {
        // 将index之后的元素向前移动一位
        System.arraycopy(elementData, index + 1, elementData, index, j);
    }
    elementCount--;
    elementData[elementCount] = null; /* to let gc do its work */
}
remove(int index)方法
remove(int index)方法源码如下:

public synchronized E remove(int index) {
    modCount++;
    // index下标合法性检验
    if (index >= elementCount)
        throw new ArrayIndexOutOfBoundsException(index);
    // 获取旧的元素值
    E oldValue = elementData(index);
 
    // 计算需要移动的元素个数
    int numMoved = elementCount - index - 1;
    // 将元素向前移动
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                            numMoved);
    elementData[--elementCount] = null; // Let gc do its work
 
    return oldValue;
}

Vector的相对线程安全
我们前面说过Vector是相对线程安全的,为什么这么说呢?

我们看下面一段代码:

public class VectorTest {
	static class MyThread extends Thread {
		private CountDownLatch countDownLatch;
		private Vector<String> vector;
		private String element;
		
		public MyThread(CountDownLatch countDownLatch, Vector<String> vector, String element) {
			this.countDownLatch = countDownLatch;
			this.vector = vector;
			this.element = element;
		}
		
		@Override
		public void run() {
			super.run();
			
			try {
				if (!vector.contains(element)) {
					// 注意这里
					Thread.sleep(1000);
					vector.add(element);
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			} finally {
				countDownLatch.countDown();
			}
		}
	}
 
	public static void main(String[] args) throws InterruptedException {
		CountDownLatch countDownLatch = new CountDownLatch(2);
		Vector<String> vector = new Vector<>();
		
		MyThread myThread1 = new MyThread(countDownLatch, vector, "abc");
		MyThread myThread2 = new MyThread(countDownLatch, vector, "abc");
		
		myThread1.start();
		myThread2.start();
		
		countDownLatch.await();
		
		int vectorSize = vector.size();
		System.out.println("vector size: " + vectorSize);
		for (int i = 0; i < vectorSize; i++) {
			System.out.println("index " + i + ": " + vector.get(i));
		}
	}
}

运行结果(不唯一):

vector size: 2
index 0: abc
index 1: abc
注意注释处的一段代码,该段代码是判断元素element是否存在,不存在的话,则将其添加到vector之中,如果线程1和线程2同时运行该段代码,设想一下如下情景:

线程1通过vector.contains(element)同步方法来判断元素是否存在,此时,该方法返回false,即表明线程1可以将element元素插入Vector中;但是运行完该方法之后,线程1开始sleep,那这时,线程2开始运行vector.contains(element)同步方法,该方法仍然返回了false,即线程2可以将element元素插入Vector中,然后线程2开始sleep,最终结果,就是线程1和线程2都将元素“abc”添加到了vector之中,这就是我们为什么说Vector是相对线程安全的了。

要解决该问题,需要我们在自己的业务代码代码中进行同步控制,比如将那一段代码修改为如下:

synchronized (vector) {
    if (!vector.contains(element)) {
        Thread.sleep(1000);
        vector.add(element);
    }
}
  • Set
    核心:集
    特点:无序,去重,允许一个null;
    常用的实现类:

(1)hashSet

底层为HashMap的Key,非线程安全类,是不同步的,默认容量大小为16,其加载因子为0.75;元素无下标没有顺序,不能用for循环,只能用foreach和迭代器遍历
API所提供的类,HashSet能自动去重,自定义类不能去重
HashSet的去重条件:两个对象的HashCode一致,两个对象的equals方法返回True;(重写这两个方法)

HashCode()和equals()方法

不管谁相同都能存储成功,只是存储的位置不同

(2)TreeSet

底层是TreeMap的实例(都是由二叉树实现的)(是一个有序的集),
是非线程安全类,是不同步的,
元素无下标
特点:去重,按照字典顺序排序
一般不用自定义类,用自定义类需要重写compareTo方法

// 对书的名称排序(依照unicode编码大小) 
@Override
public int compareTo(Book o){
// return 0 返回0表示集合含有一个元素
// return 2 返回正数表示集合怎么存怎么取
// return -1 返回负数表示集合会倒叙存储
double num =this.bookName.compareTo(o.booName);
return (int) (num==0?this.price-o.price:num);

}

comparable和comparator区别
相同点:

1.comparable和comparator都是用来实现treeset等根据类的自然排序进行排序的集合容器中元素之间的比较,比较算法都是由比较器自己规定

不同点:

1.comparable是让集合元素自身具备比较性,让元素实现comparable接口,覆盖comparaeTo(T o)方法
比较此对象与指定对象的顺序。如果该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数。
2.comparator是让集合具备比较性,在集合初始化时就有了比较放松,定义一个类,实现compare(T o1,T o2)方法
比较用来排序的两个参数。根据第一个参数小于、等于或大于第二个参数分别返回负整数、零或正整数。
将此类的对象作为参数传给treeset等根据类的自然排序进行排序的集合容器的构造函数
举例子:让人根据年龄排序,如果年龄一样根据姓名的自然顺序排序

通过comparable方式

import java.util.Iterator;
import java.util.TreeSet;

/**
 * @author WangShuang
 *
 */
public class Demo {
    public static void main(String[] args) {
        Person p0 = new Person("张三",3);
        Person p = new Person("张三",1);
        Person p1 = new Person("张三",2);
        Person p2 = new Person("张四",2);
        Person p3 = new Person("张四",2);

        TreeSet<Person> treeSet=new TreeSet<Person>();
        treeSet.add(p0);
        treeSet.add(p);
        treeSet.add(p1);
        treeSet.add(p2);
        treeSet.add(p3);

        Iterator<Person> iterator = treeSet.iterator();
        while(iterator.hasNext()){
            Person next = iterator.next();
            System.out.println(next);
        }
    }
}
class Person implements Comparable<Person>{//该接口强制让人具有比较性
    private String name;
    private int age;
    public Person(String name,int age) {
        this.name = name;
        this.age=age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    @Override
    public int compareTo(Person o) {
        if(this.age>o.age){
            return 1;
        }else if(this.age<o.age){
            return -1;
        }else{
            return this.name.compareTo(o.name);
        }
    }
    @Override
    public String toString() {
        return "Person [name=" + name +  ", age=" + age + "]";
    }
}

通过comparator方式

import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;

/**
 *  
 * @author WangShuang
 *
 */
public class Demo {
    public static void main(String[] args) {
        Person p0 = new Person("张三",3);
        Person p = new Person("张三",1);
        Person p1 = new Person("张三",2);
        Person p2 = new Person("张四",2);
        Person p3 = new Person("张四",2);

        TreeSet<Person> treeSet=new TreeSet<>(new Com());
        treeSet.add(p0);
        treeSet.add(p);
        treeSet.add(p1);
        treeSet.add(p2);
        treeSet.add(p3);

        Iterator<Person> iterator = treeSet.iterator();
        while(iterator.hasNext()){
            Person next = iterator.next();
            System.out.println(next);
        }
    }
}
class Com implements Comparator<Person>{//该接口强制让集合具有比较性
    @Override
    public int compare(Person o1, Person o2) {
        if(o1.getAge()>o2.getAge()){
            return 1;
        }else if(o1.getAge()<o2.getAge()){
            return -1;
        }else{
            return o1.getName().compareTo(o2.getName());
        }
    }
}

class Person{
    private String name;
    private int age;
    public Person(String name,int age) {
        this.name = name;
        this.age=age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    @Override
    public String toString() {
        return "Person [name=" + name +  ", age=" + age + "]";
    }
}

(3)LinkedHashSet:非线程安全类,

底层是哈希表和链接列表
初始值16,加载因子0.7
此实现与HashSet的不同在于,后者维护中一个运行于所有 条目的双重链表
无序,不重复(自定义类需要重写hashcode和equals方法)

(4)Queue

队列

HashSet和TreeSet的区别

  • Map
    键值对 键是set类型(无序,去重) 值是collection类型 (可重复,无序)

(1)HashMap

底层实现依赖于map.Entry接口
特点:
非线程安全类
允许使用一个空键和多个空值,键名不能重复
不保证顺序
默认大小为16,增长因子为0.75

实现原理
底层是通过数组和链表实现的,jdk1.8以后 链表长度大于8就开始红黑树化(查询速度更快,插入也更快),在存放k-v时,先计算key的哈希值,用哈希值去table里找有没有跟这个值相同的node,如果没有则直接放入table,有再比较key是否一样,一样就替换掉原来的value,否则将新的节点拼接到哈希值一样的那个节点后面。
加载因子为0.75 初始容量为16 扩容增量为原来的一倍

遍历:(1)不能直接遍历需要通过其所提供的Keyset
方法把键名映射到Set集合的视图上,然后在利用Set集合遍历 的方式取出键名,
取出所有值
(2)通过entrySet()方法也能取出所有键和值的形式
TreeMap
实现了SortedMap接口,底层是通过红黑树实现的
非线程安全类
按照键的类的自然顺序排序

hashmap什么时候用链表,什么时候用红黑树

在元素个数小于8时用单链表,大于8时用红黑树
因为红黑树需要进行左旋,右旋操作, 而单链表不需要,
以下都是单链表与红黑树结构对比。
如果元素小于8个,查询成本高,新增成本低
如果元素大于8个,查询成本低,新增成本高

HashMap之快速失败
为什么HashMap通过迭代器自身的remove或add方法就不会出现迭代器失败?
HashMap所有集合类视图所返回迭代器都是快速失败(fast-fail)的。

在HashMap中,有一个变量modCount来指示集合被修改的次数。在创建Iterator迭代器的时候,会给这个变量赋值给expectedModCount。当集合方法修改集合元素时,例如集合的remove()方法时,此时会修改modCount值,但不会同步修改expectedModCount值。当使用迭代器遍历元素操作时,会首先对比expectedModCount与modCount是否相等。如果不相等,则马上抛出java.util.ConcurrentModificationException异常。而通过Iterator的remove()方法移除元素时,会同时更新expectedModCount的值,将modCount的值重新赋值给expectedModCount,这样下一次遍历时,就不会发抛出ava.util.ConcurrentModificationException异常。

首先看hashmap的remove()方法

public V remove(Object key) {
    Node<K,V> e;
    return (e = removeNode(hash(key), key, null, false, true)) == null ?
        null : e.value;
}

/**
 * Implements Map.remove and related methods
 *
 * @param hash hash for key
 * @param key the key
 * @param value the value to match if matchValue, else ignored
 * @param matchValue if true only remove if value is equal
 * @param movable if false do not move other nodes while removing
 * @return the node, or null if none
 */
final Node<K,V> removeNode(int hash, Object key, Object value,
                           boolean matchValue, boolean movable) {
    Node<K,V>[] tab; Node<K,V> p; int n, index;
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (p = tab[index = (n - 1) & hash]) != null) {
        Node<K,V> node = null, e; K k; V v;
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            node = p;
        else if ((e = p.next) != null) {
            if (p instanceof TreeNode)
                node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
            else {
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key ||
                         (key != null && key.equals(k)))) {
                        node = e;
                        break;
                    }
                    p = e;
                } while ((e = e.next) != null);
            }
        }
        if (node != null && (!matchValue || (v = node.value) == value ||
                             (value != null && value.equals(v)))) {
            if (node instanceof TreeNode)
                ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
            else if (node == p)
                tab[index] = node.next;
            else
                p.next = node.next;
            //在真正移除节点时,修改modCount的值
            ++modCount;
            --size;
            afterNodeRemoval(node);
            return node;
        }
    }
    return null;
}

 /**
 * The number of times this HashMap has been structurally modified
 * Structural modifications are those that change the number of mappings in
 * the HashMap or otherwise modify its internal structure (e.g.,
 * rehash).  This field is used to make iterators on Collection-views of
 * the HashMap fail-fast.  (See ConcurrentModificationException).
 */
transient int modCount;//指示HashMap被修改的次数

当通过remove移除HashMap中的一个元素时,会修改modCount值,其他修改HashMap集合的方法也会修改modCount值。该值在创建迭代器的时候,会赋值给expectedModCount,在迭代器工作的时候,会判定检查modCount值是否修改了。如果该值被修改了,则抛出ConcurrentModificationException异常。HashMap的Value迭代器实现如下:

final class ValueIterator extends HashIterator
    implements Iterator<V> {
    public final V next() { return nextNode().value; }
}

abstract class HashIterator {
    Node<K,V> next;        // next entry to return
    Node<K,V> current;     // current entry
    int expectedModCount;  // for fast-fail
    int index;             // current slot

    HashIterator() {
        //在构造迭代器的时候,将modCount值赋值给expectedModCount
        expectedModCount = modCount;
        Node<K,V>[] t = table;
        current = next = null;
        index = 0;
        if (t != null && size > 0) { // advance to first entry
            do {} while (index < t.length && (next = t[index++]) == null);
        }
    }

    public final boolean hasNext() {
        return next != null;
    }

    final Node<K,V> nextNode() {
        Node<K,V>[] t;
        Node<K,V> e = next;
        //在获取下一个节点前,先判定modCount值是否修改,如果被修改了则抛出ConcurrentModificationException异常,从前面可以知道,当修改了HashMap的时候,都会修改modCount值。
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
        if (e == null)
            throw new NoSuchElementException();
        if ((next = (current = e).next) == null && (t = table) != null) {
            do {} while (index < t.length && (next = t[index++]) == null);
        }
        return e;
    }

    //迭代器的删除操作,会重新给exceptedModCount赋值,因此不会导致fast-fail
    public final void remove() {
        Node<K,V> p = current;
        if (p == null)
            throw new IllegalStateException();
        //先判定modCount值是否被修改了
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
        current = null;
        K key = p.key;
        removeNode(hash(key), key, null, false, false);
        //将modCount值重新赋值给expectedModCount,这样下次迭代时,不会出现fast-fail
        expectedModCount = modCount;
    }
}

可以看到,通过迭代器remove一个元素,虽然会改变modCount值,但会将modCount值重新赋值为expectedModCount,这样下次再迭代时,不会出现fast-fail。而通过HashMap的remove方法会修改modCount值,但不会更新迭代器的expectedModCount值,所以迭代器在迭代操作时,会抛出ConcurrentModificationException异常。

如果从结构上对映射进行修改,除非通过迭代器自身的 remove 或 add 方法,其他任何时间任何方式的修改,迭代器都将抛出 ConcurrentModificationException。因此,面对并发的修改,迭代器很快就会完全失败。注意,迭代器的快速失败行为不能得到保证,一般来说,存在不同步的并发修改时,不能作出任何坚决的保证。快速失败迭代器尽最大努力抛出 ConcurrentModificationException。

(2)Hashtable

该类实现了一个哈希表;
线程安全的类,同步的
不允许空键空值
初始值为11,加载因子0.75

遍历:不能直接遍历,
(1)可以通过Keyset方法把键名映射到Set集合的视图上,然 后在利 用 Set集合遍历 的方式取出键名,取出所有值
(2)通过entrySet()方法也能取出所有键和值的形式
HashTable键值都不能为null
键名不能重复,值可以重复
HashMap的键值允许为空

HashTable基本上和HashMap一模一样
如果涉及到多个线程操作同一个Map,要使用HashTable
如果只有一个线程会操作Map,可以用HashMap

(3)WeakHashMap

继承于AbstractMap,实现了Map接口。

和HashMap一样,WeakHashMap 也是一个散列表,它存储的内容也是键值对(key-value)映射,而且键和值都可以是null。

不过WeakHashMap的键是“弱键”。在 WeakHashMap 中,当某个键不再正常使用时,会被从WeakHashMap中被自动移除。更精确地说,对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的丢弃,这就使该键成为可终止的,被终止,然后被回收。某个键被终止时,它对应的键值对也就从映射中有效地移除了。

这个“弱键”的原理呢?大致上就是,通过WeakReference和ReferenceQueue实现的。 WeakHashMap的key是“弱键”,即是WeakReference类型的;ReferenceQueue是一个队列,它会保存被GC回收的“弱键”。实现步骤是:
(01) 新建WeakHashMap,将“键值对”添加到WeakHashMap中。
实际上,WeakHashMap是通过数组table保存Entry(键值对);每一个Entry实际上是一个单向链表,即Entry是键值对链表。
(02) 当某“弱键”不再被其它对象引用,并被GC回收时。在GC回收该“弱键”时,这个“弱键”也同时会被添加到ReferenceQueue(queue)队列中。
(03) 当下一次我们需要操作WeakHashMap时,会先同步table和queue。table中保存了全部的键值对,而queue中保存被GC回收的键值对;同步它们,就是删除table中被GC回收的键值对。
这就是“弱键”如何被自动从WeakHashMap中删除的步骤了。

和HashMap一样,WeakHashMap是不同步的。可以使用 Collections.synchronizedMap 方法来构造同步的 WeakHashMap

既然有WeakHashMap,那么有WeakHashSet吗? java collections包是没有直接提供WeakHashSet的。

我们可以通过Collections.newSetFromMap(Map<E,Boolean> map)方法可以将任何 Map包装成一个Set。源码如下:

public static <E> Set<E> newSetFromMap(Map<E, Boolean> map) {
        return new SetFromMap<>(map);
    }
 
    /**
     * @serial include
     */
    private static class SetFromMap<E> extends AbstractSet<E>
        implements Set<E>, Serializable
    {
        private final Map<E, Boolean> m;  // The backing map
        private transient Set<E> s;       // Its keySet
 
        SetFromMap(Map<E, Boolean> map) {
            if (!map.isEmpty())
                throw new IllegalArgumentException("Map is non-empty");
            m = map;
            s = map.keySet();
        }
 
        public void clear()               {        m.clear(); }
        public int size()                 { return m.size(); }
        public boolean isEmpty()          { return m.isEmpty(); }
        public boolean contains(Object o) { return m.containsKey(o); }
        public boolean remove(Object o)   { return m.remove(o) != null; }
        public boolean add(E e) { return m.put(e, Boolean.TRUE) == null; }
        public Iterator<E> iterator()     { return s.iterator(); }
        public Object[] toArray()         { return s.toArray(); }
        public <T> T[] toArray(T[] a)     { return s.toArray(a); }
        public String toString()          { return s.toString(); }
        public int hashCode()             { return s.hashCode(); }
        public boolean equals(Object o)   { return o == this || s.equals(o); }
        public boolean containsAll(Collection<?> c) {return s.containsAll(c);}
        public boolean removeAll(Collection<?> c)   {return s.removeAll(c);}
        public boolean retainAll(Collection<?> c)   {return s.retainAll(c);}

就是对传入的map进行了简单的包装

collection常用方法摘要

 boolean add(E e) 向集合中添加元素  
 void clear() 	清空集合    
 boolean contains(Object o) 如果此 collection 包含指定的元素,则返回 true。 
 boolean containsAll(Collection<?> c) 
          如果此 collection 包含指定 collection 中的所有元素,则返回 true。 
 boolean equals(Object o) 
          比较此 collection 与指定对象是否相等。 
 int hashCode() 
          返回此 collection 的哈希码值。 
 boolean isEmpty() 
          如果此 collection 不包含元素,则返回 true。 
 Iterator\<E> iterator() 
          返回在此 collection 的元素上进行迭代的迭代器。 
 boolean remove(Object o) 
          从此 collection 中移除指定元素的单个实例,如果存在的话(可选操作)。 
 boolean removeAll(Collection<?> c) 
          移除此 collection 中那些也包含在指定 collection 中的所有元素(可选操作)。 
 boolean retainAll(Collection<?> c) 
          仅保留此 collection 中那些也包含在指定 collection 的元素(可选操作)。 
 int size() 
          返回此 collection 中的元素数。 
 Object[] toArray() 
          返回包含此 collection 中所有元素的数组。 
\<T> T[] 
 toArray(T[] a) 
          返回包含此 collection 中所有元素的数组;返回数组的运行时类型与指定数组的运行时类型相同 

import java.util.*;

public class fuck{

	public static void main(String[] args){
		//1.创建集合
		Collection c=new ArrayList();//多态,子类指向父类
		
		//2.添加元素
		//Collection只能存储引用类型,而且只能单个存储
		c.add(1);
		c.add(new Object());
		
		//3。常用方法
		System.out.println(c.size());//获取元素的个数,2
		System.out.println(c.isEmpty());//是否为空集合,false
		
		c.clear();
		System.out.println(c.size());//0
		System.out.println(c.isEmpty());//true
		
	}
}

  1. Arrays和collections的区别
    (1)Arrays包含用来操作数组(比如排序和搜索)的各种方法。此类还包含一个允许将数组作为列表来查看的静态工厂.
    (2)Collections是个java.util下的类,它包含有各种有关集合操作的静态方法。他提供一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作(集合的帮助类)。

  2. 集合类没有实现Cloneable和Serializable接口?
    没有
    因为克隆(cloning)或者序列化(serialization)的语义和含义是跟具体的实现相关的。因此应该由集合类的具体实现类来决定如何被克隆或者序列化

  3. 迭代器
    Iterator和ListIterator是Java三个游标中的两个,都是由Java.UTIL包中的集合框架定义的。
    (1)遍历
    使用Iterator,可以遍历所有集合,如Map,List,Set;但只能在向前方向上遍历集合中的元素。
    使用ListIterator,只能遍历List实现的对象,但可以向前和向后遍历集合中的元素。
    (2)添加元素
    Iterator无法向集合中添加元素;而,ListIteror可以向集合添加元素。
    (3)修改元素
    Iterator无法修改集合中的元素;而,ListIterator可以使用set()修改集合中的元素。
    (4)索引
    Iterator无法获取集合中元素的索引;而,使用ListIterator,可以获取集合中元素的索引。

  4. java基础–安全失败和快速失败

public static void main(String[] args) {

Hashtable<String, String> table = new Hashtable<String, String>();
table.put("a", "aa");
table.put("b", "bb");
table.put("c", "cc");
table.remove("c");
Iterator<Entry<String, String>> iterator = table.entrySet().iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next().getValue());
//采用iterator直接进行修改 程序正常
iterator.remove();

//直接从hashtable增删数据就会报错
table.put("d", "dd");

//直接从hashtable增删数据就会报错,hashtable,hashmap等非并发集合,如果在迭代过程中增减了数据,就是快速失败
table.remove("c");
}
System.out.println("-----------");

Lock lock = new ReentrantLock();

//即使加上lock,还是会跑出ConcurrentModificationException异常

lock.lock();
HashMap<String, String> hashmap = new HashMap<String, String>();
hashmap.put("a", "aa");
hashmap.put("b", "bb");
hashmap.put("c", "cc");
Iterator<Entry<String, String>> iterators = hashmap.entrySet().iterator();
while (iterators.hasNext()) {
System.out.println(iterators.next().getValue());
// 正常
iterators.remove();

//直接从hashtable增删数据就会报错。
//hashtable,hashmap等非并发集合,如果在迭代过程中增减了数据,会快速失败 (一检测到修改,马上抛异常) 
//java.util.ConcurrentModificationException
hashmap.remove("c");
}
System.out.println("-----------");

lock.unlock();

ConcurrentHashMap<String, String> map = new ConcurrentHashMap<String, String>();
map.put("a", "aa");
map.put("b", "bb");
map.put("c", "cc");
Iterator<Entry<String, String>> mapiterator = map.entrySet().iterator();
while (mapiterator.hasNext()) {
System.out.println(mapiterator.next().getValue());
map.remove("c");// 正常 并发集合不存在快速失败问题
map.put("c", "cc");// 正常 并发集合不存在快速失败问题
}
System.out.println("-----------");
}

运行该段代码发现,在Hashtable和HashMap的循环迭代过程中在容器对象上做“修改”操作的话,是跑出java.util.ConcurrentModificationException异常,在Iterator上做操作不会异常。但是ConcurrentHashMap在容器对象和Iterator对象上都不会抛异常,这是为什么呢?

(1)首先来介绍两个概念,快速失败和安全失败。
Iterator的安全失败是基于对底层集合做拷贝,因此,它不受源集合上修改的影响。java.util包下面的所有的集合类都是快速失败的,
而java.util.concurrent包下面的所有的类都是安全失败的。
快速失败的迭代器会抛出ConcurrentModificationException异常,而安全失败的迭代器永远不会抛出这样的异常。

(2)我们查看Hashtable、HashMap、ConcurrentHashMap的在Java API底层的entrySet对象发现,三者都做了对当前对象的拷贝,三者的处理方式是一样的,那区别在哪里呢?看看获取下一个entrySet在逻辑上的区别

这是Hashtable、HashMap的

final Node<K,V> nextNode() {
            Node<K,V>[] t;
            Node<K,V> e = next;
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            if (e == null)
                throw new NoSuchElementException();
            if ((next = (current = e).next) == null && (t = table) != null) {
                do {} while (index < t.length && (next = t[index++]) == null);
            }
            return e;
        }


这是ConcurrentHashMap的
public final Map.Entry<K,V> next() {
            Node<K,V> p;
            if ((p = next) == null)
                throw new NoSuchElementException();
            K k = p.key;
            V v = p.val;
            lastReturned = p;
            advance();
            return new MapEntry<K,V>(k, v, map);
        }
/**
         * Advances if possible, returning next valid node, or null if none.
         */
        final Node<K,V> advance() {
            Node<K,V> e;
            if ((e = next) != null)
                e = e.next;
            for (;;) {
                Node<K,V>[] t; int i, n;  // must use locals in checks
                if (e != null)
                    return next = e;
                if (baseIndex >= baseLimit || (t = tab) == null ||
                    (n = t.length) <= (i = index) || i < 0)
                    return next = null;
                if ((e = tabAt(t, i)) != null && e.hash < 0) {
                    if (e instanceof ForwardingNode) {
                        tab = ((ForwardingNode<K,V>)e).nextTable;
                        e = null;
                        pushState(t, i, n);
                        continue;
                    }
                    else if (e instanceof TreeBin)
                        e = ((TreeBin<K,V>)e).first;
                    else
                        e = null;
                }
                if (stack != null)
                    recoverState(n);
                else if ((index = i + baseSize) >= n)
                    index = ++baseIndex; // visit upper slots if present
            }
        }

ConcurrentHashMap中的迭代器主要包括entrySet、keySet、values方法。它们大同小异,这里选择entrySet解释。当我们调用entrySet返回值的iterator方法时,返回的是EntryIterator,在EntryIterator上调用next方法时,最终实际调用到了HashIterator.advance()方法。这个方法在遍历底层数组。在遍历过程中,如果已经遍历的数组上的内容变化了,迭代器不会抛出ConcurrentModificationException异常。如果未遍历的数组上的内容发生了变化,则有可能反映到迭代过程中。这就是ConcurrentHashMap迭代器弱一致的表现。ConcurrentHashMap的弱一致性主要是为了提升效率,是一致性与效率之间的一种权衡。要成为强一致性,就得到处使用锁,甚至是全局锁,这就与Hashtable和同步的HashMap一样了。

最后我们看看JDK中对于快速失败的描述:
注意,此实现不是同步的。如果多个线程同时访问一个哈希映射,而其中至少一个线程从结构上修改了该映射,则它必须 保持外部同步。(结构上的修改是指添加或删除一个或多个映射关系的任何操作;仅改变与实例已经包含的键关联的值不是结构上的修改。)这一般通过对自然封装该映射的对象进行同步操作来完成。如果不存在这样的对象,则应该使用 Collections.synchronizedMap 方法来“包装”该映射。最好在创建时完成这一操作,以防止对映射进行意外的非同步访问,如下所示:
Map m = Collections.synchronizedMap(new HashMap(…));由所有此类的“collection 视图方法”所返回的迭代器都是快速失败 的:在迭代器创建之后,如果从结构上对映射进行修改,除非通过迭代器本身的 remove 方法,其他任何时间任何方式的修改,迭代器都将抛出 ConcurrentModificationException。因此,面对并发的修改,迭代器很快就会完全失败,而不冒在将来不确定的时间发生任意不确定行为的风险。
注意,迭代器的快速失败行为不能得到保证,一般来说,存在非同步的并发修改时,不可能作出任何坚决的保证。快速失败迭代器尽最大努力抛出 ConcurrentModificationException。因此,编写依赖于此异常的程序的做法是错误的,正确做法是:迭代器的快速失败行为应该仅用于检测程序错误。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值