ArrayList 万字长文解析:使用、优化、源码分析

文章目录

  • ArrayList 万字长文解析:使用、优化、源码分析
    • 前言
    • ArrayList 简介
    • ArrayList 的基本使用方法
    • ArrayList 性能优化
    • ArrayList 的源码分析
      • 内部结构
      • 构造方法解析
      • 扩容机制
      • System.arraycop与 Arrays.copyof 实现方式 与 使用场景
      • 迭代器
    • JDK 8版本 ArrayList bug 示例
    • ArrayList 使用注意事项代码演示
    • ArrayList 与其他集合类的比较
    • 结尾

ArrayList 万字长文解析:使用、优化、源码分析

ArrayList 万字长文解析 使用、优化、源码分析

前言

  大家好,这里是 Rocky 编程日记 ,一位喜欢后端架构及中间件源码的CSDN博主,该篇文章主要是对 Java 集合框架体系下 ArrayList的一个解析 。

  梳理出来也是想和大伙探讨一下这块内容,同时也供大家学习交流,如若笔记中有不对的地方,那一定是我的理解还不够,希望你大胆的在评论区指出来噢~。

  同时,如果对于该笔记存在很多疑惑,也欢迎和我交流讨论,最后也感谢您的阅读,嘿嘿,期待你的点赞,关注,收藏噢~。

  前人述备矣,我只是知识的搬运工,ArrayList 文中代码示例皆在开源代码仓库中,源代码仓库地址为: https://gitee.com/Rocky-BCRJ/java-diary.git。欢迎star~。

  同时,本文只是对 ArrayList 核心方法的一些讲解,如果有一些相关方法未讲解到的,欢迎评论区留言讨论,后续我也会把 JDK 注释的开源代码同步出来,敬请期待~。

ArrayList 简介

  ArrayList 是一种动态数组,可以在运行时自动扩展容量。它可以存储任何类型的对象,并提供高效的随机访问和快速的插入/删除操作。ArrayList 有以下优点:

  1. 高效的随机访问:ArrayList 内部使用数组实现,因此可以通过下标快速访问任意元素。
  2. 动态扩容:ArrayList 可以在运行时自动扩展容量,因此可以存储任意数量的元素。
  3. 可以存储任何类型的对象:ArrayList 可以存储任何类型的对象,包括基本类型和自定义类型。
  4. 支持插入/删除操作:ArrayList 内部使用数组实现,因此可以通过移动元素来实现插入和删除操作。

然而,ArrayList 也有一些缺点:

  1. 插入和删除操作可能会导致数组元素的移动,因此在大量插入和删除操作时,ArrayList 的性能可能会受到影响。
  2. ArrayList 内部使用数组实现,因此在插入和删除操作时,可能需要重新分配数组空间,这可能会导致性能下降。
  3. ArrayList 只能存储对象,不能存储基本类型,因此需要将基本类型包装成对象,这可能会导致一些额外的开销。

ArrayList 的基本使用方法

该段介绍 ArrayList 的基本操作,如添加、删除、遍历等。

package cn.rocky.other;

import java.util.ArrayList;
import java.util.Iterator;

public class ArrayListDemo {

    public static void main(String[] args) {
        // 创建 ArrayList 对象
        ArrayList<String> list = new ArrayList<>();

        // 添加元素
        list.add("Java");
        list.add("Python");
        list.add("C++");

        // 获取元素
        System.out.println("第二个元素是:" + list.get(1));

        // 修改元素
        list.set(1, "JavaScript");
        System.out.println("修改后的第二个元素是:" + list.get(1));

        // 删除元素
        list.remove(0);
        System.out.println("删除后的第一个元素是:" + list.get(0));

        System.out.println("for遍历元素:");
        // for
        for (int i = 0; i < list.size(); i++) {
            System.out.print(list.get(i) + " ");
        }

        System.out.println();
        System.out.println("for each 遍历元素:");
        // for each 遍历元素
        for (String s : list) {
            System.out.print(s + " ");
        }

        System.out.println();
        System.out.println("迭代器遍历元素:");
        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            String element = iterator.next();
            System.out.print(element + " ");
        }
    }
}

ArrayList 性能优化

