Java高级-集合

常见数据结构

栈:先进后出 存入称为入栈|压栈,取出称为出栈|弹栈,入口和出口在集合的同一侧
理解:子弹的弹夹,存弹,第一个存进去的子弹,是最后一个出来 ,也就是先进后出

队列

队列:先进先出 入口和出口在集合的两侧
理解:吃饭排队 队伍前面先打到饭,就先出去

数组

数组:查询快,增删慢
    要把数组中索引是x的元素删除
    必须创建一个新的数组,长度是源数组的长度-1
    把源数组的其他元素复制到新数组中
    再把新数组的地址赋值给变量arr
    源数组会在内存中被销毁(垃圾回收)
数组的弊端:
1.数组初始化之后,长度不可变,不便于扩展
2.数组中提供的属性和方法少,不便于进行添加、删除、插入等操作,且效率不高,同时无法直接获取存储元素的个数
3.存储数据的特点单一

链表

链表:查询慢,增删快
链表中地址不是连续的,每次查询元素,都必须从头开始,也就是没有索引
链表结构,每次增加或删除元素,对链表的整体结构没有影响

单向链表:链表中只有一条链子,不能保证元素的顺序(存储元素和取出元素的顺序有可能不一致)
双向链表:链表中有两条链子,有一条链子是专门记录元素的顺序,是一个有序的集合

计算机中的树
二叉树:分支不能超过两个,左边叫左孩子或左子树,右边叫右孩子或右子树
排序树||查找树 :在二叉树的继承上,元素是有大小顺序的
    左子树比右子树小
平衡树:左子树的数量和右子树的数量相等
不平衡树:左子树的数量和右子树的数量不相等
红黑树:特点类似于平衡树,查询速度非常快,查询叶子节点最大次数和最小次数不能超过2倍
    约束:1.节点可以是红色的或黑色的
        2.根节点是黑色的
        3.叶子节(空节点)点是黑色的
        4.每个红色的子节点都是黑色的
        5.任何一个节点到其每一个叶子节点的所有路径上黑色节点树相同

集合的分类

集合分为
    Collection接口(单列集合,用来存储一个一个的对象)
        List接口:元素有序,元素可重复
            实现类: ArrayList\LinkedList\Vector
        Set接口:元素无序,元素不可重复
            实现类:HashSet LinkedHashSet TreeSet
    Map接口:双列集合,保存具有映射关系 key-value 
        HashMap
            其子类:LinkedHashMap
        Hashtable
            其子类:Properties : 用于读取配置文件
        SortedMap    
            其子类:TreeMap

Collection接口的方法

Collection接口方法
* 添加元素:
*   add(Object obj)
*   add(Collection coll)
* 获取有效元素的个数
*   size()
* 清空集合
*   clear()
* 判断是否是空集合
*   isEmpty()
* 判断是否包含某个元素
*   contains(Object obj) : 是通过equals方法来判断是否是同一个
*   contains(Collection c) :也是调用equals方法来比较,拿两个集合的元素挨个比较
* 删除
*   boolean remove(Object obj) :通过equals方法判断是否是要删除的元素,只会删除找到的第一个元素
*   boolean removeAll(Collection coll) 取当前集合的差集
* 取两个集合的交集
*   boolean retainAll(Collection coll):把交集的结果存在当前集合中,不影响coll
* 集合是否相等
*   boolean equals(Object obj)
* 集合转成对象数组
*   Object[] toArray()
    数组-->集合 Arrays.asList(arr)
* 获取集合对象的哈希值
* int  hashCode()
* 遍历
* Iterator  iterator() :返回迭代器对象,用于集合遍历
    使用迭代器对象.next()方法,访问集合的元素
    remove():删除集合中的元素
    hasNext():判断是否有下一个元素
    整体遍历
        Iterator iterator = coll.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }
    使用循环判断迭代器是否有下一个元素,如果有就走循环,没有就不运行
也可以使用foreach遍历
        for(数据类型 局部变量名:集合(数组)变量){
            //局部变量就是值
        }

对于添加和删除操作,参数所在的类必须重写equals()方法

Collection子接口一:List

