Java基础知识总结(第八篇):集合:Collection(List、Set)、Map、Collections 工具类

news/2024/7/27 12:12:53/文章来源:https://blog.csdn.net/2301_76144723/article/details/137090036

声明: 
              1. 本文根据韩顺平老师教学视频自行整理,以便记忆
              2. 若有错误不当之处, 请指出

  系列文章目录

Java基础知识总结(第一篇):基础语法

Java基础知识总结(第二篇):流程控制语句(分支控制和循环控制)

Java基础知识总结(第三篇):数组、排序和查找

Java基础知识总结(第四篇):面向对象编程基础(类、对象、方法、包以及封装继承多态)

Java基础知识总结(第五篇):面向对象编程进阶(代码块,抽象类、接口和内部类)

Java基础知识总结(第六篇):枚举、注解和异常

Java基础知识总结(第七篇):常用类:包装类、日期类以及String、StringBuffer、Builder、Math 、Arrays 、System 、BigInteger和BigDecimal


目录

一、集合

1.集合的理解与好处

(一)数组的不足分析

(二)集合的优点

2.集合的框架体系

二、Collection

1.Collection 接口

(一)Collection 接口实现类的特点

(二)Collection 接口常用方法

(三)Collection 接口遍历元素

(1)方式 1-使用 Iterator(迭代器)

(2)方式 2-for 循环增强

2.List

(一)List接口

(1)介绍

(2)List 接口的常用方法

(二)ArrayList

(1)ArrayList说明

(2)ArrayList底层结构

(三)Vector

(1)Vector底层结构

(四)LinkedList

(1)LinkedList说明

(2)LinkedList底层结构

(五)Vector 和 ArrayList 的比较

(六)ArrayList 和 LinkedList 的比较

3.Set

(一)Set接口

(1)介绍

(2)Set接口的常用方法

(3)Set 接口的遍历方式

(二)HashSet

(1)HashSet说明

(2)HashSet底层结构 

HashSet元素添加机制

HashSet的扩容和转成红黑树机制

(三)LinkedHashSet

(1)LinkedHashSet说明

(2)LinkedHashSet底层结构

(四)TreeSet

(1)TreeSet说明

(2)TreeSet底层结构

三、Map

1.Map接口

(一)Map 接口实现类的特点

(二)Map 接口常用方法

(三)Map六大遍历方式

(1)使用keySet()方法遍历

(2)使用values()方法遍历

(3)使用entrySey()方法遍历

2.HashMap

(一)HashMap说明

(二)HashMap底层结构

3.Hashtable

(一)Hashtable说明

(二)Hashtable底层结构

4.HashMap和Hashtable对比

5.Properties

(一)Properties说明

7.TreeMap

(一)TreeMap说明

(二)TreeMap底层结构

四、开发中如何选择集合实现类

五、Collections工具类

1.Collections 工具类介绍

2.排序操作:(均为 static 方法)

3.查找、替换


一、集合

1.集合的理解与好处

(一)数组的不足分析

(1)长度开始时必须指定,而且一旦指定,不能更改

(2)保存的必须为同一类型的元素

(3)使用数组进行增加/删除元素比较麻烦

(二)集合的优点

(1)可以动态保存任意多个对象,使用比较方便!

(2)提供了一系列方便的操作对象的方法:add、remove、set、get等

(3)使用集合添加,删除新元素简洁明了

2.集合的框架体系

(1) 集合主要是两组(单列集合 , 双列集合)

(2)Collection 接口有两个重要的子接口 List Set , 他们的实现子类都是单列集合

(3)Map 接口的实现子类 是双列集合,存放的 K-V

单列集合继承图:

双列集合继承图:

二、Collection

1.Collection 接口

(一)Collection 接口实现类的特点

public interface Collection<E>extends Iterable<E>

(1)collection实现子类可以存放多个元素,每个元素可以是Object

(2)有些Collection的实现类,可以存放重复的元素,有些不可以

(3)有些Collection的实现类,有些是有序的(List),有些不是有序(Set)

(4)Collection接口没有直接的实现子类,是通过它的子接口Set和List来实现的

(二)Collection 接口常用方法

(1)add:添加单个元素

list.add("jack");
list.add(10);//相当于list.add(new Integer(10));
list.add(true);

(2)remove:删除指定元素

list.remove(0);//删除第一个元素,根据索引删除,返回的是被删除的对象
list.remove(true);//指定删除某个元素,返回的是被删除对象的索引值

(3)contains:查找元素是否存在

list.contains("jack")//存在返回true,不存在返回false

(4)size:获取元素个数

list.size()

(5)isEmpty:判断是否为空

list.isEmpty()

(6)clear:清空

list.clear()

(7)addAll:添加多个元素

list2.add("红楼梦");
list2.add("三国演义");
list.addAll(list2);

(8)containsAll:查找多个元素是否都存在

(list.containsAll(list2)

(9)removeAll:删除多个元素

list.removeAll(list2);

(三)Collection 接口遍历元素

(1)方式 1-使用 Iterator(迭代器)

介绍:

1)Iterator是Iterable接口里面的一个接口  

2)Iterator对象称为迭代器,主要用于遍历Collection集合中的元素。

3)所有实现了Collection接口的集合类都有一个iterator方法,用以返回一个实现了Iterator接口的对象,即可以返回一个迭代器。?

4)Iterator仅用于遍历集合,Iterator本身并不存放对象。

Iterator接口的方法:

hasNext():判断是否还有下一个元素

next():作用1.下移2.将下移以后集合位置上的元素返回

使用:

//1. 先得到 col 对应的 迭代器
Iterator iterator = col.iterator();
//2. 使用 while 循环遍历
while (iterator.hasNext()) {//判断是否还有数据//返回下一个元素,类型是 ObjectObject obj = iterator.next();System.out.println("obj=" + obj);
}
//3. 如果希望再次遍历,需要重置我们的迭代器
iterator = col.iterator();

