从源码分析常见集合的区别之List接口

news2025/1/11 16:57:30

说到Java集合,共有两大类分别是Collection和Map。今天就详细聊聊大家耳熟能详的List吧。

List接口实现自Collection接口,是Java的集合框架中的一员,List接口下又有ArrayListLinkedList和线程安全的Vector,今天就简单分析一下ArrayListLinkedList的异同以及各自的优势。

ArrayList

ArrayList的身世

  • AbstractList:ArrayList继承自AbstractList,AbstractList提供了一个基于数组的动态列表实现,并且它提供了通用列表操作的默认实现。这其实是抽象类的最佳实现,通过继承,可以复用抽象类已实现的通过方法,子类无需重复实现,使子类只关注自身特性的方法。
  • List: 表明ArrayList符合List接口的规范。
  • Cloneable: 表明它具有拷贝能力,可以进行深拷贝或浅拷贝操作。
  • Serializable: 使一个类可以进行序列化,即将对象转换为字节序列以便存储或传输,并在需要时将其反序列化为对象。
  • RandomAccess: 标识接口,标识实现该接口的集合支持快速随机访问元素的能力,以告诉代码选择合适的访问方式。

引用ArrayList集合中的一段代码:

/**
     * Default initial capacity.
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * Shared empty array instance used for empty instances.
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
     * Shared empty array instance used for default sized empty instances. We
     * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
     * first element is added.
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     * The array buffer into which the elements of the ArrayList are stored.
     * The capacity of the ArrayList is the length of this array buffer. Any
     * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
     * will be expanded to DEFAULT_CAPACITY when the first element is added.
     */
    transient Object[] elementData; // non-private to simplify nested class access

数据结构

构造方法

通过ArrayList的无参构造方法创建一个ArrayList对象时,Object类型的数组elementData会赋值一个空数组,我们调用ArrayList的add方法,给list插入数据时,我们才使用ArrayList的默认长度10。

    /**
     * Constructs an empty list with the specified initial capacity.
     *
     * @param  initialCapacity  the initial capacity of the list
     * @throws IllegalArgumentException if the specified initial capacity
     *         is negative
     */
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

    /**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    /**
     * Constructs a list containing the elements of the specified
     * collection, in the order they are returned by the collection's
     * iterator.
     *
     * @param c the collection whose elements are to be placed into this list
     * @throws NullPointerException if the specified collection is null
     */
    public ArrayList(Collection<? extends E> c) {
        Object[] a = c.toArray();
        if ((size = a.length) != 0) {
            if (c.getClass() == ArrayList.class) {
                elementData = a;
            } else {
                elementData = Arrays.copyOf(a, size, Object[].class);
            }
        } else {
            // replace with empty array.
            elementData = EMPTY_ELEMENTDATA;
        }
    }

扩容机制

构造一个初始长度是2的列表,调用add方法向列表中添加两个元素。查看源码调用情况:

插入指定元素到list的末尾。

/**
 * Appends the specified element to the end of this list.
 *
 * @param e element to be appended to this list
 * @return <tt>true</tt> (as specified by {@link Collection#add})
 */
public boolean add(E e) {
	ensureCapacityInternal(size + 1);  // Increments modCount!!
	elementData[size++] = e;
	return true;
}

ensureCapacityInternal:确保内部能力。

直译比较晦涩,可以理解为,在插入新的元素前,需要先确认当前List是否有足够的空间可以容纳新元素。

private void ensureCapacityInternal(int minCapacity) {
	ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

ensureCapacityInternal方法内部很简单,调用了ensureExplicitCapacity方法,入参是当前列表size+1作为当前列表的最小容量。

private void ensureExplicitCapacity(int minCapacity) {
modCount++;

// overflow-conscious code
if (minCapacity - elementData.length > 0)
    grow(minCapacity);
}
  • modCount的作用是记录结构性修改次数,当对ArrayList 进行添加或删除操作时,modCount 的值都会递增。而在迭代器进行迭代操作时,它会检查当前的 modCount 值是否与迭代器创建时记录的 expectedModCount 值相等,如果不相等,则立即抛出 ConcurrentModificationException 异常。即Fast-fail机制
  • 判断集合最小容量减去当前List的可变数组的长度是否大于0,用于判断当前List插入当前元素是否需要扩容。
    /**
     * Increases the capacity to ensure that it can hold at least the
     * number of elements specified by the minimum capacity argument.
     *
     * @param minCapacity the desired minimum capacity
     */
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        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);
    }