当使用 ArrayList 时,可以考虑以下性能优化技巧:

  1. 指定 ArrayList 的初始容量:在创建 ArrayList 时,可以指定其初始容量,这可以避免在添加元素时频繁地扩展数组。例如,如果预计 ArrayList 中将包含 100 个元素,可以使用以下代码创建 ArrayList:
ArrayList<String> list = new ArrayList<>(100);
  1. 使用 Iterator 迭代器遍历 ArrayList:使用 Iterator 迭代器遍历 ArrayList 可以避免使用 for 循环时频繁地调用 get() 方法。例如:
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    String element = iterator.next();
    // do something with element
}
  1. 使用 for-each 循环遍历 ArrayList:使用 for-each 循环遍历 ArrayList 可以使代码更加简洁,也可以避免使用 for 循环时频繁地调用 get() 方法。例如:
for (String element : list) {
    // do something with element
}
  1. 使用 toArray() 方法将 ArrayList 转换为数组:如果需要频繁地访问 ArrayList 中的元素,可以使用 toArray() 方法将 ArrayList 转换为数组,以提高访问速度。例如:
String[] array = list.toArray(new String[0]);
  1. 使用 subList() 方法获取子列表:如果需要对 ArrayList 中的部分元素进行操作,可以使用 subList() 方法获取子列表,以避免对整个 ArrayList 进行操作。例如:
List<String> subList = list.subList(0, 10);
// do something with subList

  这些技巧可以帮助提高 ArrayList 的性能,但需要根据具体情况进行选择。在实际使用中,还需要考虑其他因素,比如数据量、操作频率等。

ArrayList 的源码分析

  在这一部分中,我们将深入分析 ArrayList 的源码,以深入理解其实现原理和性能特点。我们将会讨论 ArrayList 的内部结构、扩容机制、迭代器实现等重要内容。

内部结构

  1. 数组元素:ArrayList 内部使用 Object 类型的数组来存储元素,可以通过数组下标来访问元素。
  2. 元素数量:ArrayList 中的元素数量,也就是当前数组中实际存储的元素数量。
  3. 容量大小:ArrayList 中数组的容量大小,也就是数组能够存储的最大元素数量。当元素数量超过容量大小时,ArrayList 会自动扩容。
  4. 修改次数:ArrayList 中记录修改次数的变量,用于在迭代器遍历时检测是否有其他线程对 ArrayList 进行修改。
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    // 默认初始化容量 10
		private static final int DEFAULT_CAPACITY = 10;
	  // 共享的空数组对象
    private static final Object[] EMPTY_ELEMENTDATA = {};
    // 共享的空数组对象 
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    // 思考? 这里为啥要加 transient。
    // 存储 ArrayList 中元素的数组
    transient Object[] elementData; 
		// 实际存储的元素数量
    private int size;
    // 父类的元素
    // modCount 变量表示 ArrayList 的结构修改次数,
    // 用于在迭代器遍历时检测是否发生了结构性修改,如果发生了则抛出 ConcurrentModificationException 异常
    protected transient int modCount = 0;
}  

构造方法解析

		// 默认构造函数
		public ArrayList() {
        // 将 elementData 成员变量初始化为一个空数组,表示 ArrayList 的初始容量为 0。
        // 当第一个元素被添加到 ArrayList 中时,elementData 数组会被扩容成默认容量 10。
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
		public ArrayList(int initialCapacity) {
        // 初始化容量大于 0 时,创建 Object 数组
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        }
         // 初始化容量等于 0 时,使用 EMPTY_ELEMENTDATA 对象
        else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } 
        // 初始化容量小于 0 时,抛出 IllegalArgumentException 异常
        else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }
		public ArrayList(Collection<? extends E> c) {
      	// 将 集合 c 转换成 Object 数组。 思考?这里能用 elementData去接么 element = c.toArray(); 
        Object[] a = c.toArray();
      	// 数组包含 元素时(length != 0)
        if ((size = a.length) != 0) {
            // 类型为 ArrayList,直接赋值
            if (c.getClass() == ArrayList.class) {
                elementData = a;
            } 
            // 集合元素不是 Object[] 类型,则会创建 新的 Object[] 数组,并将 元素赋值到其中,最后赋值给 elementData。 
            else {
                elementData = Arrays.copyOf(a, size, Object[].class);
            }
        } 
        // 如果数组中不包含 元素时(length == 0),则使用 EMPTY_ELEMENTDATA 。
        else {
            elementData = EMPTY_ELEMENTDATA;
        }
    }