注意:在调用iterator.next()方法之前必须要调用iterator.hasNextO进行检测。若不调用,且下一条记录无效,直接调用it.next()会抛出NoSuchElementException异常。

(2)方式 2-for 循环增强

增强for就是简化版的iterator,本质一样。只能用于遍历集合或数组。

基本语法:for(元素类型 元素名:集合名或数组名){访问元素}

示例:  

for (Object dog : list) {
System.out.println("dog=" + dog);
}

2.List

(一)List接口

(1)介绍

List接口是Collection接口的子接口

1)List集合类中元素有序(即添加顺序和取出顺序一致)、且可重复

2)List集合中的每个元素都有其对应的顺序索引,即支持索引

3)List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素。

4)List接口常用的实现类有:ArrayList、LinkedList和Vector。

(2)List 接口的常用方法
方法说明使用
void add(int index, Object ele)
index 位置插入 ele 元素
list.add(1, " 小明 ");
boolean addAll(int index, Collection eles)
index 位置开始将 eles 中的所有元素添加进来
list.addAll(1, list2);
Object get(int index)
获取指定 index 位置的元素
list.get(1)
int indexOf(Object obj)
返回 obj 在集合中首次出现的位置
(list.indexOf("tom")
int lastIndexOf(Object obj)
返回 obj 在当前集合中末次出现的位置
(list.lastIndexOf(" tom ")
Object remove(int index)
移除指定 index 位置的元素,并返回此元素
list.remove(0)
Object set(int index, Object ele):
设置指定 index 位置的元素为 ele , 相当于是替换 .
list.set(1, " 玛丽 ")
List subList(int fromIndex, int toIndex)
返回从 fromIndex toIndex 位置的子集合(不包括toIndex)
list.subList(0, 2)

(二)ArrayList

(1)ArrayList说明

1)permits all elements,including null,ArrayList可以加入null,并且可以有多个null

2)ArrayList是由数组来实现数据存储的

3)ArrayList基本等同于Vector,除了ArrayList是线程不安全(执行效率高)看源码.在多线程情况下,不建议使用ArrayList

(2)ArrayList底层结构

1)ArrayList中维护了一个Object类型的数组elementData.

底层源码:

transient Object[] elementData; // non-private to simplify nested class access

transient表示瞬间,短暂的,表示该属性不会被序列号

2)当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData容量为0,第1次添加,则扩容elementData为10,如需要再次扩容,则扩容elementData为1.5倍。

底层源码(已注释):

无参构造器:

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};public ArrayList() {this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

扩容:

 list.add(i);public boolean add(E e) {//看看数组容量够不够,不够就扩容ensureCapacityInternal(size + 1);  // Increments modCount!!elementData[size++] = e;//将新元素添加到数组中return true;
}//该方法用来确保数组容量够用,传入参数为所需要的最小容量,即数组中元素个数
private void ensureCapacityInternal(int minCapacity) {ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}//默认容量大小为10
private static final int DEFAULT_CAPACITY = 10;private static int calculateCapacity(Object[] elementData, int minCapacity) {if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {//判断是否为空数组return Math.max(DEFAULT_CAPACITY, minCapacity);//若为空则返回默认容量或最小容量哪个大设置哪个}return minCapacity;//数组不为空,返回最小容量
}//用于记录该集合被修改的次数
protected transient int modCount = 0;private void ensureExplicitCapacity(int minCapacity) {modCount++;// overflow-conscious codeif (minCapacity - elementData.length > 0){//如果最小容量大于当前容量//增加容量grow(minCapacity);}
}//最大数组大小
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;//给数组扩容
private void grow(int minCapacity) {// overflow-conscious code//原容量int oldCapacity = elementData.length;//设置新容量为原来的1.5倍int newCapacity = oldCapacity + (oldCapacity >> 1);if (newCapacity - minCapacity < 0)//如果新容量小于最小容量newCapacity = minCapacity;//直接将新容量设置为最小容量if (newCapacity - MAX_ARRAY_SIZE > 0)//如果新容量大于最大数组容量//处理大容量newCapacity = hugeCapacity(minCapacity);// minCapacity is usually close to size, so this is a win://复制数据elementData = Arrays.copyOf(elementData, newCapacity);
}//大容量处理
private static int hugeCapacity(int minCapacity) {if (minCapacity < 0) // overflowthrow new OutOfMemoryError();//如果所需最小容量大于最大数组容量,返回Integer的最大值,否则返回最大数组容量return (minCapacity > MAX_ARRAY_SIZE) ?Integer.MAX_VALUE :MAX_ARRAY_SIZE;
}

3)如果使用的是指定大小的构造器,则初始elementData容量为指定大小,如果需要扩容,则直接扩容elementData为1.5倍。

底层源码(已注释):

有参构造器:

ArrayList list = new ArrayList(8);private static final Object[] EMPTY_ELEMENTDATA = {};//这是ArrayList的有参构造器,参数即为该集合的初始容量
public ArrayList(int initialCapacity) {if (initialCapacity > 0) {this.elementData = new Object[initialCapacity];} else if (initialCapacity == 0) {
//如果传入参数为0,设置elementData为空数组this.elementData = EMPTY_ELEMENTDATA;} else {throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);}}

(三)Vector

(1)Vector底层结构

1)Vector类的定义

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

2)Vector底层也是一个对象数组,protected Object[] elementData;

3)Vector是线程同步的,即线程安全,Vector类的操作方法带有synchronized

public synchronized boolean add(E e) {modCount++;ensureCapacityHelper(elementCount + 1);elementData[elementCount++] = e;return true;
}

4)在开发中,需要线程同步安全时,考虑使用Vector 

5)如果无参构造器,默认容量为10,满后,就按2倍扩容。如果指定大小,则每次直接按两倍扩

底层源码(已注释)