List接口

List接口
    元素有序,且可重复,元素都有对应的索引
List接口的实现类:ArrayList LinkedList Vector

List常用方法

* add(int index,ele) :在index位置插入ele元素
* addAll(int index,Collection ele) 在index位置将ele中的所有元素添加进来
* get(int index) 获取指定index位置的元素
* indexOf(Object obj) 返回obj在集合中首次出现的位置
* lastIndexOf(Object obj) 返回obj在集合中最后出现的位置
* remove(int index) 删除指定位置的元素,并返回此元素
* set(int index,Object ele) 设置指定index位置的元素为ele
* List subList(int fromIndex,int toIndex) 返回从fromIndex到toIndex位置的子集合[fromIndex,toIndex)

实现类一:ArrayList

//创建对象
List list = new ArrayList()
线程不安全,效率较高
底层使用Object[] elementDate存储

ArrayList源码

jdk7情况下:
底层创建了长度为10的Object[] elementData,如果数组容量不够,则扩容,默认情况下扩容到原来长度的1.5倍,同时通过Arrays.copyOf()方法赋值到新数组中
如果确定要存多少元素,可以使用带参构造器 ArrayList ar = new ArrayList(20);
jdk8情况下
ArrayList list = new ArrayList()底层Object[] elementDate初始化为{},并没有创建长度为10的容量,第一次调用add()时,底层才创建了长度为10的数组,添加和扩容操作和jdk7一样

实现类二:LinkedList

对于频繁的插入或删除元素,使用LinkedList,效率高
新增方法:
        * addFirst(Object obj) :将参数obj添加到集合中第一个位置
        * addLast(Object obj) : 将参数obj添加到集合中最后的位置
        * getFirst() : 获取集合中的第一个元素
        * getLast() :获取集合中的最后一个元素
        * removeFirst():删除集合中的第一个元素
        * removeLast() 删除集合中的最后一个元素
底层使用双向链表存储

LinkedList源码