扩容机制

 		public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        // 加元素
        elementData[size++] = e;
        return true;
    }
		private void ensureCapacityInternal(int minCapacity) {
	// 计算 calculate 得到的容量大小 ,判断是否需要进行扩容 
      ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    // 计算容量 1.当前数组 2.minCapacity = size + 1
		private static int calculateCapacity(Object[] elementData, int minCapacity) {
        // 如果当前数组等于{}, 返回默认初始化大小 10;
      	if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
      	// 返回 size + 1;
        return minCapacity;
    }
		private void ensureExplicitCapacity(int minCapacity) {
        // 修改次数 +1
      	modCount++;

        // overflow-conscious code
      	// 需要的minCapacity容量大于数组元素长度,则需要进行扩容
        if (minCapacity - elementData.length > 0)
          	// 扩容
            grow(minCapacity);
    }
	 // 扩容方法	
	 private void grow(int minCapacity) {
        // overflow-conscious code 
     		// 意思是指在编写代码时考虑数据溢出的情况,避免因为数据溢出而导致程序出错或崩溃。
     		// 例如,在进行数值计算时,如果结果超出了数据类型的范围,就会发生数据溢出,此时就需要使用 overflow-conscious code 来避免这种情况。
     	  // 旧数组容量大小,也即当前数组容量
        int oldCapacity = elementData.length;
     	  // 计算新数组容量,新数组容量 = 旧数组容量 + 1/2旧数组容量
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        // 如果新数组容量小于最小容量,则将最小容量作为新的数组容量
     		if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        // 如果新的数组容量超过了数组最大容量(Integer.Max - 8),则调用 hugeCapacity 方法进入扩容
      	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);
    }
		// 计算扩容的最大容量, 在 Java 中,数组的最大长度是 Integer.MAX_VALUE, hugeCapacity 方法就能保证在扩容时,不会超过数组的最大长度限制。
		private static int hugeCapacity(int minCapacity) {
        // 传入的 minCapacity 小于 0,抛出 oom
      	if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        // 如果 minCapacity 大于 MAX_ARRAY_SIZE,则返回 Integer.MAX_VALUE,否则返回 MAX_ARRAY_SIZE。
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

System.arraycop与 Arrays.copyof 实现方式 与 使用场景

System.arraycopy()Arrays.copyOf() 都可以用来复制一个数组,但是它们的实现方式和使用场景略有不同。

System.arraycopy() 是一个本地方法,它可以快速地将一个数组的一部分复制到另一个数组中的指定位置。它的语法如下:

public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);

其中,src 是源数组,srcPos 是源数组中要复制的起始位置,dest 是目标数组,destPos 是目标数组中要复制到的起始位置,length 是要复制的元素数量。

Arrays.copyOf() 是一个静态方法,它可以将一个数组复制到一个新的数组中,并且可以指定新数组的长度。它的语法如下:

public static <T> T[] copyOf(T[] original, int newLength);

public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        @SuppressWarnings("unchecked")
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }

其中,original 是要复制的原始数组,newLength 是新数组的长度。

总的来说,System.arraycopy() 适用于需要高效复制数组的场景,而 Arrays.copyOf() 则更适用于需要创建一个新数组并且长度不确定的场景。另外,Arrays.copyOf() 还支持将原始数组的元素类型转换为另一种类型,而 System.arraycopy() 不支持。