//无参构造器
public Vector() {this(10);
}//有参构造器,参数为初始容量
public Vector(int initialCapacity) {this(initialCapacity, 0);
}//有参构造器。参数为初始容量和每次扩容的大小
public Vector(int initialCapacity, int capacityIncrement) {super();if (initialCapacity < 0)throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);this.elementData = new Object[initialCapacity];this.capacityIncrement = capacityIncrement;
}protected int capacityIncrement;//该方法是扩容的核心方法
private void grow(int minCapacity) {// overflow-conscious codeint oldCapacity = elementData.length;//capacityIncrement可作为构造器的参数指定,默认为0,表示每次扩容的大小int newCapacity = oldCapacity + ((capacityIncrement > 0) ?capacityIncrement : oldCapacity);if (newCapacity - minCapacity < 0)newCapacity = minCapacity;if (newCapacity - MAX_ARRAY_SIZE > 0)newCapacity = hugeCapacity(minCapacity);elementData = Arrays.copyOf(elementData, newCapacity);
}

(四)LinkedList

(1)LinkedList说明

1)LinkedList底层实现了双向链表和双端队列特点

2)可以添加任意元素(元素可以重复),包括null

3)线程不安全,没有实现同步

(2)LinkedList底层结构

1)LinkedList底层维护了一个双向链表.

2)LinkedList中维护了两个属性first和last分别指向首节点和尾节点

//指向头结点
transient Node<E> first;
//指向尾结点
transient Node<E> last;

3)每个节点(Node对象),里面又维护了prev、next、item三个属性,其中通过prev指向前一个,通过next指向后一个节点。最终实现双向链表.

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;}
}

4)所以LinkedList的元素的添加和删除,不是通过数组完成的,相对来说效率较高。

底层源码(已注释)

public boolean add(E e) {//将元素添加到末尾linkLast(e);return true;
}void linkLast(E e) {//让l指向集合的尾部final Node<E> l = last;//创建一个新结点,内容为e,prev指向集合的尾部,next置空final Node<E> newNode = new Node<>(l, e, null);//让last指向新结点last = newNode;//如果集合的尾部为空,说明该集合没有元素,让first指向新结点if (l == null)first = newNode;else//不为空,则让集合尾部结点的next指向新结点l.next = newNode;size++;modCount++;
}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 的比较

底层结构版本线程安全(同步)效率扩容倍数
ArrayList可jjdk1.2不安全,效率高如果有参构造1.5倍

如果是无参

1.第一次10

2.从第二次开始按1.5扩

Vector可变数组jdk1.0安全,效率不高

如果是无参,默认10

,满后,就按2倍扩容

如果指定大小,则每次直

接按2倍扩容.

(六)ArrayList LinkedList 的比较

底层结构增删的效率改查的效率
ArrayList可变数组较低,数组扩容较高
LinkedList双向链表较高,通过链表追加.较低

如何选择ArrayList和LinkedList:

1)如果我们改查的操作多,选择ArrayList

2)如果我们增删的操作多,选择LinkedList

3)一般来说,在程序中,80%-90%都是查询,因此大部分情况下会选择ArrayList

4)在一个项目中,根据业务灵活选择,也可能这样,一个模块使用的是ArrayList,另外一个模块是LinkedList,也就是说,要根据业务来进行选择

3.Set

(一)Set接口

(1)介绍

1)无序(添加和取出的顺序不一致),没有索引。但是取出的顺序是固定的

2)不允许重复元素,所以最多包含一个null

3)JDK API中Set接口常用的实现类有:HashSet、LinkedHashSet、TreeSet

(2)Set接口的常用方法

Set 接口是 Collection 的子接口,因此,常用方法和 Collection 接口一样.

(3)Set 接口的遍历方式

同Collection的遍历方式一样,因为Set接口是Collection接口的子接口。

1)可以使用迭代器

2)增强for

3)不能使用索引的方式来获取.

(二)HashSet

(1)HashSet说明

1)HashSet实现了Set接口

2)HashSet实际上是HashMap,源码如下

public HashSet() {map = new HashMap<>();
}

3)可以存放null值,但是只能有一个null

4)HashSet不保证元素是有序的,取决于hash后,再确定索引的结果.(即,不保证存放元素的顺序和取出顺序一致),但是取出的顺序是固定的

5)不能有重复元素/对象.

(2)HashSet底层结构 

HashSet底层是HashMap,HashMap底层是(数组+链表+红黑树)

HashSet元素添加机制

1)添加一个元素时,先获取元素的哈希值(hashCode方法)

底层源码(已注释)

//创建一个HashsSet对象
HashSet hashSet = new HashSet();
//添加一个元素
hashSet.add("java");//该属性没什么意义,主要起到占位的作用,该值为静态,被所有对象共享
private static final Object PRESENT = new Object();//执行 add()
public boolean add(E e) {//e = "java"
return map.put(e, PRESENT)==null;
}//执行map接口的put方法
public V put(K key, V value) {return putVal(hash(key), key, value, false, true);
}//获取元素的hash值
static final int hash(Object key) {//存放hash值int h;//元素为null则返回0,不为空则调用hashCode()计算hash值//对hash值进行算术右移16位,再对这两个值进行按位异或,防止冲突return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

2)对哈希值进行运算,得出一个索引值即为要存放在哈希表中的位置号

3)如果该位置上没有其他元素,则直接存放

4)如果该位置上已经有其他元素,则需要进行equals判断(String类重写的该方法比较的是类是否相等,其他对象也可通过重写该方法来确定判断标准),如果相等,则不再添加。如果不相等,则以链表的方式添加。

底层源码(已注释)

