后端学习 - 容器

文章目录

  • 一 简介
  • 二 底层数据结构总结
    • 1 List
    • 2 Set
    • 3 Queue
    • 4 Map
  • 三 Collection 的子接口 List
    • 1 ArrayList 与 Vector
    • 2 ArrayList 与 LinkedList
    • 3 ArrayList 的 JDK 7/8 差异
    • 4 ArrayList 的构造方法与扩容机制*
  • 四 Collection 的子接口 Set
    • 1 HashSet、LinkedHashSet 和 TreeSet
    • 2 HashSet / HashMap 加入实例时,查重的方式**
  • 五 Collection 的子接口 Queue
    • 1 Queue 与 Deque
    • 2 PriorityQueue【Java堆实现】
    • 3 ArrayDeque 与 LinkedList【Java栈和队列实现】
  • 六 Collection 的子接口 Map
    • 1 HashMap 和 Hashtable
    • 2 HashMap 的长度为什么是 2 的幂次方
    • 3 ConcurrentHashMap 和 Hashtable
    • 4 为什么JDK1.8的 ConcurrentHashMap 使用 CAS+Synchronized 代替 Segment*
    • 5 CAS (Compare And Swap)
    • 6 JDK8 相较于 JDK7 在底层实现方面的不同
    • 7 HashMap 底层实现的参数
    • 9 JDK 8 HashMap 的扩容
  • 七 Iterator
    • 1 类与接口
    • 2 只读使用方法
    • 3 迭代的问题:在循环中增加或删除元素
  • 八 Collections 工具类
    • 1 对传入的容器接口对象进行操作:查找/替换/排序/添加/修改
    • 2 返回一个容器接口对象


一 简介

Java容器可大致分为 List、Queue、Set、Map 四种。

  • List:存储的元素是有序的、可重复的
  • Set:存储的元素是无序的、不可重复的
  • Queue:按特定的排队规则来确定先后顺序,存储的元素是有序的、可重复的
  • Map:使用键值对(key-value)存储,类似于数学上的函数 y=f(x),“x” 代表 key,“y” 代表 value,key 是无序的、不可重复的,value 是无序的、可重复的,每个键最多映射到一个值
    在这里插入图片描述

二 底层数据结构总结

1 List

数据结构备注
ArrayListObject[]
VectorObject[]
LinkedList双向链表JDK1.6 之前为循环链表,JDK1.7 取消了循环,常用的Java栈、队列实现
  • 栈采用 LinkedListArrayDequeDeque(双端队列)接口
  • 队列使用 LinkedListQueue 接口
  • 栈和队列都是双端队列的特殊情况
// LinkedList节点,被定义为私有的静态内部类
private static class Node<E> {E item;Node<E> next;Node<E> prev;
}

2 Set

  • SetMap 的特殊形式,只有 key 有意义,其 value 均为相同的固定值
数据结构备注
HashSetHashMap存储的元素无序
LinkedHashSetLinkedHashMap存储的元素可以选择按插入顺序访问顺序排序,默认是插入顺序
TreeSetTreeMap存储的元素有序

3 Queue

数据结构备注
PriorityQueueObject[] 实现二叉堆存储的元素按照指定的权值和顺序组织,首个元素有序,其它元素无序常用的Java堆实现
ArrayDequeObject[] + 双指针

4 Map

数据结构备注
HashMap数组+链表/红黑树存储的元素无序
LinkedHashMap数组+链表/红黑树,且 Entry 存在双向的引用存储的元素可以选择按插入顺序访问顺序排序
TreeMap红黑树(自平衡的排序二叉树)存储的元素有序
HashTable数组+链表存储的元素无序
  • HashMap
    • JDK1.8 之前 HashMap数组+链表 组成的,数组是 HashMap 的主体,链表则是主要为了使用拉链法解决哈希冲突而存在的
    • JDK1.8 以后当链表长度大于阈值(默认为 8)且键值对个数大于64时,将链表转化为红黑树,以减少搜索时间
    • 如果键值对个数小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)
    • 相较于 AVL 树,红黑树减弱了对平衡的要求,降低了保持平衡需要的开销