LinkedList list = new LinkedList();内部声明了Node类型的first和last属性,默认值为null
通过add()方法,将添加的值封装到Node中,创建了Node对象
其中,Node定义为:
    private static class Node<E>{
        E item;
        Node<E> next;
        Node<E> prev;
        
        Node(Node<E> prev,E element,Node<E> next){
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }

实现类三:Vector

多数操作与ArrayList相同,区别就是Vector是线程安全的,避免使用Vector
新增方法:
    * 新增方法:
        *addElement(object obj)  添加元素
        *insertElementAt(Object obj,int index)向指定索引插入元素
        *setElementAt(Object obj,int index)修改指定索引的元素为obj
        *removeElement(Object obj)       删除指定元素
        *removeAllElements()         删除所有元素
 底层使用Object[] elementDate存储

Vector源码

jdk7和jdk8中通过Vector()构造器创建对象时,底层都创建了长度为10的数组,在扩容方面,默认扩容为原来的数组长度的2倍

三个实现类的异同

*ArrayList和LinkeList线程都不安全,但执行效率要比线程安全的Vector高
* ArrayList内部实现基于动态数组的数据结构,LinkedList基于链表的数据结构,查找和设置,ArrayList要比其他两个快一点,
* 因为LinkedList要移动指针,Vector是同步类,属于强同步类,所以做任何操作都慢
* 对于增删改LinkedList效率块,因为ArrayList要移动数据

集合遍历

方式1:使用增强for循环遍历
    for(类型 自定义名称 : 对象 ){}
方式2:使用迭代器
    1.获取迭代器对象
    Iterator 迭代器名称 = 集合对象.iterator();
    2.判断是否有元素
    while(迭代器名称.hasNext()){
        //获取元素
        迭代器名称.next()
    }

Collection子接口二:Set

Set接口

Set接口没有提供额外的方法,且不允许包含相同的元素
Set判断两个对象是否相同不是使用==运算符,而是根据equals()方法
没有索引,不能使用普通for遍历
Set的实现类 HashSet、 LinkedHashSet、TreeSet
添加元素的过程,以HashSet为例
    向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,此时哈希值接着通过某种算法计算出HashSet底层数组中的存放位置(索引位置),判断数组此位置上是否已经有元素
        如果此位置上没有其他元素,则元素a添加成功--->情况1
        如果此位置上有其他元素b(或以链表形式存在的多个元素)则比较元素a与元素b的hash值
            如果hash值不相同,则元素a添加成功 -->情况2
            如果hash值相同,进而需要调用元素a所在类的equals()方法
                    equals()返回true,元素a添加失败
                    equals()返回false,则元素a添加成功-->情况3
    对于添加成功的情况2和情况3来说,元素a 与已经存在指定索引位置上数据以链表的方式存储
    jdk7:元素a放到数组中,指向原来的元素
    jdk8:原来的元素在数组中,指向元素a
向Set中添加的数据,如果是自定义类的对象,那么自定义类需要重写equals方法和hashCode方法

其实现类一:HashSet

HashSet的特点:
*    不能保证元素的排列顺序
*   线程不是安全的
*   集合元素可以为null
*   底层是一个数组+链表(查询速度快)(jdk7) jdk8底层使用HashMap
*   HashSet:判断两个元素相等的标准:两个对象通过hashCode()方法比较相等,并且两个对象的equals()方法返回值也相等

其实现类二:LinkedHashSet

LinkedHashSet作为HashSet的子类,在添加数据的同时,每个数据还维护了两个引用,记录此数据前一个数据和后一个数据。
优点:对于频繁的遍历操作,LinkedHashSet效率高于HashSet
LinkedHashSet根据元素的hashCode值来决定元素的存储位置,同时使用双向链表维护元素的次序,这使得元素看起来是以插入顺序保存的
LinkedHahSet插入性能略低于HashSet,在迭代访问set里所有元素的效率好

其实现类三:TreeSet

1.向TreeSet中添加的数据,要求是相同类的对象。
2.两种排序方式:自然排序(实现Comparable接口) 和 定制排序(Comparator)TreeSet默认用自然排序
底层使用红黑树结构存储数据,有序,查询速度比List快
* TreeSet新增的方法:
*       Comparator compatator()
*       Object  first()
*       Object  last()
*       Object  lower(e)
*       Object  higher(e)
*       SortedSet subSet(fromElement,toElement)
*       SortedSet headSet(toElement)
*       SortedSet tailSet(fromElement)
必须添加相同类的对象

自然排序

自然排序:TreeSet会调用集合元素的compareTo方法来比较元素之间的大小关系,然后将集合元素按升序排列
如果把一个对象添加到TreeSet时,则该对象的类必须实现Comparable接口
因为只有相同类的两个实例才会比较大小,所以向 TreeSet 中添加的应该是同一个类的对象

定制排序

TreeSet的自然排序要求元素所属的类实现Comparable接口,如果元素所属的类没有实现Comparable接口,或不希望按照升序(默认情况)的方式排列元素或希望按照其它属性大小进行排序,则考虑使用定制排序。定制排序,通过Comparator接口来实现。需要重写compare(T o1,T o2)方法
仍然只能向TreeSet中添加类型相同的对象

Map

Map和Collection并列存在,用于保存具有映射关系的数据 key-value也叫kv对
* key用Set存放,不允许重复,(HashMap)key所在的类必须重写hashCode()和equals()方法
* String作为Map的键--key
* key-value是搭配关系,通过key找到对应的value
*
* Map的实现类 HashMap、TreeMap、LinkedHashMap、Hashtable、Properties
*
* 添加、删除、修改
*   put(key,value) 将指定的key-value添加或修改到当前map对象中
*   putAll(Map m) 将m中所有的key-value对存放到当前map中
*   remove(key) 删除指定的key-value对,并返回value
*   clear() 清空当前map的所有数据
* 查询
*   get(key) 通过key获取对应的value
*   containsKey(key) 是否包含指定的key
*   containsValue(value) 是否包含指定的value
*   size() 返回map中key-value对的个数
*   isEmpty() 判断当前map是否为空
*   equals(obj) 判断当前map和参数对象obj是否相等
* 元视图(方便)
*   Set keySet() 返回所有key构成的Set集合
*   Collection values() 返回所有value构成的Collection集合
*   Set entrySet(): 返回所有key-value对 构成的Set集合

其实现类一:HashMap

允许使用null键和null值,与HashSet一样,不保证映射的顺序。
*   所有的key构成的集合是Set:无序的、不可重复的。所以,key所在的类要重写:equals()和hashCode()
*   所有的value构成的集合是Collection:无序的、可以重复的。所以,value所在的类要重写:equals()
*   一个key-value构成一个entry
*   所有的entry构成的集合是Set:无序的、不可重复的
*   HashMap 判断两个 key 相等的标准是:两个 key 通过 equals() 方法返回 true,hashCode 值也相等。
*   HashMap 判断两个 value相等的标准是:两个 value 通过 equals() 方法返回 true。
* JDK7以前: HashMap 是数组+链表结构
* JDK8以后: HashMap 是数组+链表+红黑树实现

HashMap底层实现原理

JDK7:
    HashMap map = new HashMap()在实例化以后,底层创建了长度是16的一维数组Entry[] table,当进行添加操作时,首先,调用key所在类的hashCode方法计算key的值,这个哈希值经过某种算法计算以后,得到在Entry数组中的存放位置。如果此位置上没有数据,那么key-value添加成功--情况1,如果此位之上有数据(意味着此位置上存放一个或多个数据(以链表形式存在))比较key和已经存在的一个或多个数据的哈希值
        如果key的哈希值与已经存在的key的哈希值都不相同,此时key-value添加成功--情况2
        如果key的哈希值与已经存在的某一个数据(key1,value1)的哈希值相同,则继续比较,调用key所在的类的equals(key1)方法
            如果equals()返回false ,此时key-value添加成功-->情况3
            如果equals()返回true,用value,替换value1
对于情况2和情况3,此时key-value和原来的数据以链表的方式存储
在不断添加的过程中,涉及到扩容问题,超出临界值默认的扩容为原来容量的2倍,并将原有的数据复制过来

JDK8:
    new HashMap():底层没有创建一个长度为16的数组
    jdk8底层的数组是Node[],并非Entry[]
    首次调用put方法时,底层才创建长度为16的数组
    jdk7底层结构只有:数组+链表,jdk8中底层结构:数组+链表+红黑树
        当数组的某一个索引位置上的元素以链表形式存在的数据个ew数>8 且当前数组的长度>64时,此时此索引位置上的所有数据改为使用红黑树存储

JDK7代码

static final int DEFAULT_INITIAL_CAPACITY = 16;//默认初始容量为16
static final int MAXIMUM_CAPACITY = 1 << 30;//最大支持容量
static final float DEFAULT_LOAD_FACTOR = 0.75f;//默认加载因子
transient Entry<K,V>[] table;//创建了一个Entry[] table数组
//测试中,创建对象
HashMap map = new HashMap();//调用空参构造器
//jdk7源码
public HashMap() {
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
        //空参构造器调用了带参构造器,并把,默认容量和,填充因子传进去
}
public HashMap(int initialCapacity, float loadFactor){}
//在带参构造器中,首先判断传进来容量是否小于0,如果小于抛异常,如果不小于继续判断,判断传进来的容量是否大于最大容量,如果大于,将传进来的容量改成最大容量如果不大于继续判断,判断填充因子是否小于等于0,或填充因子是否为数字,满足两个其中一个条件,则抛异常,如果不满足就继续往下运行,
int capacity = 1;
判断capacity是否小于传进来的容量,如果小于一直循环,且capacity<<=1进行位移赋值操作,循环结束
    让当前类填充因子 = 传进来的填充因子 
    this.loadFactor = loadFactor
    threshold = (int)Math.min(capacity * loadFactor,MAXIMUM_CAPACITY+1),取两个中的最小值,当做扩容的临界值
    扩容的临界值 = 当前容量*填充因子
    并创建一个新的Entry数组table = new Entry[capacity]
当进行put()操作时
    传进来一个key,value
    首先判断key是否为null。如果为null,使用putForNullKey(key)方法,添加key键(因为HashMap支持键为null)
    然后通过HashMap定义的方法hash(key) 算出key的哈希值
    int hash = hash(key)
    然后通过HashMap定义的indexFor(hash,table.length),来计算Entry对象保存在 table中的数组索引值
    然后循环,集合中没有元素,就不走循环
    for(Entry<K,V> e = table[i];e!=null;e = e.next){
            Object k;
        //判断table[i]的hash是否和添加的key的哈希值一样,并且(两个key也相等或key.equals(k))判断两个是因为,key的键可以为任何类型,使用==为了判断基本数据类型,使用equals为了判断引用数据类型
     if(e.hash == hash && ((k = e.key) == key || key.equals(k)){
            V oldValue = e.value//如果相等,就把已经存在的值改为,新添加的值,因为HashMap的key不能相等.添加相同的key也就意味着替换value
             e.value = value
            return oldValue//替换成功之后,把旧值返回出去
        }
    }
    //循环完或没走循环
        modCount++;//用于记录扩容和结构改变的次数
        addEntry(hash,key,value,i)//把key的哈希值,key,value,和索引位置,传进去
        return null
    }
    //然后看addEntry方法
      void addEntry(int hash,K key V value,int bucketIndex){
          //判断集合中元素的个数是否大于等于扩容的临界值并且,集合中buckteIndex位置的元素不能为空
        if((size >= threshold) && (null != table[bucketIndex])){
            //如果满足条件,则去扩容
            resize(2*table.length);//扩容,并创建数组
            hash = (null != key) ? hash(key) : 0;
            bucketIndex = indexFor(hash, table.length);
        }
          //如果不满足if直接创建或走完if语句创建
          createEntry(hash,key,value,bucketIndex)
      }
      void createEntry(int hash,K key,V value,int bucketIndex){
          Entry<K,V> e = table[bucketIndex];
          table[bucketIndex] = new Entry<>(hash,key,value,e)
          size++;//集合中的个数++
      }
      void resize(int newCapacity){
          Entry[] oldTable = table;
          int oldCapacity = oldTable.length;
          //如果旧数组的容量== 最大支持容量
          if(oldCapacity == MAXIMUN_CAPACITY){
              threshold = Integer.MAX_VALUE;//就让扩容的临界值=Integer包装类中最大的值
              return;//并返回
          }
          //如果不满足条件
          Entry[] newTable = new Entry[newCapacity]//创建一个新的Entry数组
          
         transfer(newTable,rehash)//将原有节点的hash在扩容后的数组中重新确定下标,并将所有元素存入新建的数组中 
          table = newTable;
          threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1)//取新的临界值
      }