transient Node<K,V>[] table;final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {Node<K,V>[] tab; Node<K,V> p; int n, i;//定义辅助变量//判断数组是否为空,或者长度是否为0if ((tab = table) == null || (n = tab.length) == 0)//执行resize()方法,会返回一个初始化大小了的table表,默认长度16n = (tab = resize()).length;//(1)根据key,得到hash 去计算该key应该存放到table表的哪个索引位置//并把这个位置的对象,赋给 p//(2)判断p 是否为null//(2.1) 如果p 为null, 表示还没有存放元素, 就创建一个Node (key="java",value=PRESENT)//(2.2) 就放在该位置 tab[i] = newNode(hash, key, value, null)if ((p = tab[i = (n - 1) & hash]) == null)tab[i] = newNode(hash, key, value, null);else {//一个开发技巧提示: 在需要局部变量(辅助变量)时候,在创建Node<K,V> e; K k;//如果当前索引位置对应的链表的第一个元素和准备添加的key的hash值一样//并且满足 下面两个条件之一://(1) 准备加入的key 和 p 指向的Node 结点的 key 是同一个对象//(2)  p 指向的Node 结点的 key 的equals() 和准备加入的key比较后相同//就不能加入if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))//如果相等,则不添加。e = p;//判断该结点是否是红黑树else if (p instanceof TreeNode)e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);else {for (int binCount = 0; ; ++binCount) {//该if判断插入元素所在位置的单链表中p表示的结点后还有无结点,//若无则插入并退出循环,若有,则执行第二个ifif ((e = p.next) == null) {p.next = newNode(hash, key, value, null);if (binCount >= TREEIFY_THRESHOLD - 1) //在把元素添加到链表后,立即判断 该链表是否已经达到8个结点//, 就调用 treeifyBin() 对当前这个链表进行树化(转成红黑树)//注意,在转成红黑树时,要进行判断, 判断条件//if (tab == null || (n = tab.length) < //MIN_TREEIFY_CAPACITY(64))//            resize();//如果上面条件成立,先table扩容.//只有上面条件不成立时,才进行转成红黑树treeifyBin(tab, hash);break;}//该if语句判断p结点直接后继结点是否和要插入元素相同,若相同则退出循环if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break;//若不同,则执行p=e,让p表示该结点的后继结点p = e;}}if (e != null) { // 若e!=null为true,则表示存在相同的元素//将旧值替换成新值V oldValue = e.value;if (!onlyIfAbsent || oldValue == null)e.value = value;afterNodeAccess(e);//添加失败,返回该元素的值return oldValue;}}++modCount;if (++size > threshold)//只要加入结点的个数大于阈值,就会进行扩容resize();afterNodeInsertion(evict);return null;
}                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           
static class Node<K,V> implements Map.Entry<K,V> {final int hash;final K key;V value;HashMap.Node<K,V> next;Node(int hash, K key, V value, HashMap.Node<K,V> next) {this.hash = hash;this.key = key;this.value = value;this.next = next;}public final K getKey()        { return key; }public final V getValue()      { return value; }public final String toString() { return key + "=" + value; }public final int hashCode() {return Objects.hashCode(key) ^ Objects.hashCode(value);}public final V setValue(V newValue) {V oldValue = value;value = newValue;return oldValue;}public final boolean equals(Object o) {if (o == this)return true;if (o instanceof Map.Entry) {Map.Entry<?,?> e = (Map.Entry<?,?>)o;if (Objects.equals(key, e.getKey()) &&Objects.equals(value, e.getValue()))return true;}return false;}
}
//阈值
int threshold;
static final int MAXIMUM_CAPACITY = 1 << 30;
//该常量表示table表的默认大小,数字1位左移四位,每移一位乘于2
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
//加载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
final float loadFactor;final HashMap.Node<K,V>[] resize() {//让oldTap指向table数组HashMap.Node<K,V>[] oldTab = table;//判断oldTap是否为空,若为空,把0赋给oldCap,若不为空,把oldTap的长度赋给oldCap//oldCap负责记录原容量int oldCap = (oldTab == null) ? 0 : oldTab.length;//把阈值赋给oldThr,负责记录原阈值int oldThr = threshold;//定义newCap新容量,newThr新阈值,初始化为0int newCap, newThr = 0;if (oldCap > 0) {if (oldCap >= MAXIMUM_CAPACITY) {threshold = Integer.MAX_VALUE;return oldTab;}else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&oldCap >= DEFAULT_INITIAL_CAPACITY)newThr = oldThr << 1; // double threshold}else if (oldThr > 0) // initial capacity was placed in thresholdnewCap = oldThr;else {               // zero initial threshold signifies using defaults//将默认大小赋给newCapnewCap = DEFAULT_INITIAL_CAPACITY;//newThr是通过加载因子计算出来的临界值,当占用空间到达这个临界值,table表就会进行扩容newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);}if (newThr == 0) {float ft = (float)newCap * loadFactor;newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?(int)ft : Integer.MAX_VALUE);}//将计算出来的新临界值赋给记录阈值的常数threshold = newThr;@SuppressWarnings({"rawtypes","unchecked"})//定义新的table表newTab,大小为新容量newCapHashMap.Node<K,V>[] newTab = (HashMap.Node<K,V>[])new HashMap.Node[newCap];//让table指向这个新表table = newTab;//如果原table表不等于空if (oldTab != null) {for (int j = 0; j < oldCap; ++j) {HashMap.Node<K,V> e;if ((e = oldTab[j]) != null) {oldTab[j] = null;if (e.next == null)newTab[e.hash & (newCap - 1)] = e;else if (e instanceof HashMap.TreeNode)((HashMap.TreeNode<K,V>)e).split(this, newTab, j, oldCap);else { // preserve orderHashMap.Node<K,V> loHead = null, loTail = null;HashMap.Node<K,V> hiHead = null, hiTail = null;HashMap.Node<K,V> next;do {next = e.next;if ((e.hash & oldCap) == 0) {if (loTail == null)loHead = e;elseloTail.next = e;loTail = e;}else {if (hiTail == null)hiHead = e;elsehiTail.next = e;hiTail = e;}} while ((e = next) != null);if (loTail != null) {loTail.next = null;newTab[j] = loHead;}if (hiTail != null) {hiTail.next = null;newTab[j + oldCap] = hiHead;}}}}}return newTab;
}
HashSet的扩容和转成红黑树机制

