Java基础知识-集合篇(汇总)

一、关于集合的底层实现

1、Collection(单列集合)
      (1) List(有序,可重复)
           		【1】ArrayList
              		底层数据结构是数组,查询快,增删慢
             		线程不安全,效率高
         		【2】Vector
            		 底层数据结构是数组,查询快,增删慢
             		 线程安全,效率低
         		【3】LinkedList
             		底层数据结构是链表,查询慢,增删快
             		线程不安全,效率高
             	   《注意:双向链表(JDK1.6之前为循环链表,JDK1.7取消了循环) 》
             	   《详见:https://www.cnblogs.com/xingele0917/p/3696593.html》
     (2)Set(无序,不可重复即唯一)
         		【1】HashSet
             		 底层数据结构是哈希表。
             		 哈希表依赖两个方法:hashCode()和equals()
	                 执行顺序:
	                     首先判断hashCode()值是否相同
	                         是:继续执行equals(),看其返回值
	                             是true:说明元素重复,不添加
	                             是false:就直接添加到集合
	                         否:就直接添加到集合
	                 最终:
	                     自动生成hashCode()和equals()即可                     
             	【2】LinkedHashSet
                 	底层数据结构由链表和哈希表组成。
                 	由链表保证元素有序。
                 	由哈希表保证元素唯一。
         		【3】TreeSet
             		 底层数据结构是红黑树。(是一种自平衡的二叉树)
             		 如何保证元素唯一性呢?
                 		根据比较的返回值是否是0来决定
	                 如何保证元素的排序呢?
	                     两种方式
	                         自然排序(元素具备比较性)
	                             让元素所属的类实现Comparable接口
	                         比较器排序(集合具备比较性)
	                             让集合接收一个Comparator的实现类对象
 2、Map(双列集合)
     A:Map集合的数据结构仅仅针对键有效,与值无关。
     B:存储的是键值对形式的元素,键唯一,值可重复。         
    	【1】 HashMap
         		底层数据结构是哈希表。线程不安全,效率高
             	哈希表依赖两个方法:hashCode()和equals()
                 执行顺序:
                     首先判断hashCode()值是否相同
                         是:继续执行equals(),看其返回值
                             是true:说明元素重复,不添加
                             是false:就直接添加到集合
                         否:就直接添加到集合
                 最终:
                     自动生成hashCode()和equals()即可
         【2】LinkedHashMap
                 底层数据结构由链表和哈希表组成。
                     由链表保证元素有序。
                     由哈希表保证元素唯一。
         【3】Hashtable
             	底层数据结构是哈希表。线程安全,效率低
                 	哈希表依赖两个方法:hashCode()和equals()
                 执行顺序:
                     首先判断hashCode()值是否相同
                         是:继续执行equals(),看其返回值
                             是true:说明元素重复,不添加
                             是false:就直接添加到集合
                         否:就直接添加到集合
                 最终:
                     自动生成hashCode()和equals()即可
         【4】TreeMap
	             底层数据结构是红黑树。(是一种自平衡的二叉树)
                 如何保证元素唯一性呢?
                     根据比较的返回值是否是0来决定
                 如何保证元素的排序呢?
                     两种方式
                         自然排序(元素具备比较性)
                             让元素所属的类实现Comparable接口
                         比较器排序(集合具备比较性)
                             让集合接收一个Comparator的实现类对象

二、关于集合的选取规则

是否是键值对象形式:
    是:Map
        键是否需要排序:
            是:TreeMap
            否:HashMap
        不知道,就使用HashMap。            
    否:Collection
        元素是否唯一:
            是:Set
                元素是否需要排序:
                    是:TreeSet
                    否:HashSet
                不知道,就使用HashSet                    
            否:List
                要安全吗:
                    是:Vector
                    否:ArrayList或者LinkedList
                        增删多:LinkedList
                        查询多:ArrayList
                    不知道,就使用ArrayList
          不知道,就使用ArrayList

三、关于集合的常见方法及遍历方式

