Java集合总结

14 篇文章 0 订阅

集合

集合是Java中提供的一种容器,用来存储对象,并且对象的类型可以不一致。
集合和数组的区别

  • 数组的长度是固定的,集合长度是可变的
  • 数组中存储的是同一类型的元素,一个集合可以存储不同类型的对象(利用了泛型,不同类型的对象都会被提升为Object类型)
  • 数组可以存储基本数据类型值,集合只能存储对象

分类
集合按照存储结构,分为单例集合java.util.Collection和双列集合java.util.Map

Collection
Collection是单例集合的最顶层父接口,有两个重要的子接口:java.util.List和java.util.Set。
List(如ArrayList、LinkedList):元素有序、元素可重复
Set(如HashSet、TreeSet):元素无需,元素不可重复

在理解集合之前,需要先了解泛型,因为集合接口中广泛用到了泛型

泛型

泛型,即“参数化类型”。参数的概念在方法调用中用的最多,调用一个方法,传递必要的参数,并在方法中使用该参数。“参数化类型”就是将具体的类型参数化,本来定义一个变量的时候需要指定它的类型,利用泛型后,将变量的类型也可以定义为一个参数,在使用的时候传入具体的类型。

泛型可以定义在类、接口、方法中,分别被称为泛型类、泛型接口、泛型方法。

泛型类

定义在类上的泛型是在创建对象时确定的

/*
* 定义具有泛型的类
* 修饰符 class 类名 <范型变量>{
* 范型变量一般用E,K,V,T;
* }
* */
    
public class MyClass1<E> {

}

使用:

MyClass1<String> c = new MyClass1<String>();
MyClass1<Integer> c = new MyClass1<Integer>();
泛型方法

定义在方法上的泛型是在调用方法时确定的

/*
    泛型方法(方法上含有泛型)
    格式:
        修饰符 <泛型变量> 返回值类型 方法名称(参数列表...) {
            //...
        }
 */
 public class MyClass02<T> {
    //此方法上的泛型不是自己定义的,而是使用的类上的泛型
    public void print(T t) {
        System.out.println(t);
    }
    //泛型方法: 泛型E是在方法上自己定义的
    public <E> void show(E e) {
        System.out.println(e);
    }
    //返回值的类型也定义为泛型
    public <E> E show(E e) {
        System.out.println(e);
    }
}

使用:

MyClass02<Integer> c = new MyClass02<Integer>();
c.print(1);     //只能是整数
c.show("abc");
c.show(123);
c.show(1.2);
泛型接口

定义在接口上的泛型由实现类的对象确定,或者由实现类确定

/*
    泛型接口(接口上含有泛型)
    格式:
        public abstract interface 接口名<泛型变量> {
            //抽象方法
        }
 */
 public interface MyInter<T> {
    //抽象方法
    public abstract void print(T t);
}

//定义实现类时确定泛型的类型
public class MyImp1 implements MyInter<String> {
    public void print(String t){
        //...
    }
}

//定义实现类是可以不用确定泛型的类型
public class MyImp2 implements MyInter<T> {
    public void print(T t){
        //...
    }
}

使用:

MyImp2<String>  my = new MyImp2<String>();  
my.print("abc");
泛型通配符

当使用泛型类或者接口时(不是定义泛型)传递的数据中,泛型类型不确定,可以通过通配符<?>表示。但是一旦使用泛型的通配 符后,里面的只能使用Object类中的共性方法,集合中元素自身方法无法使用。

public static void main(String[] args) {
    Collection<Integer> list1 = new ArrayList<Integer>();
    test1(list1);
    Collection<String> list2 = new ArrayList<String>();
    test1(list2);
    
}
public static void test1(Collection<?> list) {
    //不能使用add方法,因为元素类型在调用的时候才能确定
    //list.add("abc"); 编译器会报错
    System.out.println(list.size());
}
泛型的上限和下限

JAVA的泛型中可以指定一个泛型的上限和下限。
泛型的上限

  • 格式: 类型名称 <? extends 类 > 对象名称
    只能接收该类型及其子类

泛型的下限

  • 格式: 类型名称 <? super 类 > 对象名称
    只能接收该类型及其父类型