1.HashSet底层是HashMap,第一次添加时,table数组扩容到16,临界值(threshold)是16*加载因子(loadFactor)是0.75=12

2.如果table数组使用到了临界值12,就会扩容到16*2=32,新的临界值就是32*0.75 =24,依次类推

3.在Java8中,如果一条链表的元素个数到达TREEIFY_THRESHOLD(默认是8),并且table的大小>=MIN_TREEIFY_CAPACITY(默认64)(table大小不满足会先对table进行扩容),就会进行树化(红黑树),否则仍然采用数组扩容机制

(三)LinkedHashSet

(1)LinkedHashSet说明

1)LinkedHashSet是HashSet的子类

2)LinkedHashSet底层是一个LinkedHashMap(是HashMap的子类),底层维护了一个数组+双向链表

public LinkedHashSet() {//调用父类HashSet的构造器super(16, .75f, true);
}HashSet(intinitialCapacity, float loadFactor, boolean dummy) {//底层是一个LinkedHashMap(是HashMap的子类)map = new LinkedHashMap<>(initialCapacity, loadFactor);
}public LinkedHashMap(int initialCapacity, float loadFactor) {//调用父类HashMap的构造器super(initialCapacity, loadFactor);accessOrder = false;
}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);
}

3)LinkedHashSet根据元素的hashCode值来决定元素的存储位置,同时使用链表维护元素的次序,这使得元素看起来是以插入顺序保存的。

4)LinkedHashSet不允许添重复元素

5)添加第一次时,直接将 数组table 扩容到 16 ,存放的结点类型是 LinkedHashMap$Entry

6) 数组是 HashMap$Node[] 类型,存放的元素/数据是 LinkedHashMap$Entry类型

(2)LinkedHashSet底层结构

1)在LinkedHastSet中维护了一个hash表和双向键表(LinkedHashSet有head和tail)

transient LinkedHashMap.Entry<K,V> head;
transient LinkedHashMap.Entry<K,V> tail;

2)每一个节点有before和after属性,这样可以形成双向链表

static class Entry<K,V> extends HashMap.Node<K,V> {LinkedHashMap.Entry<K,V> before, after;Entry(int hash, K key, V value, HashMap.Node<K,V> next) {super(hash, key, value, next);}
}

3)在添加一个元素时,先求hash值,在求索引,,确定该元素在table的位置,然后将添加的元素加入到双向链表(如果已经存在,不添加(原则和hashset一样)

HashMap.Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {LinkedHashMap.Entry<K,V> p =new LinkedHashMap.Entry<K,V>(hash, key, value, e);linkNodeLast(p);return p;
}private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {LinkedHashMap.Entry<K,V> last = tail;tail = p;if (last == null)head = p;else {p.before = last;last.after = p;}
}

4)这样的话,我们遍历LinkedHashSet也能确保插入顺序和遍历顺序一致

(四)TreeSet

(1)TreeSet说明

1)当我们使用无参构造器,创建 TreeSet 时,默认按字符大小排序

2)想要进行排序,要使用 TreeSet 提供的一个构造器,可以传入一个比较器(匿名内部类)并指定排序规则

TreeSet treeSet = new TreeSet(new Comparator() {@Overridepublic int compare(Object o1, Object o2) {//要求加入的元素,按照长度大小排序return ((String) o1).length() - ((String) o2).length();}
});
(2)TreeSet底层结构

1)构造器把传入的比较器对象,赋给了 TreeSet 的底层的 TreeMap 的属性 this.comparator

//TreeSet构造器调用的是TreeMap构造器
public TreeSet(Comparator<? super E> comparator) {this(new TreeMap<>(comparator));
}//TreeMap构造器
public TreeMap(Comparator<? super K> comparator) {this.comparator = comparator;
}

2)在 调用 treeSet.add("tom"), 在底层会执行到我们的匿名内部类(对象)

if (cpr != null) {//cpr 就是我们的匿名内部类(对象)do {parent = t;//动态绑定到我们的匿名内部类(对象)comparecmp = cpr.compare(key, t.key);if (cmp < 0)t = t.left;else if (cmp > 0)t = t.right;else //如果相等,即返回 0,替换原先的值return t.setValue(value);} while (t != null);
}

三、Map

1.Map接口

(一)Map 接口实现类的特点

(1)用于保存具有映射关系的数据;Key-Value

(2)Map中的key和value可以是任何引用类型的数据,会封装到HashMap$Node对象中

(3)Map中的key不允许重复,原因和HashSet一样.

(4)Map中的value可以重复

(5)Map的key可以为null,value也可以为null,注意key为null,只能有一个,value为null ,可以多个.

(6)常用String类作为Map的key

(7)key和value之间存在单向一对一关系,即通过指定的key总能找到对应的value

(8)为了方便遍历,创建了Set接口的集合,集合名为EntrySet ,定义的类型是 Map.Entry,底层会把node结点转成Entry类型,再放到EntrySet集合中,因为HashMap$Node实现了Map.Entry接口

Map map = new HashMap();
map.put("no1", "小明");
map.put("no2", "张无忌");
map.put(new Car(), new Person());//返回一个entrySet集合
Set set = map.entrySet();
System.out.println(set.getClass());// HashMap$EntrySet
for (Object obj : set) {//为了从 HashMap$Node 取出k-v//1. 先做一个向下转型Map.Entry entry = (Map.Entry) obj;System.out.println(entry.getKey() + "-" + entry.getValue() );
}

(二)Map 接口常用方法

方法说明使用
put(key,value)添加,若存在相同的key,会进行替换
map.put(null, " 刘亦菲 ")
remove(key)根据键删除映射关系
map.remove(null)
get(key)根据键获取值
Object val = map.get(null )
size()获取元素个数
map.size()
isEmpty()判断个数是否为0map.isEmpty()
clear()清除所有元素