1、Collection:
   (1)常见方法:
        【1】add()
        【2】remove()
        【3】contains()
        【4】iterator()
        【5】size()
    
   (2) 遍历:
        【1】增强for
        【2】迭代器
               
2、Map:
(1)常见方法:
       【1】 put()
        【2】remove()
        【3】containskey(),containsValue()
        【4】keySet()
        【5】get()
        【6】value()
        【7】entrySet()
        【8】size()
    
    (2)遍历:
        【1】 根据键找值
        【2】根据键值对对象分别找键和值

四、List的遍历方式

方式一:第一种、最基础的遍历方式:for循环,指定下标长度,使用List集合的size()方法,进行for循环遍历

import java.util.ArrayList;

public class Demo01 {

  public static void main(String[] args) {
   ArrayList<News> list = new ArrayList<News>();
    
   list.add(new News(1,"list1","a"));
   list.add(new News(2,"list2","b"));
   list.add(new News(3,"list3","c"));
   list.add(new News(4,"list4","d"));
   for (int i = 0; i < list.size(); i++) {
            News s = (News)list.get(i);
            System.out.println(s.getId()+"  "+s.getTitle()+"  "+s.getAuthor());
    }
  }
}

方式二、较为简洁的遍历方式:使用foreach遍历List,但不能对某一个元素进行操作(这种方法在遍历数组和Map集合的时候同样适用)

import java.util.ArrayList;

public class Demo02 {

  public static void main(String[] args) {

    ArrayList<News> list = new ArrayList<News>();

     list.add(new News(1,"list1","a"));
		     list.add(new News(2,"list2","b"));
		     list.add(new News(3,"list3","c"));
		     list.add(new News(4,"list4","d"));
    for (News s : list) {
            System.out.println(s.getId()+"  "+s.getTitle()+"  "+s.getAuthor());
   }
  }
}

方式三、使用迭代器Iterator遍历:直接根据List集合的自动遍历

import java.util.ArrayList;

public class Demo03 {

  public static void main(String[] args) {

   ArrayList<News> list = new ArrayList<News>();
    
   list.add(new News(1,"list1","a"));
   list.add(new News(2,"list2","b"));
   list.add(new News(3,"list3","c"));
   list.add(new News(4,"list4","d"));
   
     Iterator<News> iter = list.iterator();
     while (iter.hasNext()) {
            News s = (News) iter.next();
            System.out.println(s.getId()+"  "+s.getTitle()+"  "+s.getAuthor());
    }
  }
}

五、Set遍历:类似List
方式一:for循环

public class TraverseSet {
    public static void main(String[] args) {
        Set<String> set = new HashSet<String>();
        set.add("hadoop");
        set.add("hive");
        set.add("hbase");
        set.add("yarn");
        set.add("mapreduce");
        set.add("zookeeper");
        set.add("spark");
      
        //增强for循环遍历
        for (String str : set) {
            System.out.print(str+" ");
        }
    }
}

方式二:迭代器