// HashMap数据结构
class HashMap<K, V> extends ... {Entry<K, V>[] table;int size;  // 键值对个数int threshold;  // 扩容阈值 = table.length * loadFactorfloat loadFactor;  // 负载因子,默认0.75// HashMap Entrystatic class Entry<K, V> implements Map.Entry<K, V> {K key;V value;Entry<K, V> next;  // 用于拉链法解决哈希冲突int hash;  // key哈希值}
}

在这里插入图片描述

  • LinkedHashMap
    • 继承自 HashMap,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成
    • 每个 Entry 增加了双向引用,构成一条双向链表,可以 保持键值对的插入顺序或访问顺序
    • 访问顺序指的是,对一个 key 执行 put/get 后,将键值对移动到链表末尾(便于实现 LRU)
    • 指定按访问顺序组织链表,需要调用构造方法 public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) 并指定 accessOrder = true
  • TreeMap
    • 存放的元素必须是全序的:构造时元素需要实现 Comparable 接口,或者指定 Comparator
    • 迭代时输出按键排序
// TreeMap数据结构
class TreeMap<K, V> extends... {Comparator<? super K> comparator;Entry<K, V> root;int size;// TreeMap Entry(红黑树节点)static final class Entry<K, V> implements Map.Entry<K, V> {K key;V value;Entry<K, V> left;Entry<K, V> right;Entry<K, V> parent;boolean color;  // 标志节点红或黑}
}*

三 Collection 的子接口 List

1 ArrayList 与 Vector

  • ArrayListVector 的底层都是 Object[] 存储的,即对数组进行封装
  • ArrayListList 的主要实现类,适用于频繁的查找工作,线程不安全
  • VectorList 的古老实现类,线程安全