map.clear()

containsKey(key)查找键是否存在map.containsKey(null)
keySet()获取所有的值,封装成一个Set集合Set key = map.keySet() 
entrySet()获取所有键值对,封装成一个Set集合Set entrys = map.enteySet()
values()获取所有的值,封装成一个Collection集合Collection value = map.values()

(三)Map六大遍历方式

准备代码:

Map map = new HashMap();
map.put("邓超", "孙俪");
map.put("王宝强", "马蓉");
map.put("宋喆", "马蓉");
map.put("刘令博", null);
map.put(null, "刘亦菲");
map.put("鹿晗", "关晓彤");
(1)使用keySet()方法遍历
//第一组: 先取出 所有的Key , 通过Key 取出对应的Value
Set keyset = map.keySet();
  1. 迭代器遍历

//迭代器
Iterator iterator = keyset.iterator();
while (iterator.hasNext()) {Object key =  iterator.next();System.out.println(key + "-" + map.get(key));
} 
  1. 增强for遍历
//增强for
for (Object key : keyset) {System.out.println(key + "-" + map.get(key));
}
(2)使用values()方法遍历
//把所有的values取出
Collection values = map.values();
  1. 迭代器遍历

//迭代器
Iterator iterator2 = values.iterator();
while (iterator2.hasNext()) {Object value =  iterator2.next();System.out.println(value);
}
  1. 增强for遍历
//增强for
for (Object value : values) {System.out.println(value);
}
(3)使用entrySey()方法遍历
//通过EntrySet 来获取 k-v
Set entrySet = map.entrySet();// EntrySet<Map.Entry<K,V>>
  1. 迭代器遍历

//迭代器
Iterator iterator3 = entrySet.iterator();
while (iterator3.hasNext()) {Object entry =  iterator3.next();//HashMap$Node -实现-> Map.Entry (getKey,getValue)//向下转型 Map.EntryMap.Entry m = (Map.Entry) entry;System.out.println(m.getKey() + "-" + m.getValue());
}
  1. 增强for遍历
//增强for
for (Object entry : entrySet) {//将entry 转成 Map.EntryMap.Entry m = (Map.Entry) entry;System.out.println(m.getKey() + "-" + m.getValue());
}

2.HashMap

(一)HashMap说明

2)HashMap是Map接口使用频率最高的实现类。

3)HashMap是以key-value对的方式来存储数据(HashMap$Node类型)

4)key不能重复,但是值可以重复,允许使用nul键和null值。

5)如果添加相同的key,则会覆盖原来的key-value,等同于修改.(key不会替换,value会替换)

6)与HashSet一样,不保证映射的顺序,因为底层是以hash表的方式来存储的.(jdk8的hashMap底层数组+链表+红黑树)

7)HashMap没有实现同步,因此是线程不安全的,方法没有做同步互斥的操作,没有synchronized

(二)HashMap底层结构

扩容机制(和HashSet相同)

1)HashMap底层维护了Node类型的数组table,默认为null

2)当创建对象时,将加载因子(loadfactor)初始化为0.75.

static final float DEFAULT_LOAD_FACTOR = 0.75f;public HashMap() {this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}

3)当添加key-val时,通过key的哈希值得到在table的索引。然后判断该索引处是否有元素,如果没有元素直接添加。如果该索引处有元素,继续判断该元素的key和准备加入的key相是否等,如果相等,则直接替换val;如果不相等需要判断是树结构还是链表结构,做出相应处理。如果添加时发现容量不够,则需要扩容。

4)第1次添加,则需要扩容table容量为16,临界值(threshold)为12(16*0.75)

5)以后再扩容,则需要扩容table容量为原来的2倍(32),临界值为原来的2倍,即24,依次类推.

6)在Java8中,如果一条链表的元素个数超过TREEIFY_THRESHOLD(默认是8),并且table的大小>=MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树)

3.Hashtable

(一)Hashtable说明

1)存放的元素是键值对:即K-V

2)hashtable的键和值都不能为null,否则会抛出NullPointerException

3)hashTable使用方法基本上和HashMap—样

4)hashTable是线程安全的(synchronized),hashMap是线程不安全的

(二)Hashtable底层结构

1)底层有数组 Hashtable$Entry[] 初始化大小为 11

//Hashtable的构造器
public Hashtable() {this(11, 0.75f);
}
//Entry内部类
private static class Entry<K,V> implements Map.Entry<K,V>

2)临界值 threshold = 11 * 0.75(8)

扩容:

3)执行 方法 addEntry(hash, key, value, index); 添加K-V 封装到Entry

4)当 if (count >= threshold) 满足时,就进行扩容

5)按照 int newCapacity = (oldCapacity << 1) + 1; 的大小扩容.(即原先大小的两倍再加1)

4.HashMap和Hashtable对比

版本线程安全(同步)效率允许null键null值
HashMap1.2不安全可以
Hashtable1.0安全较低不可以

5.Properties

(一)Properties说明

(1)Properties类继承自Hashtable类并且实现了Map接口,也是使用一种键值对的形式来保存数据。

(2)Properties 继承 Hashtable,他的使用特点和Hashtable类似

(3)Properties还可以用于从xxx.properties文件中,加载数据到Properties类对象,并进行读取和修改

(4)工作后xxx.properties文件通常作为配置文件,这个知识点在IO流详解

7.TreeMap

(一)TreeMap说明

(1)使用无参构造器,创建 TreeMap 时,会使用键的默认比较器(key的自然排序)排序,对于大多数内置的数据类型(如整数,字符串等),都有默认的比较器,可以直接使用

(2)想要进行排序,要使用 TreeMap 提供的一个构造器,可以传入一个比较器(匿名内部类)并指定排序规则