迭代器

  1. 迭代器的作用:迭代器是一种用于遍历集合类中元素的工具,它可以让我们轻松地遍历 ArrayList 中的元素,而不需要使用传统的 for 循环或者 foreach 循环。

  2. 迭代器的使用方法:通过调用 ArrayList 的 iterator() 方法可以获取一个迭代器对象,然后可以使用 hasNext() 方法和 next() 方法来遍历 ArrayList 中的元素。需要注意的是,在使用迭代器遍历元素时,不能直接使用 ArrayList 的 get() 方法获取元素。

  3. 迭代器的优点:相比传统的 for 循环或者 foreach 循环,使用迭代器遍历集合类中的元素有以下几个优点:

    • 可以在遍历过程中删除元素,而不会出现 ConcurrentModificationException 异常。
    • 可以遍历所有实现了 Iterable 接口的集合类,而不需要关心具体的集合类型。
    • 可以避免使用索引来访问元素,从而避免出现数组越界等异常。
  4. 迭代器的缺点:使用迭代器遍历集合类中的元素也有一些缺点:

    • 不能在遍历过程中修改元素,否则会出现 ConcurrentModificationException 异常。
    • 遍历过程中不能向集合中添加新的元素,否则会导致遍历不完整。

JDK 8版本 ArrayList bug 示例

public class TestList {

    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 2, 3);
        Object[] array = list.toArray();
        // JDK8 返回 Integer[] 数组,JDK9+ 返回 Object[] 数组。
        System.out.println("class name : " + array.getClass().getSimpleName());
        // JDK 8 和 JDK9+ 表现不同, 前者会报 ArrayStoreException 异常, 后者不会 
        array[0] = new Object();
    }
}

ArrayList 使用注意事项代码演示

  当使用 subList() 方法获取 ArrayList 的子列表时,返回的是一个新的列表对象,但是它与原来的列表对象共享同一个数据存储区域,也就是说,对子列表的修改会影响到原来的列表对象。如果在对子列表进行修改的同时,原来的列表对象也被修改了,就会导致 ConcurrentModificationException 异常的抛出。

package cn.rocky.other;

import java.util.ArrayList;
import java.util.List;

public class TestList04 {

    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("A");
        list.add("B");
        list.add("C");

        List<String> subList = list.subList(0, 2);

        list.add("D"); // 修改原列表

        subList.clear(); // 修改子列表

        // 此时会抛出 ConcurrentModificationException 异常
    }
}

结果如下:

Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList$SubList.checkForComodification(ArrayList.java:1241)
	at java.util.ArrayList$SubList.size(ArrayList.java:1050)
	at java.util.AbstractList.clear(AbstractList.java:234)
	at cn.rocky.other.TestList04.main(TestList04.java:18)

  多线程环境下操作 ArrayList 也会出现一些其妙的问题,代码如下:

package cn.rocky.other;

import java.util.ArrayList;

public class TestList05 {
    public static void main(String[] args) throws InterruptedException {
        ArrayList<Integer> list = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            list.add(i);
        }

        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 500; i++) {
                list.remove(i);
            }
        });

        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 500; i++) {
                list.remove(i);
            }
        });

        thread1.start();
        thread2.start();

        thread1.join();
        thread2.join();

        System.out.println(list.size());
    }
}

结果如下:

Exception in thread "Thread-1" Exception in thread "Thread-0" java.lang.IndexOutOfBoundsException: Index: 459, Size: 276
	at java.util.ArrayList.rangeCheck(ArrayList.java:659)
	at java.util.ArrayList.remove(ArrayList.java:498)
	at cn.rocky.other.TestList05.lambda$main$1(TestList05.java:20)
	at java.lang.Thread.run(Thread.java:750)
java.lang.IndexOutOfBoundsException: Index: 276, Size: 276

ArrayList 与其他集合类的比较

  1. ArrayList:底层是基于数组实现的,因此支持随机访问和快速的插入、删除操作,但是在插入、删除元素时可能需要移动其他元素,因此效率不如 LinkedList。ArrayList 还有一个优点是它的空间利用率高,因为它的容量是动态扩展的,而且可以通过设置初始容量来避免频繁扩容。
  2. LinkedList:底层是基于链表实现的,因此支持快速的插入、删除操作,但是随机访问效率较低,因为需要从头开始遍历链表。LinkedList 还有一个优点是它支持高效的队列和栈操作,因为可以在链表的头部和尾部进行插入、删除操作。
  3. Vector:与 ArrayList 类似,底层也是基于数组实现的,但是它是线程安全的,因此在多线程环境下使用更加安全,但是效率较低。Vector 还有一个缺点是它的扩容机制是翻倍扩容,因此在扩容时可能会浪费一些空间。