2 ArrayList 与 LinkedList

  • 都是线程不安全的
  • ArrayList 底层使用 Object[] 存储(支持随机访问),LinkedList 使用双向链表存储(不支持随机访问
  • ArrayList 的空间浪费主要体现在列表的结尾会预留一定的容量空间,而 LinkedList 的空间花费则体现在它的每一个元素都需要消耗比 ArrayList 更多的空间(因为要存放直接后继和直接前驱以及数据)
  • 获取/插入/删除元素的时间复杂度不同,体现在数组与链表的区别

3 ArrayList 的 JDK 7/8 差异

  • JDK 7:ArrayList 的对象的创建类似于单例的饿汉式
ArrayList list = new ArrayList();//底层创建了长度是*10*的Object[]数组elementData
list.add(123);//elementData[0] = new Integer(123);
list.add(11);//如果此次的添加导致底层elementData数组容量不够,则扩容。/*
默认情况下,扩容为原来的容量的1.5倍,同时需要将原有数组中的数据复制到新的数组中。 
结论:建议开发中使用带参的构造器:ArrayList list = new ArrayList(int capacity)
*/
  • JDK 8: ArrayList 的对象的创建类似于单例的懒汉式,延迟了数组的创建,节省内存
ArrayList list = new ArrayList();  // 底层Object[] elementData初始化为{}.并没有创建长度为10的数组
list.add(123);  // 第一次调用add()时,底层才创建了长度10的数组,并将数据123添加elementData[0]
// 后续的添加和扩容操作与jdk 7 无异

4 ArrayList 的构造方法与扩容机制*

参考链接

  1. 三种构造方法:无参、传入指定长度、传入 Collection
// 属性
private static final int DEFAULT_CAPACITY = 10;  // 默认的容量大小(常量)
private static final Object[] EMPTY_ELEMENTDATA = {};  // 定义的空数组(final修饰,大小固定为0)
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};  // 定义的默认空容量的数组(final修饰,大小固定为0)
transient Object[] elementData;   // 定义的不可被序列化的数组,实际存储元素的数
private int size;  // 数组中元素的个数// 无参的构造方法
"""
容量此时是0,元素个数size为默认值0
"""
public ArrayList() {this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;  // 只有调用无参构造器才会获得 DEFAULTCAPACITY_EMPTY_ELEMENTDATA
}// 传容量的构造方法
"""
initialCapacity > 0时,容量为initialCapacity,元素个数size为默认值0
initialCapacity = 0时,容量为0,元素个数size为默认值0
initialCapacity < 0时,抛出异常
"""
public ArrayList(int initialCapacity) {if (initialCapacity > 0) {this.elementData = new Object[initialCapacity];} else if (initialCapacity == 0) {this.elementData = EMPTY_ELEMENTDATA;  // 调用有参构造器传入0,获得EMPTY_ELEMENTDATA(区别于无参构造器)} else {throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);}
}// 传入Collection元素列表的构造方法
"""
如果传入的Collection不包含元素,容量是0,元素个数size为0
如果传入的Collection包含元素,容量为传入序列的长度,元素个数size也为序列长度,此时的ArrayList是满的
"""
public ArrayList(Collection<? extends E> c) {// 将列表转化为对应的数组elementData = c.toArray();if ((size = elementData.length) != 0) {// 原对象数组的数组类型转化为 Object对象数组的数组类型if (elementData.getClass() != Object[].class)elementData = Arrays.copyOf(elementData, size, Object[].class);} else {// 赋予空数组this.elementData = EMPTY_ELEMENTDATA;}
}
  1. 扩容add & grow
// 单参数的add方法,用户使用的方法
public boolean add(E e) {modCount++;add(e, elementData, size);return true;
}// 重载的多参数add方法
private void add(E e, Object[] elementData, int s) {// 判断元素个数是否等于当前容量,如果相等则调用无参的grow方法if (s == elementData.length)elementData = grow();elementData[s] = e;size = s + 1;
}// 无参的grow方法
private Object[] grow() {return grow(size + 1);  // 传递参数为需求的最小容量,也就是当前容量+1
}// 重载的有参grow方法
"""
if语句中不会处理 用默认 __无参构造方法__ 创建的数组的 __初始扩容__ 情况,其余扩容情况都是由if语句处理ArraysSupport.newLength函数的作用是创建一个大小为oldCapacity + max(minimum growth, preferred growth)的数组
minCapacity是传入的参数,上面显示它的值是当前容量+1,那么minCapacity - oldCapacity的值就恒为1,minimum growth的值也就恒为1
oldCapacity >> 1 右移一位,也就是减半,preferred growth的值即为oldCapacity大小的一半(当oldCapacity为0时,右移后还是0)
"""
private Object[] grow(int minCapacity) {// 当前容量int oldCapacity = elementData.length;// 如果当前容量大于0 或者 数组不是DEFAULTCAPACITY_EMPTY_ELEMENTDATA(创建时调用的是无参构造器)if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {int newCapacity = ArraysSupport.newLength(oldCapacity,minCapacity - oldCapacity, /* 最小增长量 */oldCapacity >> 1           /* 倾向增长量 */);return elementData = Arrays.copyOf(elementData, newCapacity);// 如果 数组是DEFAULTCAPACITY_EMPTY_ELEMENTDATA(容量等于0的话,只剩这一种情况了)} else {return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];}
}
  • 容量增加 1 的情况:
    • 原来的容量为 0,而且是有参构造器创建的 ArrayList(传入 0 或者是空 Collection ,不能是无参构造器创建)
  • 容量变为原来1.5倍的情况:
    • 原来的容量大于 0
  • 容量变为 max(DEFAULT_CAPACITY, minCapacity) 的情况:
    • 原来的容量为 0,而且是无参构造器创建的 ArrayList(其中 DEFAULT_CAPACITY = 10
    • 用默认无参构造方法创建的数组在添加元素前,ArrayList 的容量为0,添加一个元素后,ArrayList 的容量就变为 10

四 Collection 的子接口 Set

1 HashSet、LinkedHashSet 和 TreeSet

  • HashSet
    • 底层数据结构是 HashMap
  • LinkedHashSet
    • 底层数据结构是 LinkedHashMap,元素的插入和取出顺序满足 FIFO 或 LRU
    • HashSet 的子类,在添加数据的同时,每个 Entry 还维护了两个引用,记录此数据前一个数据和后一个数据
    • 对于频繁的遍历操作,LinkedHashSet 效率高于 HashSet
  • TreeSet
    • 底层数据结构是 TreeMap,元素是有序的,排序的方式有自然排序和定制排序
    • 比较两个对象是否相同的标准为:compare() 返回 0,而不再是 equals()
    • 不能放入无法排序的对象
  • 使用场景
    • 都不是线程安全的
    • HashSet 用于不需要保证元素插入和取出顺序的场景
    • LinkedHashSet 用于保证元素的插入和取出顺序满足 FIFO 或 LUR 的场景
    • TreeSet 用于支持对元素自定义排序规则的场景
	// TreeSet的自定排序(TreeMap同理)public void test2(){Comparator com = new Comparator() {@Overridepublic int compare(Object o1, Object o2) {// 定制比较方法...} else {// 类型不匹配...}}};TreeSet set = new TreeSet(com);}

2 HashSet / HashMap 加入实例时,查重的方式**

  • 当对象加入 HashSet 时,HashSet 会先计算对象的 hashcode值来判断对象加入的位置
    • 如果该位置无对象,则 HashSet 中无重复元素,加入成功
    • 如果该位置有对象(下标相同的两个对象, hashcode不一定相同),与相同位置的其他的对象的 hashcode 值作比较
      • 如果没有相符的 hashcode,则无重复元素,加入成功
      • 有相同 hashcode 值的对象,这时会调用 equals() 方法来检查 hashcode 相等的对象是否真的相同,如果两者相同,HashSet 就不会让加入操作成功
    public void test3(){HashSet set = new HashSet();Person p1 = new Person(1001,"AA");Person p2 = new Person(1002,"BB");set.add(p1);set.add(p2);System.out.println(set);p1.name = "CC";set.remove(p1);System.out.println(set);  // BB, CCset.add(new Person(1001,"CC"));System.out.println(set);  // BB, CC, CCset.add(new Person(1001,"AA"));System.out.println(set);  // BB, CC, CC, AA}

五 Collection 的子接口 Queue

1 Queue 与 Deque

  • Queue 是单端队列,只能从一端插入元素,另一端删除元素,实现上一般遵循 FIFO 规则

  • Queue 的两套方法
    在这里插入图片描述

  • Deque 是双端队列,在队列的两端均可以插入或删除元素

  • Deque 的两套方法
    在这里插入图片描述

2 PriorityQueue【Java堆实现】

  • PriorityQueue 利用了二叉堆的数据结构来实现的,底层使用可变长的数组来存储数据
  • PriorityQueue 通过堆元素的上浮和下沉,实现了在 O(logn) 的时间复杂度内插入元素和删除堆顶元素。
  • PriorityQueue 是非线程安全的,且不支持存储无法比较的对象
  • PriorityQueue 同样要求元素实现 Comparable 接口,或传入 Comparator 对象
  • 遍历输出时,只有首个元素是有序的

3 ArrayDeque 与 LinkedList【Java栈和队列实现】

  • ArrayDequeLinkedList 都实现了 Deque 接口,两者都具有双端队列的功能,可以实现栈和队列
  • ArrayDeque 是基于可变长的数组和双指针来实现,而 LinkedList 则通过链表来实现
  • ArrayDeque 插入时可能存在扩容过程, 不过均摊后的插入操作依然为 O(1)
    • 虽然 LinkedList 不需要扩容,但是每次插入数据时需要申请空间,均摊性能相比更慢
    • 从性能的角度上,选用 ArrayDeque 来实现队列要比 LinkedList 更好

六 Collection 的子接口 Map

1 HashMap 和 Hashtable

  • Hashtable
    • 线程安全,内部的方法基本都经过 synchronized 修饰
    • 不允许有 null 键和 null 值,否则会抛出 NullPointerException
    • 创建时如果给定了容量初始值,会直接使用给定的大小
    • 基本被淘汰,不要在代码中使用它
  • HashMap
    • 非线程安全
    • 可以存储 null 的 key 和 value,但 null 作为键只能有一个,null 作为值可以有多个
    • 总是使用 2 的整数次幂作为数组长度,创建时如果给定了容量初始值,会将其扩充为 2 的幂次方大小

2 HashMap 的长度为什么是 2 的幂次方

  • Hash 值的范围值-2147483648 到 2147483647,前后加起来大概 40 亿的映射空间,只要哈希函数映射得比较均匀松散,一般应用是很难出现碰撞的
  • 问题是一个 40 亿长度的数组,内存是放不下的,用之前还要先做对数组的长度取模运算,得到的余数才能用来要存放的位置也就是对应的数组下标
  • 位运算比求模更高效,hash%length == hash&(length-1) 的前提是 length 是 2 的整数次幂

3 ConcurrentHashMap 和 Hashtable

  • 均线程安全
  • 数据结构
类型数据结构使用的锁
ConcurrentHashMap JDK1.7Segment 数组 + HashEntry 数组 + 链表Segment(本质是 ReentrantLock),每次锁若干 HashEntry
ConcurrentHashMap JDK1.8Node 数组 + 链表/红黑树synchronized,每次锁一个 Node
Hashtable数组+链表synchronized,每次锁全表
  1. 在 JDK1.7 的时候,ConcurrentHashMap 采用分段锁机制,对整个桶数组进行了分割分段(Segment,每个 Segment 都是一个可重入锁),每一个 Segment 只锁容器其中一部分数据,多线程访问容器里不同数据段的数据不会存在锁竞争,提高并发访问率
static class Segment<K,V> extends ReentrantLock implements Serializable {...}

在这里插入图片描述

  1. JDK1.8 的时候已经摒弃了 Segment 的概念,synchronized 只锁定当前链表或红黑二叉树的首节点,并发控制使用 synchronized 和 CAS 来操作
    • 虽然在 JDK1.8 中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本

在这里插入图片描述

  1. Hashtable(同一把锁) :使用 synchronized 来保证线程安全,效率非常低下
    • 当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈效率越低

在这里插入图片描述

4 为什么JDK1.8的 ConcurrentHashMap 使用 CAS+Synchronized 代替 Segment*

  • Segment 数组本质上是 ReentrantLock 的数组,其中的每一个 ReentrantLock 锁的是 HashEntry 数组的若干个位置

  • Synchronized 在优化后有偏向锁、轻量级锁和重量级锁三个等级

  • 如果把每个 ReentrantLock 锁的范围细化为一个位置,是否能与 synchronized 锁一个位置的效果相同?答案是否定的:

    1. 锁被细化到一个哈希桶,出现并发争抢的可能性就很低了。对于一个哈希桶,在没有多线程竞争时,使用 Synchronized 的偏向锁机制的效率是最高的
    2. 出现争抢时,Synchronized 轻量级锁具有自旋机制,避免线程状态切换引起的开销;而 ReentrantLock 倾向于将获取不到锁的线程挂起

5 CAS (Compare And Swap)

V:要更新的变量(var)
E:预期值(expected)
N:新值(new)

  • 比较并交换的过程:判断V是否等于E,如果等于,将V的值设置为N;如果不等,说明已经有其它线程更新了V,则当前线程放弃更新,什么都不做
  • 当多个线程同时使用CAS操作一个变量时,只有一个会胜出,并成功更新,其余均会失败,但失败的线程并不会被挂起,仅是被告知失败,并且允许再次尝试,当然也允许失败的线程放弃操作

6 JDK8 相较于 JDK7 在底层实现方面的不同

  • new HashMap()时,底层没有创建数组,首次调用 put() 方法时,底层创建长度为16的数组
  • JDK7 底层结构是 HashEntry 数组+链表;JDK8 中底层结构:Node 数组 + 链表 / 红黑树(数组元素类型的改变是因为支持了链表转红黑树)
    • 当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8 且当前键值对个数 > 64时,此时此索引位置上的所数据改为使用红黑树存储

7 HashMap 底层实现的参数

  • DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16
  • DEFAULT_LOAD_FACTORHashMap的默认装载因子:0.75,是对空间和时间效率的一个平衡选择
  • threshold:扩容的临界值,数值上等于 容量*填充因子:16 * 0.75 => 12
  • TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树,默认8
  • MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量,默认64

9 JDK 8 HashMap 的扩容

  • HashMap 的数组长度一定是 2 的幂次,目的是方便计算哈希值对数组长度取模
  • HashMap 只有在插入元素时才会初始化(创建长为16的 Node 数组),或者扩容
    • 具体地,扩容还要满足两个条件之一
      1. 当前存入数据大于阈值
      2. 存入数据到某一条链表时,该链表数据个数大于 8,且键值对个数小于 64
  • 扩容的具体操作:
    1. 将 Node 数组长度变为原来的 2 倍
    2. 调整原数据在新数组中的索引,调用 resize 方法,根据原来的 hash 值新增的 bit 是0还是1,0=索引不变,1=原来的索引 + 原来哈希表的长度

七 Iterator

1 类与接口

  • Iterable 接口有方法 iterator() 返回 Iterator 对象,使用该对象的方法进行遍历
  • 对象实现了 Iterable,就可以使用 for each 语法
  • 不实现 Iterable 的类也可以创建 Iterator 对象
public interface Iterable<T> {/*** 获取迭代器**/Iterator<T> iterator();
}public interface Iterator() {/*** 是否有下个元素**/boolean hasNext();/*** 迭代器游标下移,并返回指向的元素**/E next();/*** 删除最后返回的元素**/void remove();
}

2 只读使用方法

        Iterator iterator = coll.iterator();while(iterator.hasNext()){// next():①指针下移 ②将下移以后集合位置上的元素返回System.out.println(iterator.next());}

3 迭代的问题:在循环中增加或删除元素

  • 只能使用 Iterator,因为迭代器内部会维护一些索引位置相关的数据,迭代过程中容器不能发生结构性变化,否则这些数据会失效
  • 如果还未调用 next() 或在上一次调用 next() 方法之后已经调用了 remove() 方法,再调用 iterator.remove() 都会抛出 IllegalStateException
  • iterator 调用的 remove() ,和集合对象的 remove() 不同
        // 删除集合中"Tom"Iterator iterator = coll.iterator();while (iterator.hasNext()){Object obj = iterator.next();if("Tom".equals(obj)){iterator.remove();}}

八 Collections 工具类

  • 核心思想是面向接口编程:Collections 提供了很多针对容器接口的通用算法和功能,只要实现了指定接口,就能调用其中的功能
  • 提供的功能大概分为如下两类

1 对传入的容器接口对象进行操作:查找/替换/排序/添加/修改

// 二分查找
public static <T> int binarySearch(List<? extends Comparable<? super T> list, T key>);
public static <T> int binarySearch(List<? extends T> list, T key, Comparator<? super T> c);// 查找元素出现的次数
public static int frequency(Collection<?> c, Object o);// 最大值/最小值
...

2 返回一个容器接口对象

  • 两种情况
  1. 适配器:将其它类型的数据转换成容器接口对象(输入其它类型,输出容器)
// 1.空容器方法:返回静态不可变的空容器接口对象,只读,用于节省创建新对象的开销
public static final <T> List<T> emptyList();
public static final <T> Set<T> emptySet();
public static final <K, V> Map<K, V> emptyMap();
public static <T> Iterator<T> emptyIterator();// 2.单一对象方法:将一个单独的对象转换为不可变的容器接口对象,同样只读
public static <T> Set<T> singleton(T o);
public static <T> List<T> singletonList(T o);
public static <K, V> Map<K, V> singletonMap(K key, V value);
  1. 装饰器:修饰给定的容器接口对象,对其增强(输入容器,输出容器)
// 1.写安全:将修改容器的方法重写为抛出异常,返回只读容器
public static <T> Collection<T> unmodifiableCollection(Collection<? extends T> c);
public static <T> List<T> unmodifiableList(List<? extends T> list));
public static <T> Set<T> unmodifiableSet(Set<? extends T> set));
public static <K, V> Map<K, V> unmodifiableMap(Map<? extends K, ? extends V> m);// 2.类型安全:在泛型失效的情况下(和老版本JDK代码交互时),确保容器元素类型正确
public static <E> List<E> checkedList(List<E> list, Class<E> type);
...// 3.线程安全:向方法加锁,使容器变成线程安全的,不推荐使用
public static <T> List<T> synchronizedList(List<T> list));
...

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/307178.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

简单聊聊AspNetCore的启动流程

首先&#xff0c;得和大家达成一个共识&#xff0c;即AspNetCore项目其实就是一个控制台项目。可以简单的理解&#xff0c;AspNetCore就是将一个Web服务器宿主在一个进程(即控制台)中&#xff0c;然后它在这个进程中进行http请求的监听处理。AspNetCore中默认使用kestrel作为we…

共聚焦图片怎么加标尺_聚焦扶贫政策,打造小康生活

导语&#xff1a;农村独栋小楼、整洁的水泥路……扶贫产业蓬勃发展&#xff0c;我省结合实际&#xff0c;狠抓特色产业&#xff0c;助力脱贫攻坚&#xff0c;实现乡村振兴。武宁县&#xff1a;“四个聚焦”巩固脱贫成果2020年是全面建成小康社会目标实现之年&#xff0c;是全面…

后端学习 - 并发编程

文章目录零 基本概念1 CAS、ABA 问题和原子变量2 this 引用逸出3 不变性 immutable4 同步、异步、阻塞、非阻塞5 JMM6 同步方案演示&#xff1a;计数器 demo*一 进程与线程1 区别与联系2 Java内存区域3 线程组4 线程的上下文切换5 并发与并行6 线程的生命周期与状态二 线程间的…

打造跨平台.NET Core后台服务

续之前讲的在TopShelf上部署ASP.NET Core程序&#xff0c;作为后台服务运行&#xff0c;自从.NET Core 3.0出现以后&#xff0c;出现了自带的Generic Host&#xff0c;使得自托管服务变为可能。这种方式和TopShelf方式一样&#xff0c;可以直接F5进行服务的调试&#xff0c;也为…

iphone桌面横屏设置在哪里_我和我各司其职的桌面们

作者&#xff1a;旭彦兮沐桌面是只属于我们自己一个人的舞台&#xff0c;是与我们独处的好伙伴。好好布置一下自己的桌面&#xff0c;能在很大程度上保持我们心情的愉悦和做事情的效率&#xff0c;让我们保持专注当下的沉浸感。我最早了解到「桌面文化」其实是很早之前了&#…

后端学习 - RabbitMQ

文章目录一 MQ 的作用与基本概念1 流量削峰2 应用解耦3 异步调用4 四个基本概念二 核心模式1 工作队列模式&#xff08;Work Queue&#xff09;2 发布/订阅模式&#xff08;Publish / Subscribe&#xff09;3 路由模式&#xff08;Routing&#xff09;4 主题模式&#xff08;To…

dubbo k8s 服务发现_工商银行基于 Dubbo 构建金融微服务架构的实践-服务发现篇

简介&#xff1a; Dubbo 作为分布式微服务框架&#xff0c;众多公司在实践中基于 Dubbo 进行分布式系统架构。重启开源后&#xff0c;我们不仅看到 Dubbo 3.0 最新的 Roadmap 发布&#xff0c;而且还看到阿里在自身电商开始推进 Dubbo 和内部 HSF 的融合&#xff0c;并在 双11 …

初识ABP vNext(12):模块的独立运行与托管

点击上方蓝字"小黑在哪里"关注我吧模块运行动态 C# API 客户端前言很久没更新这个系列。。。之前的章节中讲到ABP的模块是可以独立运行的&#xff0c;但是没有介绍具体怎么操作&#xff0c;本篇就来讨论一下模块如何独立运行&#xff0c;以及一些托管方式。本人也是处…

后端学习 - Spring5

文章目录一 简介二 IOC1 底层原理2 实现过程3 Spring 实现 IOC 的两个接口二 Bean1 普通 Bean 与 FactoryBean2 Bean 单例与否的设置3 Bean 的生命周期三 IOC 的 Bean 管理&#xff08;XML&#xff09;1 创建对象2 属性注入 - 使用 set 方法3 属性注入 - 通过有参构造器实现3 注…

吐槽一下Abp的用户和租户管理模块

1. 背景ASP.NET Core 基于声明的访问控制到底是什么鬼&#xff1f;聊到基于声明的身份认证将 身份和签发机构分离&#xff0c;应用程序信任签发机构&#xff0c;故认可签发的身份信息。-----------ClaimB站:438962688 Name:饭思思_weibo:538210234 Name:饭思思van姓名:不详 籍贯…

后端学习 - JDBC

文章目录一 JDBC概述1 Java中的数据存储技术2 什么是JDBC3 JDBC程序的编写步骤二 Java连接数据库的方式三 使用 PreparedStatement 实现 CRUD 操作1 数据库的调用的三个接口2 增Create/删Delete/改Update 操作3 查Retrieval操作4 批量插入操作四 数据库事务1 事务2 事务的 ACID…

后端学习 - JavaWeb

技术体系 文章目录一 HTML1 网页的组成部分2 HTML 概述3 HTML 标签4 常用标签5 表单与表单的提交二 CSS1 语法格式2 使用方法三 JavaScript1 概述2 与 HTML 结合的两种方式3 变量类型及特殊值4 关系、逻辑运算5 数组6 函数7 事件8 DOM &#xff08;Document Object Model&#…

心想技术驱动业务,却在背道而驰

这里是Z哥的个人公众号每周五11&#xff1a;45 按时送达当然了&#xff0c;也会时不时加个餐&#xff5e;我的第「165」篇原创敬上大家好&#xff0c;我是Z哥。相信每一位真正的程序员心里都有这样一个念想&#xff1a;只要我的技术够牛&#xff0c;就能驱动业务的发展。但是往…

后端学习 - SpringMVC

文章目录一 SpringMVC 简介1 MVC2 SpringMVC3 创建第一个 SpringMVC 项目二 RequestMapping1 注解类与方法的区别2 value 属性3 method 属性4 params 属性5 headers 属性6 SpringMVC 支持路径中的占位符三 获取 Request 的一系列参数1 通过控制器方法的形参2 控制器方法形参 映…

hbase shell远程连接_hbase与phoenix集成

Phoenix是构建在HBase之上的关系型数据库层&#xff0c;作为内嵌的客户端JDBC驱动用以对HBase中的数据进行低延迟访问Phoenix会将用户编写的sql查询编译为一系列的scan操作&#xff0c;最终产生通用的JDBC结果集返回给客户端Phoenix可以看成是mysql准备安装包apache-phoenix-4.…

对精致码农大佬的 [理解 volatile 关键字] 文章结论的思考和寻找真相

一&#xff1a;背景1. 讲故事昨天在园里的编辑头条看到 精致码农大佬 写的一篇题为&#xff1a;[C#.NET 拾遗补漏]10&#xff1a;理解 volatile 关键字 (https://www.cnblogs.com/willick/p/13889006.html) 的文章&#xff0c;大概就是说在 多线程环境下&#xff0c;一个在debu…

后端学习 - SpringBoot

SpringBoot 是整合 Spring 技术栈的一站式框架&#xff0c;是简化 Spring 技术栈的快速开发脚手架约定大于配置 文章目录一 概述1 第一个 SpringBoot 项目2 SpringBoot 特性&#xff1a;依赖管理3 SpringBoot 特性&#xff1a;自动配置二 SpringBoot 的 IOC容器1 组件添加&…

centos rpm 安装 perl_Linux【常用软件安装篇】

摘要&#xff1a;本文介绍Linux常用的软件安装方式以及jdk、vim、mysql、tomcat、redis的安装过程。1 Linux常用软件安装方式常用方式有&#xff1a;rmp包安装、yum指令安装、源码包安装、解压免安装。1.1 rpm包安装rpm是Red-Hat Package Manager&#xff08;RPM软件包管理器&a…

日计不足涓滴成河-自定义响应结果格式化器

什么是响应结果响应结果就是&#xff0c;在客户端向服务器发出请求后&#xff0c;服务器根据客户端的请求参数&#xff0c;给出的结果&#xff0c;这就是一个完整的响应结果过程。响应的结果包含的内容非常多&#xff0c;主要的有 HTTP Status Code&#xff0c;Content-Type,Co…

docker 容器启动顺序_Docker容器启动时初始化Mysql数据库

1. 前言 Docker在开发中使用的越来越多了&#xff0c;最近搞了一个Spring Boot应用&#xff0c;为了方便部署将Mysql也放在Docker中运行。那么怎么初始化 SQL脚本以及数据呢&#xff1f; 我这里有两个传统方案。 第一种方案是在容器启动后手动导入&#xff0c;太low了不行。第二…