public static void main(String[] args) {
    Collection<Integer> list1 = new ArrayList<Integer>();
    Collection<String> list2 = new ArrayList<String>();
    Collection<Object> list3 = new ArrayList<Object>();

    test1(list1);
    test1(list2); //报错,String类型不是Number类型的子类
    test2(list3);
    
}
//必须是Number类型或者是Number类型的子类
public static void test1(Collection<? extends Number> list) {

}
//必须是Number类型或者是Number类型的父类
public static void test2(Collection<? super Number> list) {

}
Collection接口中使用泛型的源码
//Collection接口中使用泛型
public interface Collection<E> extends Iterable<E> {
    //...
}

//Iterable接口中使用泛型
public interface Iterable<T> {
    //...
    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        
        //增强for循环中,t的类型用泛型T代替
        for (T t : this) {
            action.accept(t);
        }
    }
    //...
}
使用泛型的好处
  • 可以统一数据类型,便于操作
  • 将运行时的异常提前到了编译时,提高了效率
    Collection<String> coll = new ArrayList<String>();定义了集合中只能存字符串对象,如果存其他的类型,则编译期间就会报错(编译器会有报错提示)
  • 避免了强制类型转换
  • 实现代码的模板化,把数据类型当作参数传递,提高了可重用性

接口

Collection提供的接口

Collection提供了一些接口,供单列集合(List和Set)通用,常用接口如下:

  • public boolean add(E e) :把给定的对象添加到集合中
  • public void clear():清空集合中所有元素
  • public boolean remove(E e):从集合中删除指定对象
  • public boolean contains(E e):判断集合中是否存在指定对象
  • public boolean isEmpty():判断集合是否为空
  • public int size():返回集合的元素个数
  • public Object[] toArray()::把集合中的元素存储到数组中

用法:

public static void collectionApis() {
    //利用多态
    Collection<String> coll = new ArrayList<String>();

    //1
    coll.add("a");
    coll.add("b");
    coll.add("c");
    System.out.println(coll);//[a, b, c]

    //2
    coll.remove("b"); //true
    coll.remove("d"); //false
    System.out.println(coll); //[a, c]

    //3
    System.out.println(coll.size()); //2

    //4
    System.out.println(coll.contains("e")); //false

    //5
    System.out.println(coll.isEmpty());  //false

    //6
    Object[] arr = coll.toArray();
    System.out.println(arr[0]); //a
    
    //7
    coll.clear();
    System.out.println(coll); //[]
}
Iterator提供的接口
  • public Iterator iterator():获取集合对应的迭代器
  • public E next():取迭代器的下一个元素
  • public boolean hasNext():判断是否有下一个元素

初始状态,迭代器索引位于第一个元素前,调用一次next(),迭代器向后移动一个位置,如果该位置有元素,则返回该元素,否则报错java.util.NoSuchElementException。当hasNext方法返回false,表示到达了集合末尾。

在遍历的过程中,不能对集合元素进行增删操作

用法:

public static void IteratorTest() {
    Collection<String> coll = new ArrayList<String>();

    coll.add("a");
    coll.add("b");
    coll.add("c");

    //获取集合对应的迭代器
    Iterator<String> it = coll.iterator();

    //判断是否有下一个元素
    while(it.hasNext()) {
        String item = it.next(); //取迭代去的下一个元素
         //不能在遍历的过程中修改元素,否则报错:java.util.ConcurrentModificationException
        //coll.remove("b");
        System.out.println(item);
    }
    String item = it.next(); //没有元素了再去取元素则报错:java.util.NoSuchElementException
    System.out.println(item);
}
增强for循环

用来遍历集合和数组

在遍历的过程中,不能对集合元素进行增删操作

for(元素的数据类型  变量 : Collection集合/数组){ 
  	//
}

用法:

public static void forTest() {
    Collection<String> coll = new ArrayList<String>();

    coll.add("a");
    coll.add("b");
    coll.add("c");

    for(String item: coll) {
        System.out.println(item);
    }
    
    int[] arr = {1, 2, 3};
    for(int item: arr){
        System.out.println(item);
    }
}
Collection的默认类型Object
public static void test() {
    //没有指定类型,则默认是Object类型,可以存放各种类型的元素
    Collection coll = new ArrayList();
    coll.add(1);
    coll.add("abc");
    coll.add('e');
    coll.add(1.2);

    System.out.println(coll);
    for(Object item: coll) {
        if(item instanceof String) {
            System.out.println("字符串");
            //item.length(); //错误写法,因为此时item是Object类型,不能使用String的特有方法
            System.out.println("字符串的长度:"+((String) item).length());
        }
        System.out.println(item);
    }
}

List集合

java.util.List接口继承自Collection接口,实现了List接口的集合(如ArrayList和LinkedList)称为List集合,List接口的特点:

  • 元素存取有序
  • 带有索引,可以通过索引访问集合中的任意元素
  • 集合中的元素可重复
List集合中的方法
  • public void add(int index, E element):向指定的位置添加指定的元素,索引从0开始
  • public E get(int index):获取指定位置的元素,索引从0开始,
  • public E remove(int index):移除指定位置的元素,索引从0开始,并返回被移除的元素
  • public E set(int index, E element):将指定位置的元素替换为指定的值,索引从0开始,并返回旧值

用法:

public static void listApisTest(){
    List<String> list = new ArrayList<String>();

    list.add("a");
    list.add("b");
    list.add("c");
    
    //1
    list.add(1, "aa");
    System.out.println(list); //[a, aa, b, c]
    //2
    System.out.println(list.get(2));//b
    //System.out.println(list.get(5));//报错:java.lang.IndexOutOfBoundsException

    //3
    System.out.println(list.remove(1)); //aa
    System.out.println(list); //[a, b, c]

    //4
    System.out.println(list.set(1, "1")); //b
    System.out.println(list); //[a, 1, c]
}
ArrayList和LinkedList

ArrayList集合使用数组作为存储结构,因此元素的查找块,增加和删除慢;LinkedList集合使用链表作为存储结构,因此元素的查找慢,增加和删除快。

Set集合

java.util.Set接口继承自Collection接口,实现了Set接口的集合(如HashSet、LinkedHashSet)称为Set集合,Set接口的特点:

  • 不带索引
  • 集合中的元素不可重复

Set集合元素是唯一的前提是元素所属类重写hashCode和equals方法。

举个例子

public class Person {
    Integer id;
    String name;

    public Person(Integer id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
    
    @Override
    public int hashCode() {
        int result = id;
        result = 31 * result + (name != null ? name.hashCode() : 0);

        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if(this == obj) return true;
        if(null == obj || getClass() != obj.getClass()) return false;

        Person person = (Person)obj;

        if(id != person.id) return false;
        return name != null ? name.equals(person.name) : person.name == null;
    }
}
public static void main(String[] args) {
    HashSet set = new HashSet<>();
    Person p1 = new Person(1,"a");
    Person p2 = new Person(2,"b");
    
    set.add(p1);
    set.add(p2);
    
    p1.name = "c";
    set.remove(p1);
    System.out.println(set);

    set.add(new Person(1,"c"));
    System.out.println(set);
    
    set.add(new Person(1, "a"));
    System.out.println(set);
}

结果:

[Person{id=1, name='c'}, Person{id=2, name='b'}]
[Person{id=1, name='c'}, Person{id=2, name='b'}, Person{id=1, name='c'}]
[Person{id=1, name='c'}, Person{id=2, name='b'}, Person{id=1, name='a'}, Person{id=1, name='c'}]

分析:

public static void main(String[] args) {
    HashSet set = new HashSet<>();
    Person p1 = new Person(1,"a");
    Person p2 = new Person(2,"b");
    
    set.add(p1);
    set.add(p2);
    
    p1.name = "c";
    //删除p1={1, "c"},按照p1处理过的哈希值在数组中查找该位置是否有元素,
    //这时候这个位置并没有元素,因为set集合中的p1虽然值变成了{1, "c"},
    //但是哈希值没变,还是插入时的{1,"a"}计算出来的哈希值,因此,删除无效
    set.remove(p1); 
    System.out.println(set);
	
	//按照{1, "c"}处理过的哈希值在数组中查找该位置是否有元素,
	//这时候这个位置并没有元素,因此添加成功
    set.add(new Person(1,"c"));
    System.out.println(set);
    
    //按照{1, "c"}处理过的哈希值在数组中查找该位置是否有元素,
    //这时候,这个位置有元素,该元素就是修改之前的p1,因此继续
    //按照equals方法比较两个对象是否相等,结果不相等,因此添加成功
    set.add(new Person(1, "a"));
    System.out.println(set);
}
HashSet

特点:元素无序
HashSet根据对象的哈希值来确定元素在集合中的存储位置,查找和存取都需要计算元素的哈希值,而不是遍历查找,因此存取和查找的性能好。

每个类都会默认继承Object类,Object类中有hashCode和equals方法,当子类不重写这两个方法时:
//Person.java

public class Person {
    String name;
    int age;