TreeMap treeMap = new TreeMap(new Comparator() {@Overridepublic int compare(Object o1, Object o2) {//按照传入的 k(String) 的大小进行排序return ((String) o2).compareTo((String) o1);}
});

(二)TreeMap底层结构

(1)构造器把传入的实现了 Comparator接口的匿名内部类(对象),传给给TreeMap的comparator

public TreeMap(Comparator<? super K> comparator) {this.comparator = comparator;
}

(2)调用put方法

2.1)第一次添加, 把k-v 封装到 Entry对象,放入root

Entry<K,V> t = root;
if (t == null) {compare(key, key); // 检查是否为空root = new Entry<>(key, value, null);size = 1;modCount++;return null;
}

2.2)以后添加

Comparator<? super K> cpr = comparator;
if (cpr != null) {do { //遍历所有的key , 给当前key找到适当位置parent = t;cmp = cpr.compare(key, t.key);//动态绑定到我们的匿名内部类的compareif (cmp < 0)t = t.left;else if (cmp > 0)t = t.right;else  //如果遍历过程中,发现准备添加Key 和当前已有的Key 相等,会替换值,但不替换键return t.setValue(value);} while (t != null);
}

四、开发中如何选择集合实现类

在开发中,选择什么集合实现类,主要取决于业务操作特点,然后根据集合实现类特性进行选择,分析如下:

1. 先判断存储的类型(一组对象[单列]或一组键值对[双列])

2. 一组对象[单列]:Collection接口

        允许重复:List

                增删多:LinkedList [底层维护了一个双向链表]

                改查多:ArrayList [底层维护Object类型的可变数组]

        不允许重复:Set

                无序:HashSet[底层是HashMap,维护了一个哈希表即(数组+链表+红黑树)】

                排序:TreeSet

                插入和取出顺序一致:LinkedHashSet,维护数组+双向链表

3. 一组键值对[双列]:Map

        键无序:HashMap[底层是:哈希表jdk7:数组+链表,jdk8:数组+链表+红黑树]

        键排序:TreeMap

        键插入和取出顺序一致:LinkedHashMap

        读取文件Properties

五、Collections工具类

1.Collections 工具类介绍

(1)Collections是一个操作Set、List和Map等集合的工具类

(2)Collections中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作

2.排序操作:(均为 static 方法)

(1)reverse(List):反转List中元素的顺序

Collections.reverse(list);

(2)shuffle(List):对List集合元素进行随机排序

Collections.shuffle(list);

(3)sort(List):根据元素的自然顺序对指定List集合元素按升序排序

Collections.sort(list);

(4)sort(List,Comparator):根据指定的Comparator产生的顺序对List集合元素进行排序

//按照字符串的长度大小排序
Collections.sort(list, new Comparator() {@Overridepublic int compare(Object o1, Object o2) {//可以加入校验代码. return ((String) o2).length() - ((String) o1).length();}
});

(5)swap(List,int,int):将指定list集合中的i处元素和j处元素进行交换

Collections.swap(list, 0, 1);

3.查找、替换

(1)Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素

Collections.max(list)

(2)Object max(Collection,Comparator):根据Comparator指定的顺序,返回给定集合中的最大元素

//比如,我们要返回长度最大的元素
Object maxObject = Collections.max(list, new Comparator() {@Overridepublic int compare(Object o1, Object o2) {return ((String)o1).length() - ((String)o2).length();}
});

(3)Object min(Collection):根据元素的自然顺序,返回给定集合中的最小元素

(4)Object min(Collection,Comparator):根据Comparator指定的顺序,返回给定集合中的最小元素

(5)int frequency(Collection,Object):返回指定集合中指定元素的出现次数

Collections.frequency(list, "tom")

(6)void copy(List dest,List src):将src中的内容复制到dest中,如果src的大小大于dest的大小会抛出数值越界的异常

Collections.copy(dest, list);

(7)boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换List对象的所有旧值

Collections.replaceAll(list, "tom", "汤姆");

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

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

相关文章

159 Linux C++ 通讯架构实战14,epoll 函数代码实战

ngx_epoll_init函数的调用 //&#xff08;3.2&#xff09;ngx_epoll_init函数的调用&#xff08;要在子进程中执行&#xff09; //四章&#xff0c;四节 project1.cpp&#xff1a;nginx中创建worker子进程&#xff1b; //nginx中创建worker子进程 //官方nginx ,一个…

HarmonyOS 应用开发之页面和自定义组件生命周期

在开始之前&#xff0c;我们先明确自定义组件和页面的关系&#xff1a; 自定义组件&#xff1a;Component装饰的UI单元&#xff0c;可以组合多个系统组件实现UI的复用&#xff0c;可以调用组件的生命周期。 页面&#xff1a;即应用的UI页面。可以由一个或者多个自定义组件组成…

Topaz Video AI for Mac v5.0.0激活版 视频画质增强软件

Topaz Video AI for Mac是一款功能强大的视频处理软件&#xff0c;专为Mac用户设计&#xff0c;旨在通过人工智能技术为视频编辑和增强提供卓越的功能。这款软件利用先进的算法和深度学习技术&#xff0c;能够自动识别和分析视频中的各个元素&#xff0c;并进行智能修复和增强&…

区块链web3智能合约开发学习-开发工具Remix(1)

学习区块链中常用语言solidity时&#xff0c;我们会用到特别的开发工具&#xff0c;对于学习前期&#xff0c;建议是将代码写到Remix IDE中进行编译部署和测试&#xff0c;这就是我们编写和交互智能合约的地方&#xff0c; 在线remix编译器&#xff1a; https://remix.ethereu…

腾讯云(CVM)托管进行权限维持

前言 刚好看到一个师傅分享了一个阿里云ECS实战攻防&#xff0c;然后想到了同样利用腾讯云CVM的托管亦可实现在实战攻防中的权限维持。 简介 腾讯云自动化助手&#xff08;TencentCloud Automation Tools&#xff0c;TAT&#xff09;是一个原生运维部署工具&#xff0c;它可…

单片机中的RAM vs ROM

其实&#xff0c;单片机就是个小计算机。大计算机少不了的数据存储系统&#xff0c;单片机一样有&#xff0c;而且往往和CPU集成在一起&#xff0c;显得更加小巧灵活。 直到90年代初&#xff0c;国内容易得到的单片机是8031&#xff1a;不带存储器的芯片&#xff0c;要想工作&a…

基于SpringBoot的“校园志愿者管理系统”的设计与实现(源码+数据库+文档+PPT)

基于SpringBoot的“校园志愿者管理系统”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 系统总体结构图 系统首页界面图 志愿者注册…

基于DCT和扩频的音频水印嵌入提取算法matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 ......................................................................... N 10; %嵌入一…

每日一题:用c语言写(输入n个数(n小于等于100),输出数字2的出现次数)

目录 一、要求 二、代码 三、结果 ​四、注意 一、要求 二、代码 #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> int main() {//输入n个数&#xff08;n小于等于100&#xff09;&#xff0c;输出数字2的出现次数;int n[100] ;int num 0;int count 0;/…

华清远见STM32MP157开发板助力嵌入式大赛ST赛道MPU应用方向项目开发

第七届&#xff08;2024&#xff09;全国大学生嵌入式芯片与系统设计竞赛&#xff08;以下简称“大赛”&#xff09;已经拉开帷幕&#xff0c;大赛的报名热潮正席卷而来。嵌入式大赛截止今年已连续举办了七届&#xff0c;为教育部认可的全国普通高校大学生国家级A类赛事&#x…

关于Windows中的系统还原工具的知识,看这篇文章就差不多了

序言 Windows中的系统还原工具是可用的更有用的实用程序之一,通常是尝试修复Windows中的主要问题的第一步。 简而言之,Windows系统还原工具允许你执行的操作是还原到以前的软件、注册表和驱动程序配置(称为还原点)。这就像“撤消”对Windows的最后一次重大更改,将计算机…

golang语言系列:Web框架+路由 之 Gin

云原生学习路线导航页&#xff08;持续更新中&#xff09; 本文是golang语言学习系列&#xff0c;本篇对Gin框架的基本使用方法进行学习 1.Gin框架是什么 Gin 是一个 Go (Golang) 编写的轻量级 http web 框架&#xff0c;运行速度非常快&#xff0c;如果你是性能和高效的追求者…

2024年京东云主机租用价格_京东云服务器优惠价格表

2024年京东云服务器优惠价格表&#xff0c;轻量云主机优惠价格5.8元1个月、轻量云主机2C2G3M价格50元一年、196元三年&#xff0c;2C4G5M轻量云主机165元一年&#xff0c;4核8G5M云主机880元一年&#xff0c;游戏联机服务器4C16G配置26元1个月、4C32G价格65元1个月、8核32G费用…

ALPHA开发板上PHY网络芯片LAN8720:常用的几个寄存器功能

一. 简介 正点原子的开发板 ALPHA开发板&#xff0c;有线网络硬件方案所使用的也是最常用的一种方案&#xff0c;IMX6ULL芯片内部是自带 MAC网络芯片的&#xff0c;所以&#xff0c;也就是采用 "SOC内部集成网络MAC外设 PHY网络芯片方案"。 前面一篇文章简单了解了…

基于OSPF的企业内网安全优化

1.拓扑 2.IP地址规划 设备/地址/vlan设备/地址汇聚交换机/VLAN10192.200.10.0/24汇聚交换机/VLAN20192.200.20.0/24汇聚交换机/VLAN30192.200.30.0/24汇聚交换机/VLAN40192.200.40.0/24汇聚交换机/VLAN50192.200.50.0/24汇聚交换机/VLAN60192.200.60.0/24防火墙/VLAN70/服务器…

uniapp-设置UrlSchemes从外部浏览器H5打开app

需求&#xff1a;外部浏览器H5页面&#xff0c;跳转到uniapp开发的原生app内部。 1、uniapp内部的配置&#xff1a; &#xff08;1&#xff09;打开manifest->App常用其他设置&#xff0c;如下&#xff0c;按照提示输入您要设置的urlSchemes&#xff1a; &#xff08;2&am…

JVS智能BI数据分析:图表的数据联动配置详解

图表的数据联动 图表的数据联动是指在可视化图表中&#xff0c;当一个图表的数据发生变化时&#xff0c;另一个图表中的数据也会自动更新。这种功能通常用于展示相互关联的数据集&#xff0c;帮助用户更直观地了解数据之间的关系和趋势。 我们先看看实际的效果&#xff0c;如下…

springboot配置文件application.properties,application.yml/application.yaml

application.properties Springboot提供的一种属性配置方式&#xff1a;application.properties 初始时配置文件中只有一行语句。 启动时&#xff0c;比如tomcat的端口号默认8080&#xff0c;路径默认。如果我们要改变端口号及路径之类的可以在application.properties中配置。…

Web日志/招聘网站/电商大数据项目样例【实时/离线】

Web服务器日志分析项目 业务分析 业务背景 ​ 某大型电商公司&#xff0c;产生原始数据日志某小时达4千五万条&#xff0c;一天日志量月4亿两千万条。 主机规划 &#xff08;可略&#xff09;日志格式&#xff1a; 2017-06-1900:26:36101.200.190.54 GET /sys/ashx/ConfigH…

视频汇聚/安防监控/视频存储EasyCVR平台EasyPlayer播放器更新:新增【性能面板】

视频汇聚/安防监控/视频存储平台EasyCVR基于云边端架构&#xff0c;可以在复杂的网络环境中快速、灵活部署&#xff0c;平台视频能力丰富&#xff0c;可以提供实时远程视频监控、视频录像、录像回放与存储、告警、语音对讲、云台控制、平台级联、磁盘阵列存储、视频集中存储、云…