结尾

  感谢您阅读本文,如果您有任何问题或建议,请在评论区留言,我将尽快回复您。如果你也有关于 ArrayList 使用的心得体会,欢迎评论区留言讨论。如果您觉得本文对您有所帮助,请点赞,评论,收藏,如果可以的话请分享给更多的人,让更多的人受益。

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

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

相关文章

【基于Rsync实现Linux To Windows文件同步】

基于Rsync实现Linux To Windows文件同步 简介安装步骤安装Linux服务器端1.安装rsync2.启动Rsync3.验证是否启动成功4.修改rsyncd.conf重启rsync服务 安装Windows客户端1.rsync客户端安装&#xff1a;2.配置环境变量3.测试rsync命令4.创建密码文件5.密码文件授权6.查看服务端需要…

Python高光谱遥感数据处理与机器学习实践技术丨Matlab高光谱遥感数据处理与混合像元分解

目录 Python高光谱遥感数据处理与机器学习实践技术 第一章 高光谱基础 第二章 高光谱开发基础&#xff08;Python&#xff09; 第三章 高光谱机器学习技术&#xff08;python&#xff09; 第四章 典型案例操作实践 Matlab 高光谱遥感数据处理与混合像元分解 第一章 理论…

【大数据之路4】分布式计算模型 MapReduce

4. 分布式计算模型 MapReduce 1. MapReduce 概述1. 概念2. 程序演示1. 计算 WordCount2. 计算圆周率 π 3. 核心架构组件4. 编程流程与规范1. 编程流程2. 编程规范3. 程序主要配置参数4. 相关问题1. 为什么不能在 Mapper 中进行 “聚合”&#xff08;加法&#xff09;&#xff…

操作系统原理 —— 什么是基本分页存储管理?(二十二)

在操作系统中&#xff0c;一个新的进程需要载入内存当中执行&#xff0c;在装入的时候需要给该进程分配一定的运行内存&#xff0c;在之前的章节中讲解了连续分配的几种方式&#xff0c;比如&#xff1a;单一连续分配、固定分区分配、动态分区分配&#xff0c;还讲解了对应的动…

Nacos架构与原理 - 总体架构

文章目录 Nacos 起源Nacos 定位Nacos 优势Nacos 生态Nacos 总体设计设计原则架构图用户层业务层内核层插件 小结 Nacos 起源 Nacos 在阿里巴巴起源于 2008 年五彩石项目&#xff08;完成微服务拆分和业务中台建设&#xff09;&#xff0c;成长于十年双十⼀的洪峰考验&#xff…

基于遗传算法的柔性生产调度研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

软件测试金融测试岗面试热点问题

1、网上银行转账是怎么测的&#xff0c;设计一下测试用例。 回答思路&#xff1a; 宏观上可以从质量模型&#xff08;万能公式&#xff09;来考虑&#xff0c;重点需要测试转账的功能、性能与安全性。设计测试用例可以使用场景法为主&#xff0c;先列出转账的基本流和备选流。…

DHT11温湿度传感器

接口定义 传感器通信 DHT11采用简化的单总线通信。单总线仅有一根数据线&#xff08;SDA&#xff09;&#xff0c;通信所进行的数据交换、挂在单总线上的所有设备之间进行信号交换与传递均在一条通讯线上实现。 单总线上必须有一个上拉电阻&#xff08;Rp&#xff09;以实现单…

burpsuite工具的使用(详细讲解)

一&#xff09;前言 我已经在之前详细的说明了burpsuite的安装过程&#xff0c;如果不了解的可以看 burpsuite安装教程 &#xff1a;http://t.csdn.cn/uVx9X 在这了补充说明一下&#xff0c;在安装完burpsuite并设置完代理后&#xff0c;会出现如果访问的url是使用http协议的…

【建议收藏】自动化测试框架开发教程

在自动化测试项目中&#xff0c;为了实现更多功能&#xff0c;我们需要引入不同的库、框架。 首先&#xff0c;你需要将常用的这些库、框架都装上。 pip install requests pip install selenium pip install appium pip install pytest pip install pytest-rerunfailures pip …