    Person(String name, int age){
        this.name = name;
        this.age = age;
    }
}

//Demo.java

public static void hashSetTest() {
    HashSet<Person> set1 = new HashSet<Person>();
    set1.add(new Person("张三", 12));
    set1.add(new Person("李四", 18));
    set1.add(new Person("张三", 12));
    
    for(Person item: set1){
        System.out.print(item.name);
        System.out.println(item.age);
    }
}

打印的结果为:

张三12
李四18
张三12

因为item中存储的是实例的地址值,地址值不一样,得到的哈希值也不一样。

Person类重写hashCode和equals方法后:

public class Person {
    //...
    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

    @Override
    public boolean equals(Object obj) {
        if(this == obj) return true;
        if(obj == null || getClass() != obj.getClass()) return false;

        Person per = (Person)obj;
        return this.age == per.age && Objects.equals(this.name, per.name);
    }
}

结果:

李四18
张三12
LinkedHashSet

特点:元素无序,但是能按照元素插入顺序遍历元素
LinkedHashSet继承了HashSet,在上面说的HashSet的存储结构上,加上了一个双向链表用来记录元素插入顺序。比如下面这段代码:

Set linkedHashSet = new LinkedHashSet<>();
linkedHashSet.add(new String("AA"));
linkedHashSet.add(456);
linkedHashSet.add(456);
linkedHashSet.add(new String("刘"));

在这里插入图片描述

TreeSet
  • 底层采用红黑色
  • 可以按照添加对象的指定属性进行排序,方式有两种:自然排序和定制排序
  • 自然排序:比如插入-1,2,4,3,遍历顺序为-1,2,3,4。如果元素是自定义类对象,则该类可以实现Comparable接口,并重写compareTo方法,在该方法中处理按照属性排序的逻辑
public class Student implements Comparable{
    private int age;
    Student(int age){
        this.age=age;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                '}';
    }

    @Override
    public int compareTo(Object o) {
        Student student =(Student)o;
        //重写compareTo(),传入一个参数,然后拿当前的类的年纪跟传入参数的年纪作为对比,返回-1时,其实就是倒序排列,可以简化为
        //Integer.compare(this.age, student.age);
        return this.age> student.age?-1:this.age< student.age?1:0;
    }
}
  • 定制排序
public class TestM {
    public static void main(String[] args) {
        TreeSet<Student> objects = new TreeSet<>(((o1, o2) -> o1.age>o2.age?-1:o1.age<o2.age?1:0));//这个地方同样可以使用lambda表达式来代替Integer.compare(o2.age, o1.age)
        objects.add(new Student(5));
        objects.add(new Student(5));
        objects.add(new Student(6));
        objects.add(new Student(8));
        System.out.println(objects);
    }
}
  • 添加的元素必须是同类的对象

Map

java.util.Map,Map集合用来存储键值对。Collection接口下的集合是单列集合,存储单个单元数据,而Map接口下的集合存储数据为key-value键值对,称为双列集合。Map集合不能包含重复的键,但是值可以重复。

Map集合和Set集合一样,要保证键的唯一性,需要重写键的hashCode和equals方法。

常用的Map集合有:

  • HashMap:采用哈希表结构,元素无序,线程不安全,效率高低
  • HashTable:和HashMap一样,但是线程安全,效率低,基本不使用
  • LinkedHashMap:采用哈希表+链表结构,元素无序,但是能按照元素插入顺序遍历元素,原因同LinkedHashSet中的解释
Hashtable和HashMap的异同

相同点:

  • 都实现了Map接口

不同点:

  • 继承不同
    public class Hashtable extends Dictionary implements Map
    public class HashMap extends AbstractMap implements Map
  • Hashtable中的方法是线程安全,同步的,效率低;HashMap中的方法默认情况下是线程不安全,非同步的,效率高
  • Hashtable中的key和value都不允许出现null;HashMap中的key可以是null,且只有一个key是null,可以有多个value是null。因此,HashMap中不要使用get()方法来判断key是否存在,因为get()返回null,有可能是key本身就是null值,所以应该使用containsKey()方法
  • 遍历方式的内部实现不同,Hashtable、HashMap都使用了 Iterator。而Hashtable还使用了Enumeration的方式
  • 哈希值不同,Hashtable直接使用对象的hashCode()作为哈希值,HashMap将对象的hashCode()计算之后的值作为哈希值
  • 数组初始大小和扩容方式不同,Hashtable中的数组默认大小是11,扩容是oldx2+1;HashMap中的数组默认大小是16,扩容是oldx2
Map接口采用方法
  • public V put(K key, V value):向集合中添加键值对
  • public V remove(Object key):根据给定的键,移除元素,并返回值
  • public V get(Object key):根据给定的键,获取值
  • public Set<K> keySet():将Map集合中所有的键放到Set集合中
  • public Set<Map.Entry<K, V>> entrySet(): 获取到Map集合中所有的键值对对象的集合(Set集合)

Entry将Map中的键值对封装成了对象,通过entrySet可以获取这些键值对组成的对象,每个对象都有一个getKey()和getValue()方法获取对应的键和值。
用法:

public static void mapTest() {
    HashMap<String, Integer> map = new HashMap<>();
    map.put("a", 1);
    map.put("b", 2);
    map.put("c", 3);
    map.put("a", 4);

    System.out.println(map);//{a=4, b=2, c=3}

    map.remove("a");
    System.out.println(map); //{b=2, c=3}
    
    System.out.println(map.get("b")); //2
    
    Set<String> set = map.keySet();
    System.out.println(set); //[b, c]
    
    for(String key: set) {
        System.out.println(map.get(key)); //2 3
        
    }
}

public static void entryTest() {
    HashMap<String, Integer> map = new HashMap<>();
    map.put("a", 1);
    map.put("b", 2);
    map.put("c", 3);

    Set<Map.Entry<String, Integer>> entrySet = map.entrySet();
    System.out.println(entrySet); //[a=1, b=2, c=3]

    /*
    a1
    b2
    c3
    */
    for(Map.Entry<String, Integer> entry: entrySet) {
        System.out.print(entry.getKey());
        System.out.println(entry.getValue());
    }
}

LinkedHashMap支持按照访问顺序来遍历数据:

public static void linkedHashMapTest() {
    //10:哈希表初始大小 0.75f:装载因子 true:按照访问顺序来遍历数据 
    HashMap<String, Integer> map = new LinkedHashMap<>(4, 0.75f, true);
    map.put("a", 1);
    map.put("b", 2);
    map.put("c", 3);

    System.out.println(map); //{a=1, b=2, c=3}
    map.get("a");
    System.out.println(map);//{b=2, c=3, a=1}
}

Collections工具类

Collections是一个操作Set、List、Map等集合的工具类,常用方法有:

  • public static <T> boolean addAll(Collection<T>c, T ... elements):往集合中批量添加元素
  • public static void shuffle(List<?> list):打乱集合元素的顺序(注意,这个接口的参数只能是List类型的集合)
  • public static <T> void sort(List<T> list):将集合中的元素按照默认规则排序(注意,这个接口的参数只能是List类型的集合)
  • public static <T> void sort(List<T> list, Comparator<> super T>):将集合中的元素按照指定规则排序(注意,这个接口的参数只能是List类型的集合)

用法:

public static void collectionsApisTest() {
    ArrayList<Integer> list = new ArrayList<Integer>();
    Collections.addAll(list,3,2,5,7,4);
    System.out.println(list);

    Collections.sort(list);
    System.out.println(list); //[2, 3, 4, 5, 7]

    Collections.sort(list, new Comparator<Integer>() {
        @Override
        public int compare(Integer o1, Integer o2) {
            //o1-o2,按照从小到大排序
            //o2-o1,按照从大到小排序
            return o2 - o1;
        }
    });
}

源码简析

ArrayList
  • 底层采用数组存储数据 ;
  • 在JDK7中,实例化ArrayList的时候,如果没有指定数组大小,底层会创建一个大小为10的Object类型的数组;每次插入元素的时候,检查是否需要扩容,当数组满了,扩容为原来的1.5倍;
  • 在JDK8中,实例化ArrayList的时候,如果没有指定数组大小,底层会初始化对象数组为{},并没有创建数组。当首次往数组中添加元素的时候,底层创建长度为10的数组,扩容同JDK7。
  • 线程不安全,效率高
public boolean add(E e) {
        modCount++;
        add(e, elementData, size);
        return true;
    }
Vector
  • Vector作为古老的List接口实现类,目前使用不多,代替者是ArrayList
  • 在JDK7和JDK8中实现,同ArrayList在JDK7中
  • 线程安全,效率低
public synchronized boolean add(E e) {
        modCount++;
        add(e, elementData, elementCount);
        return true;
    }
LinkedList
  • 底层采用双向链表存储数据
HashSet和HashMap

HashMap底层结构和HashSet类似,执行HashSet set = new HashSet<>();的时候,调用的是public HashSet() { map = new HashMap<>(); }方法,因此,这里重点介绍HashMap。

存储结构
HashMap底层使用哈希表结构存储,JDK8之前,哈希表采用数组和链表实现(即解决冲突的方法是链地址法);JDK8及其之后,哈希表采用数组、链表和红黑树实现,因为出现大量哈希冲突的时候,链表太长会导致查找效率降低,因此JDK8的做法是当链表元素个数超过8并且数组长度大于64的时候,将链表转换为红黑树。

初始化和扩容
JDK7中,实例化一个HashMap时,默认创建一个大小为16的数组(如果没有指定初始大小的话),数组为Entry[];在JDK8中,在添加第一个元素的时候才会创建一个大小为16的数组(如果没有指定初始大小的话),数组为Node[],Node继承了Entry;如果哈希表装载因子(数组中的元素包括链表上的元素除以数组长度)超过0.75(0.75*16=12)时,会扩容成原数组大小的两倍,扩容之后还需要重新hash。
下面用Node代表数组。

添加元素
比如要插入的键值对是k1->v1,会先计算k1的哈希值h1,然后根据h1计算对应的哈希表中的位置,这里用的是数组大小减1在和h1做位与运算,相当于h1和数组大小取模运算,但是位与运算效率要高一点。如果这个位置为空的话,那就把元素直接放到这个位置上。如果这个位置已经存在元素了,那么就会遍历这个桶上的链表,拿着h1去和这些元素的哈希值作对比,如果存在元素的哈希值和h1相同,并且key的equals方法返回的也是true,那么就会用新的v1去替换掉这个旧的已存在元素的value。如果遍历到链表的尾部,还不存在这样的元素的话,那新的元素就可以直接插入到链表尾部。这个时候,当链表长度大于8并且哈希表容量大于等于64的时候,链表会转成红黑树。

哈希冲突

  • JDK7中,连地址法解决哈希冲突,是通过头插入法,如数组0号位置有元素A,此时新元素B的数组下标也是0,则将0号位置换成元素B,让B指向A
  • 而在JDK8中,通过尾插法,将B元素链在A元素后面。当链表元素个数超过8并且数组长度大于64的时候,将链表转换为红黑树;当链表元素个数大于等于7并且数组长度大于等于64的时候,对数组进行扩容。
    在这里插入图片描述
Hashtable、ConcurrentHashMap、SynchronizedMap

由于HashMap是线程不安全的,因此在要求线程安全的并发场景下,可以使用以下三种方式解决:

  • Hashtable
  • ConcurrentHashMap
  • SynchronizedMap

Hashtable

public synchronized V get(Object key) {
       // ...
}
public synchronized V put(K key, V value) {
    // ...
}

可以看到,Hashtable的put和get方法都加上了synchronized关键字,因此当一个线程对Hashtable的对象进行put操作时,另一个线程不但不可以put,而且也不能get。

ConcurrentHashMap

public V put(K key, V value) {
        return putVal(key, value, false);
    }