JDK8代码

//默认设置了几个静态常量
static final int DEFAULT_INITIAL_CAPACITY = 1<<4; // 默认容量为16
static final int MAXIMUM_CAPACITY = 1 << 30;//支持的最大容量
static final float DEFAULT_LOAD_FACTOR = 0.75f;//填充因子
static final int TREEIFY_THRESHOLD = 8;//Bucket中链表长度大于该默认值,转换为红黑树
static final int UNTREEIFY_THRESHOLD = 6;//Bucket中红黑树存储的Node小于该默认值,转换为链表
//创建的是Node[]数组,因为Node{}实现类Map.Entry,所以本质上还是Entry数组

//空参构造器
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; 
    }
//put方法
    public V put(K key,V value){
        return putVal(hash(Key),key,value,false,true);
        //调用putVal()添加,hash(key)获取key的哈希值
    }
    final V putVal(int hash,K key,V value,boolean onlyIfAbsent,boolean evict){
        Node<K,V>[] tab; Node<K,V> p;int n,i;
        //判断是否是第一次添加
        if((tab = table) == null || (n =tab.length)==0){
            //如果是第一次添加则
            n = (tab = resize()).length;//resize()返回新的Node数组
        }
        //p赋值为tab索引位置下元素,并判断该位置是否有元素,也就是,是否为空
        if((p = tab[i = (n-1) & hash])== null)
            //如果为空则直接添加成功
            tab[i] = newNode(hash,key,value,null);
        else{
            //如果p不为空,也就意味着该索引位置上有元素
            Node<K,V> e; K k;
            //如果传入的key和已经有的key的hash值一样,就替换一下
            if(p.hash == hash && ((k = p.key) == key || (key !=null && key.equals(k)))){
                e = p;
            }else if(p instanceof TreeNode){//判断当前p是否是TreeNode类型的数据,如果是,则采用这种方法添加
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            }else{
                //都不满足以上条件,继续使用链表
                //遍历链表数据
                for(int binCount = 0;;++binCount){
                    if((e = p.next) == null){
                        p.next = newNode(hash,key,value,null)
                        //判断binCount是否大于等于TREEIFY_THRESHOLD-1,如果大于了,执行treeifyBin方法
                        if (binCount >= TREEIFY_THRESHOLD - 1)
                            //这个方法是将链表转换为红黑树
                            treeifyBin(tab, hash);
                        break;
                    }
                    if(e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))
                       break;
                    p = e;
                }
            }
            if (e != null) { 
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();//扩容
        afterNodeInsertion(evict);
        return null;
    }

 HashMap的子类:LinkedHashMap

LinkedHashMap
*   在HashMap的基础上,使用了一对双向链表来记录添加元素的顺序
            Entry<K,V> before,after;
*   与LinkedHashSet类似,LinkedHashMap 可以维护 Map 的迭代
*    顺序:迭代顺序与 Key-Value 对的插入顺序一致

TreeMap

TreeMap存储 Key-Value 对时,需要根据 key-value 对进行排序,TreeMap 可以保证所有的 Key-Value 对处于有序状态。
*       TreeSet底层使用红黑树结构存储数据
*       TreeMap 的 Key 的排序:
*           自然排序:TreeMap 的所有的 Key 必须实现 Comparable 接口,而且所有TreeMap的Key应该是同一个类的对象,否则将会抛出类转换异常 ClassCastException
*           定制排序:创建 TreeMap 时,传入一个 Comparator 对象,该对象负责对TreeMap 中的所有 key 进行排序。此时不需要实现Comparable 接口
*       TreeMap判断两个key相等的标准:两个key通过compareTo()方法或者compare()方法返回0。

Hashtable

Hashtable是个古老的Map实现类,JDK1.0就提供了,不同于HashMap,Hashtable,该实现类是线程安全的
*       Hashtable实现原理和HashMap相同,功能相同。底层都使用哈希表结构,查询速度快,很多情况下可以互用。
*       与HashMap不同,Hashtable 不允许使用 null 作为 key 和 value
*       与HashMap一样,Hashtable 也不能保证其中 Key-Value 对的顺序
*       Hashtable判断两个key相等、两个value相等的标准,与HashMap一致

 Hashtable的子类:properties

Properties
*Properties 类是 Hashtable 的子类,该对象用于处理配置文件
* 由于配置文件里的 key、value 都是字符串类型,所以 Properties 里的 key和 value 都是字符串类型
*存取数据时,建议使用setProperty(String key,String value)方法和getProperty(String key)方法

collections工具类的使用

Collections 是一个操作 Set、List 和 Map 等集合的工具类
*
*   Collections 中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作,还提供了对集合对象设置不可变、对集合对象实现同步控制等方法
*
*排序操作:(均为static方法)
*    reverse(List) 反转List中元素的顺序
*    shuffle(List) 对List集合元素进行随机排序
*    sort(List) 根据元素的自然顺序对执行的List集合元素按升序排序
*    sort(List,Comparator) 根据制定的Comparator产生的顺序对List集合元素进行排序
*    swap(List list,int i,int j) 将指定List集合中i处元素和j处元素进行交换
* 查找、替换
*   Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
*   Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
*   Object min(Collection)
*   Object min(Collection,Comparator)
*   int frequency(Collection,Object):返回指定集合中指定元素的出现次数
*   boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换List 对象的所有旧值
*
* Collections常用方法:同步控制
* Collections 类中提供了多个 synchronizedXxx() 方法,该方法可使将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题


本人是Java初学者,水平有限,本文章中如果有不对的地方,麻烦您能指出来。向您表示感谢。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值