网络安全面试题大全(整理版)500+面试题附答案详解,最全面详细

前言 随着国家政策的扶持&#xff0c;网络安全行业也越来越为大众所熟知&#xff0c;想要进入到网络安全行业的人也越来越多。 为了拿到心仪的Offer之外&#xff0c;除了学好网络安全知识以外&#xff0c;还要应对好企业的面试。 作为一个安全老鸟&#xff0c;工作这么多年&…

PHY6230国产蓝牙BLE5.2 2.4G SoC低成本遥控灯控芯片

PHY6230是高性价比低功耗高性能Bluetooth LE 5.2系统级芯片&#xff0c;集成32-bit高性能低功耗MCU&#xff0c;16KB OTP&#xff0c;8KB Retention SRAM和64KB ROM&#xff0c;可选EEPROM&#xff0c;适用多种PC/手机外设连接、遥控、灯控等场景。 特点&#xff1a; 高性能多…

3年经验面试20K+测试岗,看到这样的面试题我懵了....

我要跳槽&#xff01;我是着急忙慌的准备简历——3年软件测试经验&#xff0c;可独立测试大型产品项目&#xff0c;熟悉项目测试流程...薪资要求&#xff1f;3年测试经验起码能要个20K吧 我加班肝了一页半简历&#xff0c;投出去一周&#xff0c;面试电话倒是不少&#xff0c;…

【云原生】Docker的数据卷、数据卷容器,容器互联

1.数据卷&#xff08;容器与宿主机之间数据共享&#xff09; 数据卷是一个供容器使用的特殊目录&#xff0c;位于容器中。可将宿主机的目录挂载到数据卷上&#xff0c;对数据卷的修改操作立刻可见&#xff0c;并且更新数据不会影响镜像&#xff0c;从而实现数据在宿主机与容器…

B站、抖音上那些4K、60帧视频是如何修复的?

如何把一个不清晰的视频变成高清的视频&#xff1f;今天就来教大家视频画质修复把720p的渣画质变成4K超清画质。 相信对于电影和后期爱好者来说&#xff0c;糊成马赛克的画质一定劝退了无数人&#xff0c;那不妨试试这个 牛学长视频修复工具 牛学长视频修复工具通过高级的AI…

Kubernetes配置管理

1. ConfigMap简介 Kubernetes ConfigMap是一种用于存储应用程序配置信息的对象。在企业中&#xff0c;我们通常会有许多不同的应用程序&#xff0c;每个应用程序都需要一些配置信息&#xff0c;例如数据库连接字符串、API密钥等等。这些配置信息可能会因为环境的不同而有所不同…

5.6.1 Ext JS之标签页的关闭和批零关闭

Tab Panel 是包含多个标签页的面板, 这是一种很常用的组件, 类似于浏览器的标签页。关于 Ext JS的Tab Panel的基本使用可以参考: [Ext JS3.9] 标签面板(TabPanel )介绍与开发, 本篇介绍如何关闭单个标签页和批量关闭标签页。 Tab 标签页的可关闭 默认状况下,标签页是无…

centos7下svnserve方式部署subversion/SVN服务端(实操)

一般来说&#xff0c;subversion服务器可以用两种方式架设&#xff1a; 一种是基于svnserve&#xff0c;svnserve作为服务端&#xff1b; 一种是基于Apache&#xff0c;用apache作为服务端。 这里采用第一种方式部署。 执行如下命令&#xff0c;安装SVN。 yum install sub…

学习Maven Web 应用

Maven Web 应用 本章节我们将学习如何使用版本控制系统 Maven 来管理一个基于 web 的项目&#xff0c;如何创建、构建、部署已经运行一个 web 应用。 创建 Web 应用 我们可以使用 maven-archetype-webapp 插件来创建一个简单的 Java web 应用。 打开命令控制台&#xff0c;…

区块链学习三——比特币的数据结构

区块链学习三——比特币的数据结构 文章内容来源于北京大学肖臻老师《区块链技术与应用》公开课 文章目录 区块链学习三——比特币的数据结构 一、哈希指针&#xff08;hash pointers&#xff09;二、区块链三、Merkle tree1.Merkle tree的作用&#xff1a;Merkle Proof2.Proo…