    /** Implementation for put and putIfAbsent */
    final V putVal(K key, V value, boolean onlyIfAbsent) {
        if (key == null || value == null) throw new NullPointerException();
        int hash = spread(key.hashCode());
        int binCount = 0;
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
                if (casTabAt(tab, i, null,
                             new Node<K,V>(hash, key, value, null)))
                    break;                   // no lock when adding to empty bin
            }
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);
            else {
                V oldVal = null;
                synchronized (f) {
                    if (tabAt(tab, i) == f) {
                        if (fh >= 0) {
                            binCount = 1;
                            for (Node<K,V> e = f;; ++binCount) {
                                K ek;
                                if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                     (ek != null && key.equals(ek)))) {
                                    oldVal = e.val;
                                    if (!onlyIfAbsent)
                                        e.val = value;
                                    break;
                                }
                                Node<K,V> pred = e;
                                if ((e = e.next) == null) {
                                    pred.next = new Node<K,V>(hash, key,
                                                              value, null);
                                    break;
                                }
                            }
                        }
                        else if (f instanceof TreeBin) {
                            Node<K,V> p;
                            binCount = 2;
                            if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                           value)) != null) {
                                oldVal = p.val;
                                if (!onlyIfAbsent)
                                    p.val = value;
                            }
                        }
                    }
                }
                if (binCount != 0) {
                    if (binCount >= TREEIFY_THRESHOLD)
                        treeifyBin(tab, i);
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        addCount(1L, binCount);
        return null;
    }
public V get(Object key) {
        Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
        int h = spread(key.hashCode());
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (e = tabAt(tab, (n - 1) & h)) != null) {
            if ((eh = e.hash) == h) {
                if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                    return e.val;
            }
            else if (eh < 0)
                return (p = e.find(h, key)) != null ? p.val : null;
            while ((e = e.next) != null) {
                if (e.hash == h &&
                    ((ek = e.key) == key || (ek != null && key.equals(ek))))
                    return e.val;
            }
        }
        return null;
    }

JDK8中,ConcurrentHashMap采用了transient volatile HashEntry<K,V>[] table保存数据。这样对每个table数组元素加锁,见源代码中synchronized(f),因此可以减少并发冲突的概率,提高并发性能。对于get方法,ConcurrentHashMap采用了与HashMap一样的思路,并没有加锁。

SynchronizedMap

public V get(Object key) {
    synchronized (mutex) {return m.get(key);}
}

public V put(K key, V value) {
    synchronized (mutex) {return m.put(key, value);}
}

SynchronizedMap的put和get方法都加上了synchronized关键字,调用了HashMap的put和get方法。

综上,ConcurrentHashMap的put操作是对数组元素加锁,get操作没有加锁;而SynchronizedMap的put和get方法都进行了加锁,因此ConcurrentHashMap的效率高于SynchronizedMap的效率。

总结

在这里插入图片描述

List集合
  • 元素存取有序
  • 带有索引,可以通过索引访问集合中的任意元素
  • 集合中的元素可重复
  • ArrayList
    采用数组作为存储结构,因此元素的查找块,增加和删除慢
  • LinkedList
    采用链表作为存储结构,因此元素的查找慢,增加和删除快
Set集合
  • 不带索引,无序性,但不代表输出顺序随机,每次输出的顺序一样,只是和插入的顺序不一样,因为底层数组是按照元素的哈希值作为索引存储的
  • 集合中的元素不可重复,前提是元素对象所属类实现了hashCode和equals方法
  • HashSet
    采用数组+链表+红黑树实现,元素的增删改查性能好
    元素无序
  • LinkedHashSet
    在HashSet实现的基础上增加了链表,元素的增删改查性能好
    能够让元素按照存储的顺序取出
Map集合
  • 用来存储键值对
  • 键不重复(前提是键对象所属类实现了hashCode和equals方法)
  • HashMap
    采用哈希表结构,元素无序
  • LinkedHashMap
    采用哈希表+链表结构,能够让元素按照存储的顺序取出
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值