增加容量以确保它至少可以容纳最小容量参数指定的元素数量。

扩容时先根据当前容量,通过右移运算符计算出扩容后的动态数组大小。

将一个数的二进制表示向右移动指定的位数。右移操作等效于将操作数除以 2 的移位次数次幂。

‘>>’ '<<'运算符相较于乘和除以的优势是性能更强。

oldCapacity >> 1等价于N除以2的一次幂,即oldCapacity/2。

那么举个例子,往一个当前长度是10的数组中插入一个新的元素,那么它扩容后的新数组长度便为:

10 + (10/2)= 15;

/**
* The maximum size of array to allocate.
* Some VMs reserve some header words in an array.
* Attempts to allocate larger arrays may result in
* OutOfMemoryError: Requested array size exceeds VM limit
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;


private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

if (newCapacity - MAX_ARRAY_SIZE > 0)
    newCapacity = hugeCapacity(minCapacity);


private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}

这段代码的作用是确保在需要扩展 ArrayList 的容量时,不会超过预定义的最大数组大小 MAX_ARRAY_SIZE。如果需要分配更大的容量,则会使用 hugeCapacity() 方法计算一个巨大的容量值,以满足需求。这样可以避免分配过大的内存而导致异常或性能问题。

复制

完成以上判断是否需要扩容的操作,现在需要将旧数组的数据复制到新扩容的数组中。

    @SuppressWarnings("unchecked")
    public static <T> T[] copyOf(T[] original, int newLength) {
        return (T[]) copyOf(original, newLength, original.getClass());
    }

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

  /**
    * 复制数组
    * @param src 源数组
    * @param srcPos 源数组中的起始位置
    * @param dest 目标数组
    * @param destPos 目标数组中的起始位置
    * @param length 要复制的数组元素的数量
    */
public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);

Java native关键字用于表示一个方法是由外部的本地代码(通常是由其他边城语言如C/C++编写的)实现的。它常用于与底层系统或硬件进行交互、调用操作系统特定的功能或访问本地库等情况。native 方法的声明只包含方法名和参数列表,没有方法体。它告诉编译器该方法的实现不是在 Java 代码中,而是在外部的本地代码中。

最终ArrayList的数组复制功能通过调用C/C++实现。

以上,便完成了ArrayList复制与扩容功能,再此,留下一个思考题:新增元素,ArrayList和LinkedList那个性能更高?

搜索

    /**
     * Returns the element at the specified position in this list.
     *
     * @param  index index of the element to return
     * @return the element at the specified position in this list
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }
/**
 * Checks if the given index is in range.  If not, throws an appropriate
 * runtime exception.  This method does *not* check if the index is
 * negative: It is always used immediately prior to an array access,
 * which throws an ArrayIndexOutOfBoundsException if index is negative.
 */
private void rangeCheck(int index) {
if (index >= size)
    throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
   @SuppressWarnings("unchecked")
    E elementData(int index) {
        return (E) elementData[index];
    }

ArrayList的get方法首先校验入参Index在本ArrayList是否数组越界,如果没有数组越界,使用数组的方法定位到特定位置的元素,所以ArrayList的get方法时间复杂度是O(1)。

LinkedList

LinkedList的身世

数据结构

  • AbstractSequentialList: 该类提供了基于链表结构的有序访问操作。
  • Deque: Double Ended Queue,双端队列,支持在队列两端插入和删除操作。
  • List: 表明ArrayList符合List接口的规范。
  • Cloneable: 表明它具有拷贝能力,可以进行深拷贝或浅拷贝操作。
  • Serializable: 使一个类可以进行序列化,即将对象转换为字节序列以便存储或传输,并在需要时将其反序列化为对象。

构造方法

    /**
     * Constructs an empty list.
     */
    public LinkedList() {
    }

    /**
     * Constructs a list containing the elements of the specified
     * collection, in the order they are returned by the collection's
     * iterator.
     *
     * @param  c the collection whose elements are to be placed into this list
     * @throws NullPointerException if the specified collection is null
     */
    public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }

LinkedList构造方法很简洁,有参构造方法调用了addAll()方法,方法内通过内部类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;
        }
    }

添加元素

add
transient Node<E> last;


	/**
     * Links e as last element.
     */
    void linkLast(E e) {
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }

LinkedList会记录当前列表的最后一个元素,将最后一个元素赋值,用于创建新的节点,通过内部类Node构造方法创建一个新的节点,当新的节点创建成功后,将最新的节点赋值给当前最后一个元素。当LinkedList还未添加元素时,新插入的元素即为第一个元素,如果当前LinkedList不是第一次添加元素,那么建立最后一个元素和新插入元素的连接关系。而后当前LinkedList的size自增,modCount自增。

addAll
    public boolean addAll(Collection<? extends E> c) {
        return addAll(size, c);
    }

LinkedList的addAll(int index, Collection<? extends E> c)方法有两个地方调用:

  • LinkedList构造方法
  • 公共方法addAll
public boolean addAll(int index, Collection<? extends E> c) {
        checkPositionIndex(index);

        Object[] a = c.toArray();
        int numNew = a.length;
        if (numNew == 0)
            return false;

        Node<E> pred, succ;
        if (index == size) {
            succ = null;
            pred = last;
        } else {
            succ = node(index);
            pred = succ.prev;
        }

        for (Object o : a) {
            @SuppressWarnings("unchecked") E e = (E) o;
            Node<E> newNode = new Node<>(pred, e, null);
            if (pred == null)
                first = newNode;
            else
                pred.next = newNode;
            pred = newNode;
        }

        if (succ == null) {
            last = pred;
        } else {
            pred.next = succ;
            succ.prev = pred;
        }

        size += numNew;
        modCount++;
        return true;
    }
    private void checkPositionIndex(int index) {
        if (!isPositionIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
    /**
     * Tells if the argument is the index of a valid position for an
     * iterator or an add operation.
     */
    private boolean isPositionIndex(int index) {
        return index >= 0 && index <= size;
    }

在执行正式逻辑前,会先校验插入的集合列表是否存在数组越界问题。

将传入的collection对象转化为数组,判断数组的长度,以确定需要构造多少个Node节点,创建两个Node节点对象:

  • pred:当前索引位置的前一个节点(predecessor),用于在插入新节点时连接前一个节点和新节点之间的关系。
  • succ:当前索引位置的节点本身(successor)用于在插入新节点时连接新节点和当前节点之间的关系。

随后判断index和size是否相等,当新初始化一个LinkedList时,index和size都是0,那么这个判断的意义就是:给当前LinkedList的last元素赋值。

当不相等时,根据index的值定位到当前元素的Node节点信息,给当前节点维护上一个节点的关系。

随后是遍历数组信息,创建节点,维护节点间上一个、下一个关系。

    /**
     * Returns the (non-null) Node at the specified element index.
     */
    Node<E> node(int index) {
        // assert isElementIndex(index);

        if (index < (size >> 1)) {
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }

这个是LinkedList内部类Node的根据当前index查询对应元素的方法,首先判断当前要查询的index在列表中前半段还是后半段(通过index < (size >> 1)判断实现),位于前半段时,正序循环查询,位于后半段时,逆序循环查询,时间复杂度是O(n)。

获取元素

    /**
     * Returns the element at the specified position in this list.
     *
     * @param index index of the element to return
     * @return the element at the specified position in this list
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }
    private void checkElementIndex(int index) {
        if (!isElementIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
    /**
     * Tells if the argument is the index of an existing element.
     */
    private boolean isElementIndex(int index) {
        return index >= 0 && index < size;
    }

LinkedList的get方法很简洁,首先判断index在LinkedList是否合法(数组越界),随后调用Node的node(index)即可,因此LinkedList的get方法时间复杂度同样是O(n)。

明明N/2,为什么还是O(n)?

当n->∞时,∞/2依然是∞,因此时间复杂度是O(n)。

测试

talk is cheap,show me your code.

public class StringSub {
    public static void main(String[] args) {
        int numElements = 1000000; // 要插入的元素数量

        // 测试 ArrayList 插入性能
        List<Integer> arrayList = new ArrayList<>();
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < numElements; i++) {
            arrayList.add(i);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("ArrayList 插入 " + numElements + " 个元素耗时:" + (endTime - startTime) + " 毫秒");

        // 测试 LinkedList 插入性能
        List<Integer> linkedList = new LinkedList<>();
        startTime = System.currentTimeMillis();
        for (int i = 0; i < numElements; i++) {
            linkedList.add(i);
        }
        endTime = System.currentTimeMillis();
        System.out.println("LinkedList 插入 " + numElements + " 个元素耗时:" + (endTime - startTime) + " 毫秒");

        // 测试 ArrayList 查询性能
        startTime = System.currentTimeMillis();
        for (int i = 0; i < numElements; i++) {
            int element = arrayList.get(i);
        }
        endTime = System.currentTimeMillis();
        System.out.println("ArrayList 查询 " + numElements + " 个元素耗时:" + (endTime - startTime) + " 毫秒");

        // 测试 LinkedList 查询性能
        startTime = System.currentTimeMillis();
        for (int i = 0; i < numElements; i++) {
            int element = linkedList.get(i);
        }
        endTime = System.currentTimeMillis();
        System.out.println("LinkedList 查询 " + numElements + " 个元素耗时:" + (endTime - startTime) + " 毫秒");
    }
}
基于JDK 8的测试结果

基于JDK 17的测试结果
内存测试

使用JProfiler测试,对比两者创建100万个对象内存差异:

可以看到,LinkedList创建100万个对象,使用内存39997KB,ArrayList创建100万个对象,使用内存20859KB。为什么LinkedList需要比ArrayList近一半的内存?

打个断点,我们看一下:

再回到LinkedList的结构图,我们可以看到,每个LinkedList元素所在的节点,都有三部分组成,一个node节点存储上一个节点信息,一个node节点存储下一个节点信息,只有还一个item存储当前信息,因此,同样一个数据存在LinkedList中占用的内存要比ArrayList更大。

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

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

相关文章

力扣刷题(C++)知识点

一&#xff0c;找到数组的中间位置 这个是错的&#xff0c;不能分开来 C vector<int>& nums 用法 创建一维数组vector&#xff1a; vector<int> nums; //不指定长度vector<int> nums(n); //指定长度为n c &#xff1c;numeric&#xff1e; accumul…

VM327:38 Uncaught ReferenceError: boay is not defined

找了好久的错误&#xff0c;查找的时候都是路径错了&#xff0c;或者少符号了&#xff0c;&#xff0c;&#xff0c;&#xff0c;&#xff0c;但是就是不是&#xff0c;这个错误就很明显&#xff0c;但是人一般对自己的代码真的很自信的&#xff01;&#xff01;&#xff01; 最…

自动化安装系统—PXE(一)

系统安装过程 加载boot loader加载启动安装菜单加载内核和initrd文件加载根系统运行anaconda的安装向导 安装光盘中与安装相关的文件 安装autofs启动后会自动出现/misc目录。 在虚拟机设置中添加CD/DVD&#xff0c;使用系统ISO文件&#xff0c;登录系统后mount /dev/cdrom …

解决lldb调试时可能出现的personality set failed: Function not implemented

最近在尝试使用Visual Studio 2022远程连接Linux进行C/C的开发&#xff0c;由于CentOS风波不断&#xff0c;所以现在的开发基本上都是使用ubuntu了&#xff0c;但是目前VS2022有一些BUG&#xff0c;就是远程调试时&#xff0c;如果目标系统是ubuntu则会出现启动调试器很慢的问题…

【Linux】线程同步和死锁

目录 死锁 什么是死锁 构成死锁的四个必要条件 如何避免死锁 线程同步 同步的引入 同步的方式 条件变量 条件变量的使用 整体代码 死锁 什么是死锁 死锁是指在一组进程中的各个进程均占有不会释放的资源&#xff0c;但因互相申请被其他进程所占用不会释放 的资源而处…

【EI复现】一种建筑集成光储系统规划运行综合优化方法(Matlab代码实现)

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

大模型如何可信?字节跳动研究的最新《可信赖的大型语言模型》综述,提出评估 LLMs 可信度时需要考虑的七大维度

文章目录 一、前言二、主要内容三、总结 &#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ 一、前言 论文地址&#xff1a;Trustworthy LLMs: a Survey and Guideline for Evaluating Large Language Models’ Alignment 在将大型语言模型&#xff08;…

Markdown编辑器的使用

这里写自定义目录标题 欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants 创建一个自定义列表如何创建一个…

电路综合原理与实践---T衰减与PI衰减的详细计算理论与设计仿真

电路综合原理与实践—T衰减与PI衰减的详细计算理论与设计仿真 最近要找工作在刷笔试题目&#xff0c;会刷到关于T衰减的理论计算问题&#xff0c;一直搞不明白怎么算的&#xff0c;搞明白之后给大家伙来分享一下。 基础理论可以参考&#xff1a;电阻衰减网络计算&#xff08;P…

点燃性能火箭!揭秘内联函数的魔法 ✨

目录 前言&#xff1a;探索函数调用的微观世界 —— 从调用到跳转 &#x1f680; 函数调用的微观世界 &#x1f31f; 深入理解栈、堆以及堆栈帧&#x1f511; 栈&#xff08;Stack&#xff09;&#xff1a; 堆&#xff08;Heap&#xff09;&#xff1a; 堆栈帧&#xff08…

4.0 Spring Boot入门

1. Spring Boot概述 Spring Boot介绍 Spring Boot是Pivotal团队在2014年推出的全新框架&#xff0c;主要用于简化Spring项目的开发过程&#xff0c;可以使用最少的配置快速创建Spring项目。 Spring Boot版本 2014年4月v1.0.0.RELEASE发布。 ​ 2.Spring Boot特性 约定优于配…

LED为何通过电流控制?

前段时间&#xff0c;散热部的同事咨询我关于手机的闪光灯输出电压值&#xff0c;说实话&#xff0c;一时间把我问住了。关于闪光灯&#xff0c;以往我们关注电流值&#xff0c;电压值很少关注。虽说手机的闪光灯驱动IC输出为BOOST电路&#xff0c;但是输出电压到多少&#xff…

SolidUI社区-元数据文档

背景 随着文本生成图像的语言模型兴起&#xff0c;SolidUI想帮人们快速构建可视化工具&#xff0c;可视化内容包括2D,3D,3D场景&#xff0c;从而快速构三维数据演示场景。SolidUI 是一个创新的项目&#xff0c;旨在将自然语言处理&#xff08;NLP&#xff09;与计算机图形学相…

20-最难的问题

题目 NowCoder生活在充满危险和阴谋的年代。为了生存&#xff0c;他首次发明了密码&#xff0c;用于军队的消息传递。假设你是军团中的一名军官&#xff0c;需要把发送来的消息破译出来、并提供给你的将军。 消息加密的办法是&#xff1a;对消息原文中的每个字母&#xff0c;分…

【AI】p54-p58导航网络、蓝图和AI树实现AI随机移动和跟随移动、靠近玩家挥拳、AI跟随样条线移动思路

p54-p58导航网络、蓝图和AI树实现AI随机移动和跟随移动、靠近玩家挥拳、AI跟随样条线移动思路 p54导航网格p55蓝图实现AI随机移动和跟随移动AI Move To&#xff08;AI进行移动&#xff09;Get Random Pointln Navigable Radius&#xff08;获取可导航半径内的随机点&#xff09…

爬虫练手项目——获取龙族小说全文

网站信息 目标网站信息如下&#xff1a;包含了龙族1-5全部内容 代码 import requests from bs4 import BeautifulSoup import os import timeheaders {User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Sa…

全球劳动力革命,Papaya Global 打破薪资界限

员工需求和劳动力结构的进一步变化&#xff0c;只会增加对更加自动化和全面的全球薪资解决方案的需求。 远程工作潮流与全球劳动力的蓬勃发展&#xff0c;使得企业在全球范围内&#xff0c;寻找最优秀的人才成为可能。然而&#xff0c;随之而来的复杂薪资管理挑战&#xff0c;也…

C++ QT(二)

目录 Qt 控件按钮QPushButton控件简介用法示例运行效果 QToolButton控件简介用法示例运行效果 QRadioButton控件简介用法示例运行效果 QCheckBox控件简介用法示例运行效果 QCommandLinkButton控件简介用法示例运行效果 QDialogButtonBox控件简介用法示例运行效果 输入窗口部件Q…

HCIP的BGP小综合实验

一、实验要求 1.R2-7每台路由器均存在一个环回接口用于建立邻居&#xff1b; 同时还存在一个环回来代表连接用户的接口&#xff1b; 最终这些连接用户的接口网络需要可以和R1/8的环回通讯。 2.AS2网段地址172.16.0.0/16&#xff0c;减少路由条目。 二、实验过程 2.1 配置IP以…

【JVM】CPU飙高排查方案与思路

文章目录 CPU飙高排查方案与思路 CPU飙高排查方案与思路 1.使用top命令查看占用cpu的情况 2.通过top命令查看后&#xff0c;可以查看是哪一个进程占用cpu较高&#xff0c;上图所示的进程为&#xff1a;40940 3.查看进程中的线程信息 4.可以根据进程 id 找到有问题的线程&a…