public class TraverseSet {
    public static void main(String[] args) {
        Set<String> set = new HashSet<String>();
        set.add("hadoop");
        set.add("hive");
        set.add("hbase");
        set.add("yarn");
        set.add("mapreduce");
        set.add("zookeeper");
        set.add("spark");
        //迭代器遍历
        Iterator<String> it = set.iterator();
        while (it.hasNext()) {
            System.out.print(it.next()+" ");    
        }
}

六、Map遍历
1、方式一、通过加强for循环map.keySet(),然后通过键key获取到value值


import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class MapTest {

    public static void main(String[] args) {
        Map<String, String> map=new HashMap<String, String>();
        map.put("张三1", "男");
        map.put("张三2", "男");
        map.put("张三3", "男");
        map.put("张三4", "男");
        map.put("张三5", "男");
        
        //第一种遍历map的方法,通过加强for循环map.keySet(),然后通过键key获取到value值
        for(String s:map.keySet()){
            System.out.println("key : "+s+" value : "+map.get(s));
        }
        
    }
    
    
}

2、方式二:只遍历键或者值,通过加强for循环

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class MapTest {

    public static void main(String[] args) {
        Map<String, String> map=new HashMap<String, String>();
        map.put("张三1", "男");
        map.put("张三2", "男");
        map.put("张三3", "男");
        map.put("张三4", "男");
        map.put("张三5", "男");   
        
        //第二种只遍历键或者值,通过加强for循环
        for(String s1:map.keySet()){//遍历map的键
            System.out.println("键key :"+s1);
        }
        for(String s2:map.values()){//遍历map的值
            System.out.println("值value :"+s2);
        }
        
    }
        
}

3、方式三:Map.Entry<String, String>的加强for循环遍历输出键key和值value

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class MapTest {

    public static void main(String[] args) {
        Map<String, String> map=new HashMap<String, String>();
        map.put("张三1", "男");
        map.put("张三2", "男");
        map.put("张三3", "男");
        map.put("张三4", "男");
        map.put("张三5", "男"); 
        
        //第三种方式Map.Entry<String, String>的加强for循环遍历输出键key和值value
        for(Map.Entry<String, String> entry : map.entrySet()){
            System.out.println("键 key :"+entry.getKey()+" 值value :"+entry.getValue());
        }
 
    } 
    
}

4、方式四:Iterator遍历获取,然后获取到Map.Entry<String, String>,再得到getKey()和getValue()

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class MapTest {

    public static void main(String[] args) {
        Map<String, String> map=new HashMap<String, String>();
        map.put("张三1", "男");
        map.put("张三2", "男");
        map.put("张三3", "男");
        map.put("张三4", "男");
        map.put("张三5", "男");

        //第四种Iterator遍历获取,然后获取到Map.Entry<String, String>,再得到getKey()和getValue()
        Iterator<Map.Entry<String, String>> it=map.entrySet().iterator();
        while(it.hasNext()){
            Map.Entry<String, String> entry=it.next();
            System.out.println("键key :"+entry.getKey()+" value :"+entry.getValue());
        }
       
    }
    
}

七、数组与集合的区别
(1)数组在内存中是连续存储的,所以它的索引速度是非常的快,而且赋值与修改元素也很简单。而集合底层存储结构很多,根据需要选择。
(2)数组只能存放类型一样的数据(基本类型/引用类型),集合可以存储不同类型。

八、如何实现数组和 List 之间的转换?
数组转 List:使用 Arrays. asList(array) 进行转换。
List 转数组:使用 List 自带的 toArray() 方法。

代码示例:

// list to array
List<String> list = new ArrayList<String>();
list. add("heheda");
list. add("呵呵哒");
list. toArray();
// array to list
String[] array = new String[]{"heheda","呵呵哒"};
Arrays. asList(array);

九、Collection 和 Collections 有什么区别?

【1】Collection 是一个集合接口,它提供了对集合对象进行基本操作的通用接口方法,所有集合都是它的子类,比如 List、Set 等。
【2】Collections 是一个包装类,包含了很多静态方法,不能被实例化,就像一个工具类,比如提供的排序方法: Collections. sort(list)。

十、哪些集合类是线程安全的?
Vector、Hashtable、Stack 都是线程安全的,而像 HashMap 则是非线程安全的,不过在 JDK 1.5 之后随着 Java. util. concurrent 并发包的出现,它们也有了自己对应的线程安全类,比如 HashMap 对应的线程安全类就是 ConcurrentHashMap。

十、迭代器 Iterator 是什么?
Iterator 接口提供遍历任何 Collection 的接口。我们可以从一个 Collection 中使用迭代器方法来获取迭代器实例。迭代器取代了 Java 集合框架中的 Enumeration,迭代器允许调用者在迭代过程中移除元素。

十一.Iterator 和 ListIterator 有什么区别?
Iterator 可以遍历 Set 和 List 集合,而 ListIterator 只能遍历 List。
Iterator 只能单向遍历,而 ListIterator 可以双向遍历(向前/后遍历)。
ListIterator 从 Iterator 接口继承,然后添加了一些额外的功能,比如添加一个元素、替换一个元素、获取前面或后面元素的索引位置。

十二、怎么确保一个集合不能被修改?
可以使用 Collections. unmodifiableCollection(Collection c) 方法来创建一个只读集合,这样改变集合的任何操作都会抛出 Java. lang. UnsupportedOperationException 异常。

示例代码如下:

List list = new ArrayList<>();
list. add(“x”);
Collection clist = Collections. unmodifiableCollection(list);
clist. add(“y”); // 运行时此行报错
System. out. println(list. size());

十三、Arraylist 与 LinkedList 异同

  1. 是否保证线程安全: ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全;
  1. 底层数据结构: Arraylist 底层使用的是Object数组;LinkedList 底层使用的是双向链表数据结构(JDK1.6之 前为循环链表,JDK1.7取消了循环。注意双向链表和双向循环链表的区别);
  1. 插入和删除是否受元素位置的影响: ① ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素 位置的影响。 比如:执行add(E e) 方法的时候, ArrayList 会默认在将指定的元素追加到此列表的末尾,这种时间复杂度就为 O(n-i)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位/向 前移一位的操作。 ② LinkedList 采用链表存储,所以插入,删除元素时间复杂度不受元素位置的影响,都是 近似 O(1)而数组为近似 O(n)。
  1. 是否支持快速随机访问: LinkedList 不支持高效的随机元素访问,而 ArrayList 支持。快速随机访问就是通 过元素的序号快速获取元素对象(对应于get(int index) 方法)。
  1. 内存空间占用: ArrayList的空 间浪费主要体现在在list列表的结尾会预留一定的容量空间,而LinkedList的空 间花费则体现在它的每一个元素都需要消耗比ArrayList更多的空间(因为要存放直接后继和直接前驱以及数
    据)。
简要总结:
【1】数据结构实现:ArrayList 是动态数组的数据结构实现,而 LinkedList 是双向链表的数据结构实      现。
【2】随机访问效率:ArrayList 比 LinkedList 在随机访问的时候效率要高,因为 LinkedList 是线性的数据存储方式,所以需要移动指针从前往后依次查找。
【3】增加和删除效率:在非首尾的增加和删除操作,LinkedList 要比 ArrayList 效率要高,因为 ArrayList 增删操作要影响数组内的其他数据的下标。
综合来说,在需要频繁读取集合中的元素时,更推荐使用 ArrayList,而在插入和删除操作较多时,更推荐使用 LinkedList。

十四、ArrayList 与 Vector 区别

【1】Vector类的所有方法都是同步的。可以由两个线程安全地访问一个Vector对象、但是一个线程访问Vector的话代码要 在同步操作上耗费大量的时间。
【2】Arraylist不是同步的,所以在不需要保证线程安全时时建议使用Arraylist。

【1】线程安全:Vector 使用了 Synchronized 来实现线程同步,是线程安全的,而 ArrayList 是非线程安全的。
【2】性能:ArrayList 在性能方面要优于 Vector。
【3】扩容:ArrayList 和 Vector 都会根据实际的需要动态的调整容量,只不过在 Vector 扩容每次会增加 1 倍,而 ArrayList 只会增加 50%。

十五、HashMap的底层实现

1、JDK1.8之前

JDK1.8 之前 HashMap 底层是 数组和链表 结合在一起使用也就是 链表散列。HashMap 通过 key 的 hashCode 经 过扰动函数处理过后得到 hash 值,然后通过 (n- 1) & hash 判断当前元素存放的位置(这里的 n 指的是数组的 长度),如果当前位置存在元素的话,就判断该元素与要存入的元素的 hash 值以及 key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突。
所谓扰动函数指的就是 HashMap 的 hash 方法。使用 hash 方法也就是扰动函数是为了防止一些实现比较差的 hashCode() 方法 换句话说使用扰动函数之后可以减少碰撞。
JDK 1.8 HashMap 的 hash 方法源码:
JDK 1.8 的 hash方法 相比于 JDK 1.7 hash 方法更加简化,但是原理不变。

static final int hash(Object key) {
int h;
// key.hashCode():返回散列值也就是hashcode
// ^ :按位异或
// >>>:无符号右移,忽略符号位,空位都以0补齐
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

对比一下 JDK1.7的 HashMap 的 hash 方法源码.

static int hash(int h) {
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}

相比于 JDK1.8 的 hash 方法 ,JDK 1.7 的 hash 方法的性能会稍差一点点,因为毕竟扰动了 4 次。
所谓 “拉链法” 就是:将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希 冲突,则将冲突的值加到链表中即可。

在这里插入图片描述

2、JDK1.8之后
相比于之前的版本, JDK1.8之后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转 化为红黑树,以减少搜索时间。

在这里插入图片描述TreeMap、TreeSet以及JDK1.8之后的HashMap底层都用到了红黑树。红黑树就是为了解决二叉查找树的缺 陷,因为二叉查找树在某些情况下会退化成一个线性结构。

推荐阅读:
《Java 8系列之重新认识HashMap》 :https://zhuanlan.zhihu.com/p/21673805

十六、HashMap 的长度为什么是2的幂次方
为了能让 HashMap 存取高效,尽量较少碰撞,也就是要尽量把数据分配均匀。我们上面也讲到了过了,Hash 值的 范围值-2147483648到2147483647,前后加起来大概40亿的映射空间,只要哈希函数映射得比较均匀松散,一般应 用是很难出现碰撞的。但问题是一个40亿长度的数组,内存是放不下的。所以这个散列值是不能直接拿来用的。用之 前还要先做对数组的长度取模运算,得到的余数才能用来要存放的位置也就是对应的数组下标。这个数组下标的计算 方法是“ (n - 1) & hash ”。(n代表数组长度)。这也就解释了 HashMap 的长度为什么是2的幂次方。

这个算法应该如何设计呢?
我们首先可能会想到采用%取余的操作来实现。但是,重点来了:“取余(%)操作中如果除数是2的幂次则等价于与其 除数减一的与(&)操作(也就是说 hash%length==hash&(length-1)的前提是 length 是2的 n 次方;)。” 并且 采 用二进制位操作 &,相对于%能够提高运算效率,这就解释了 HashMap 的长度为什么是2的幂次方。

十七、HashMap 多线程操作导致死循环问题
在多线程下,进行 put 操作会导致 HashMap 死循环,原因在于 HashMap 的扩容 resize()方法。由于扩容是新建一 个数组,复制原数据到数组。由于数组下标挂有链表,所以需要复制链表,但是多线程操作有可能导致环形链表。复 制链表过程如下:
以下模拟2个线程同时扩容。假设,当前 HashMap 的空间为2(临界值为1),hashcode 分别为 0 和 1,在散列地址 0 处有元素 A 和 B,这时候要添加元素 C,C 经过 hash 运算,得到散列地址为 1,这时候由于超过了临界值,空
间不够,需要调用 resize 方法进行扩容,那么在多线程条件下,会出现条件竞争,模拟过程如下:
线程一:读取到当前的 HashMap 情况,在准备扩容时,线程二介入
在这里插入图片描述
线程二:读取 HashMap,进行扩容
在这里插入图片描述
线程一:继续执行
在这里插入图片描述这个过程为,先将 A 复制到新的 hash 表中,然后接着复制 B 到链头(A 的前边:B.next=A),本来 B.next=null, 到此也就结束了(跟线程二一样的过程),但是,由于线程二扩容的原因,将 B.next=A,所以,这里继续复制A,让
A.next=B,由此,环形链表出现:B.next=A; A.next=B
注意:jdk1.8已经解决了死循环的问题。

十八、HashMap 和 HashSet 区别
如果你看过 HashSet 源码的话就应该知道:HashSet 底层就是基于 HashMap 实现的。(HashSet 的源码非常非常 少,因为除了 clone() 方法、writeObject()方法、readObject()方法是 HashSet 自己不得不实现之外,其他方法都是 直接调用 HashMap 中的方法。)

在这里插入图片描述十九、HashMap 和 Hashtable 的区别

  1. 线程是否安全: HashMap 是非线程安全的,HashTable 是线程安全的;HashTable 内部的方法基本都经过 synchronized 修饰。(如果你要保证线程安全的话就使用 ConcurrentHashMap 吧!);
  2. 效率: 因为线程安全的问题,HashMap 要比 HashTable 效率高一点。另外,HashTable 基本被淘汰,不要在 代码中使用它;
  3. 对Null key 和Null value的支持: HashMap 中,null 可以作为键,这样的键只有一个,可以有一个或多个键 所对应的值为 null。。但是在 HashTable 中 put 进的键值只要有一个 null,直接抛出 NullPointerException。
  4. 初始容量大小和每次扩充容量大小的不同 : ①创建时如果不指定容量初始值,Hashtable 默认的初始大小为 11,之后每次扩充,容量变为原来的2n+1。HashMap 默认的初始化大小为16。之后每次扩充,容量变为原来 的2倍。②创建时如果给定了容量初始值,那么 Hashtable 会直接使用你给定的大小,而 HashMap 会将其扩充 为2的幂次方大小(HashMap 中的tableSizeFor() 方法保证,下面给出了源代码)。也就是说 HashMap 总是使用2的幂作为哈希表的大小,后面会介绍到为什么是2的幂次方。
  5. 底层数据结构: JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)时,将链表转化为红黑树,以减少搜索时间。Hashtable 没有这样的机制。

HasMap 中带有初始容量的构造函数:

  public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        this.loadFactor = loadFactor;
        this.threshold = tableSizeFor(initialCapacity);
    }
 public HashMap(int initialCapacity) {
      this(initialCapacity, DEFAULT_LOAD_FACTOR);
  }
  

下面这个方法保证了 HashMap 总是使用2的幂作为哈希表的大小。

/**
     * Returns a power of two size for the given target capacity.
     */
    static final int tableSizeFor(int cap) {
        int n = cap - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

二十、ConcurrentHashMap 和 Hashtable 的区别
ConcurrentHashMap 和 Hashtable 的区别主要体现在实现线程安全的方式上不同。
【1】底层数据结构: JDK1.7的 ConcurrentHashMap 底层采用 分段的数组+链表 实现,JDK1.8 采用的数据结构跟 HashMap1.8的结构一样,数组+链表/红黑二叉树。Hashtable 和 JDK1.8 之前的 HashMap 的底层数据结构类 似都是采用 数组+链表 的形式,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的;
【2】实现线程安全的方式(重要): ① 在JDK1.7的时候,ConcurrentHashMap(分段锁) 对整个桶数组进行了 分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁 竞争,提高并发访问率。 到了 JDK1.8 的时候已经摒弃了Segment的概念,而是直接用 Node 数组+链表+红黑 树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。(JDK1.6以后 对 synchronized锁做了很 多优化) 整个看起来就像是优化过且线程安全的 HashMap,虽然在JDK1.8中还能看到 Segment 的数据结构, 但是已经简化了属性,只是为了兼容旧版本;② Hashtable(同一把锁) :使用 synchronized 来保证线程安全, 效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈效率越低。
两者的对比图:
图片来源:http://www.cnblogs.com/chengxiao/p/6842045.html
HashTable:

在这里插入图片描述JDK1.7的ConcurrentHashMap:
在这里插入图片描述JDK1.8的ConcurrentHashMap(TreeBin: 红黑二叉树节点 Node: 链表节点):
在这里插入图片描述

二十一、ConcurrentHashMap线程安全的具体实现方式/底层具体实现
1、JDK1.7(上面有示意图)
首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的 数据也能被其他线程访问。
ConcurrentHashMap 是由 Segment 数组结构和 HashEntry 数组结构组成。
Segment 实现了 ReentrantLock,所以 Segment 是一种可重入锁,扮演锁的角色。HashEntry 用于存储键值对数 据。

static class Segment<K,V> extends ReentrantLock implements Serializable { }

一个 ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和HashMap类似,是一种数组和链表结
构,一个 Segment 包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元素,每个 Segment 守护着一个 HashEntry数组里的元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment的锁。

2、JDK1.8 (上面有示意图)
ConcurrentHashMap取消了Segment分段锁,采用CAS和synchronized来保证并发安全。数据结构跟HashMap1.8 的结构类似,数组+链表/红黑二叉树。
synchronized只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲突,就不会产生并发,效率又提升N倍。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
资源包主要包含以下内容: ASP项目源码:每个资源包中都包含完整的ASP项目源码,这些源码采用了经典的ASP技术开发,结构清晰、注释详细,帮助用户轻松理解整个项目的逻辑和实现方式。通过这些源码,用户可以学习到ASP的基本语法、服务器端脚本编写方法、数据库操作、用户权限管理等关键技术。 数据库设计文件:为了方便用户更好地理解系统的后台逻辑,每个项目中都附带了完整的数据库设计文件。这些文件通常包括数据库结构图、数据表设计文档,以及示例数据SQL脚本。用户可以通过这些文件快速搭建项目所需的数据库环境,并了解各个数据表之间的关系和作用。 详细的开发文档:每个资源包都附有详细的开发文档,文档内容包括项目背景介绍、功能模块说明、系统流程图、用户界面设计以及关键代码解析等。这些文档为用户提供了深入的学习材料,使得即便是从零开始的开发者也能逐步掌握项目开发的全过程。 项目演示与使用指南:为帮助用户更好地理解和使用这些ASP项目,每个资源包中都包含项目的演示文件和使用指南。演示文件通常以视频或图文形式展示项目的主要功能和操作流程,使用指南则详细说明了如何配置开发环境、部署项目以及常见问题的解决方法。 毕业设计参考:对于正在准备毕业设计的学生来说,这些资源包是绝佳的参考材料。每个项目不仅功能完善、结构清晰,还符合常见的毕业设计要求和标准。通过这些项目,学生可以学习到如何从零开始构建一个完整的Web系统,并积累丰富的项目经验。
资源包主要包含以下内容: ASP项目源码:每个资源包中都包含完整的ASP项目源码,这些源码采用了经典的ASP技术开发,结构清晰、注释详细,帮助用户轻松理解整个项目的逻辑和实现方式。通过这些源码,用户可以学习到ASP的基本语法、服务器端脚本编写方法、数据库操作、用户权限管理等关键技术。 数据库设计文件:为了方便用户更好地理解系统的后台逻辑,每个项目中都附带了完整的数据库设计文件。这些文件通常包括数据库结构图、数据表设计文档,以及示例数据SQL脚本。用户可以通过这些文件快速搭建项目所需的数据库环境,并了解各个数据表之间的关系和作用。 详细的开发文档:每个资源包都附有详细的开发文档,文档内容包括项目背景介绍、功能模块说明、系统流程图、用户界面设计以及关键代码解析等。这些文档为用户提供了深入的学习材料,使得即便是从零开始的开发者也能逐步掌握项目开发的全过程。 项目演示与使用指南:为帮助用户更好地理解和使用这些ASP项目,每个资源包中都包含项目的演示文件和使用指南。演示文件通常以视频或图文形式展示项目的主要功能和操作流程,使用指南则详细说明了如何配置开发环境、部署项目以及常见问题的解决方法。 毕业设计参考:对于正在准备毕业设计的学生来说,这些资源包是绝佳的参考材料。每个项目不仅功能完善、结构清晰,还符合常见的毕业设计要求和标准。通过这些项目,学生可以学习到如何从零开始构建一个完整的Web系统,并积累丰富的项目经验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值