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

说到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快约13%,为什么LinkedList需要比ArrayList近一半的内存?

打个断点,我们看一下:

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

  • 头部插入/删除:只需要修改头结点的指针即可完成插入/删除操作,因此时间复杂度为 O(1)。
  • 尾部插入/删除:只需要修改尾结点的指针即可完成插入/删除操作,因此时间复杂度为 O(1)。
  • 指定位置插入/删除:需要先移动到指定位置,再修改指定节点的指针完成插入/删除,因此需要移动平均 n/2 个元素,时间复杂度为 O(n)。

所以,LinkedList适合增删、ArrayList适合查询业务场景是不适用的。不建议在实际项目中使用LinkedList。

来自作者的一段话:

LinkedList 的作者约书亚 · 布洛克(Josh Bloch)自己都说从来不会使用 LinkedList 。

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

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

相关文章

QCustomPlot的X轴是时间轴的曲线绘制

主要设置X轴的参数 SharedPointer<QCPAxisTickerTime> timeTicker(new QCPAxisTickerTime); timeTicker->setTimeFormat("%h:%m:%s");demo如下 Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);dataTimer …

java-集合

1. 接口继承关系和实现 集合类存放于 Java.util 包中&#xff0c;主要有 3 种&#xff1a;set(集&#xff09;、list(列表包含 Queue&#xff09;和 map(映射)。 Collection&#xff1a;Collection 是集合 List、Set、Queue 的最基本的接口。Iterator&#xff1a;迭代器&…

C语言小练习(一)

&#x1f31e; “人生是用来体验的&#xff0c;不是用来绎示完美的&#xff0c;接受迟钝和平庸&#xff0c;允许出错&#xff0c;允许自己偶尔断电&#xff0c;带着遗憾&#xff0c;拼命绽放&#xff0c;这是与自己达成和解的唯一办法。放下焦虑&#xff0c;和不完美的自己和解…

Hyperledger Fabric的使用及开发

Hyperledger Fabric是Linux基金会发起的一种跨行业的区块链技术&#xff0c;目前在多家大型公司有着应用&#xff0c;这里就不多做HF本身的介绍了&#xff0c;有兴趣可关注其官网。 1. 准备工作&#xff1a; 开始前需要一定的准备工作&#xff0c;安装各类中间件&#xff1a;…

MySQL8.0.26-Linux版安装

MySQL8.0.26-Linux版安装 1. 准备一台Linux服务器 云服务器或者虚拟机都可以; Linux的版本为 CentOS7; 2. 下载Linux版MySQL安装包 MySQL :: Download MySQL Community Server (Archived Versions) 3. 上传MySQL安装包 4. 创建目录,并解压 mkdir mysql ​ tar -xvf mysql-8…

无涯教程-TensorFlow - 分布式计算

本章将重点介绍如何开始使用分布式TensorFlow&#xff0c;目的是帮助开发人员了解重复出现的基本分布式TF概念&#xff0c;如TF服务器。无涯教程将使用Jupyter Notebook分布式TensorFlow。 第1步 - 导入分布式计算必需的必要模块- import tensorflow as tf 第2步 - …

OpenCV基础知识(6)— 滤波器

前言&#xff1a;Hello大家好&#xff0c;我是小哥谈。在尽量保留原图像信息的情况下&#xff0c;去除图像内噪声、降低细节层次信息等一系列过程&#xff0c;被叫做图像的平滑处理&#xff08;或者叫图像的模糊处理&#xff09;。实现平滑处理最常用的工具就是滤波器。通过调节…

[国产MCU]-W801开发实例-开发环境搭建

W801开发环境搭建 文章目录 W801开发环境搭建1、W801芯片介绍2、W801芯片特性3、W801芯片结构4、开发环境搭建1、W801芯片介绍 W801芯片是联盛德微电子推出的一款高性价比物联网芯片。 W801 芯片是一款安全 IoT Wi-Fi/蓝牙 双模 SoC芯片。芯片提供丰富的数字功能接口。支持2.…

麻辣烫数据可视化,麻辣烫市场将持续蓬勃发展

麻辣烫&#xff0c;这道源自中国的美食&#xff0c;早已成为人们生活中不可或缺的一部分。它独特的香辣口味&#xff0c;让人忍不住每每流连忘返。与人们的关系&#xff0c;简直如同挚友一般。每当寒冷的冬日或疲惫的时刻&#xff0c;麻辣烫总是悄然走进人们的心房&#xff0c;…

计算机毕设项目之基于django+mysql的疫情实时监控大屏系统(前后全分离)

系统阐述的是一款新冠肺炎疫情实时监控系统的设计与实现&#xff0c;对于Python、B/S结构、MySql进行了较为深入的学习与应用。主要针对系统的设计&#xff0c;描述&#xff0c;实现和分析与测试方面来表明开发的过程。开发中使用了 django框架和MySql数据库技术搭建系统的整体…

n5173b是德科技keysight N5173B信号发生器

产品概述 是德科技/安捷伦N5173B EXG模拟信号发生器 当您需要平衡预算和性能时&#xff0c;是德科技N5173B EXG微波模拟信号发生器是经济高效的选择。它提供解决宽带滤波器、放大器、接收机等参数测试的基本信号。执行基本LO上变频或CW阻塞&#xff0c;低成本覆盖13、20、31.…

Servlet 初步学习

文章目录 Servlet1 简介2 快速入门3 执行流程4 生命周期5 方法介绍6 体系结构7 urlPattern配置8 XML配置 Servlet 1 简介 Servlet是JavaWeb最为核心的内容&#xff0c;它是Java提供的一门 动态 web资源开发技术。 使用Servlet就可以实现&#xff0c;根据不同的登录用户在页面…

windows权限维持—黄金白银票据隐藏用户远控RustDeskGotoHttp

windows权限维持—黄金白银票据&隐藏用户&远控&RustDesk&GotoHttp 1. 前置1.1. 初始问题1.1.1. 解决办法 2. 隐藏用户2.1. 工具原理2.2. 案例操作2.2.1. 单机添加用户2.2.1.1. 工具添加用户2.2.1.2. 工具查看隐藏用户2.2.1.3. 本地查看隐藏用户 2.2.2. 域内添加…

玩机搞机----面具模块的组成 制作模块

root面具相信很多玩家都不陌生。早期玩友大都使用第三方卡刷补丁来对系统进行各种修复和添加功能。目前面具补丁代替了这些操作。今天的帖子了解下面具各种模块的组成和几种普遍的代码组成。 Magisk中运行的每个单独的shell脚本都将在内部的BusyBox的shell中执行。对于与第三方…

代码随想录算法训练营day39 | 62. 不同路径,63. 不同路径 II

目录 62. 不同路径 63. 不同路径 II 62. 不同路径 类型&#xff1a;动态规划 难度&#xff1a;medium 思路&#xff1a; 应用二维数组的动态规划&#xff0c;到达某个方格的方法数目&#xff0c;为这个方格的上一个方格和左一个方格的方法数目和。 需要先初始化第一行和第一…

关于查看处理端口号和进程[linux]

查看端口号 lsof -i:端口号如果-bash: lsof: 未找到命令那我们可以执行yum install lsof 删除端口号进程 一般我们都会使用kill命令 kill -l#列出所有可用信号1 (HUP)&#xff1a;重新加载进程。9 (KILL)&#xff1a;杀死一个进程。15 (TERM)&#xff1a;正常停止一个进程。 …

PyTorch Lightning:通过分布式训练扩展深度学习工作流

一、介绍 欢迎来到我们关于 PyTorch Lightning 系列的第二篇文章&#xff01;在上一篇文章中&#xff0c;我们向您介绍了 PyTorch Lightning&#xff0c;并探讨了它在简化深度学习模型开发方面的主要功能和优势。我们了解了 PyTorch Lightning 如何为组织和构建 PyTorch 代码提…

详解junit

目录 1.概述 2.断言 3.常用注解 3.1.Test 3.2.Before 3.3.After 3.4.BeforeClass 3.5.AfterClass 4.异常测试 5.超时测试 6.参数化测试 1.概述 什么是单元测试&#xff1a; 单元测试&#xff0c;是针对最小的功能单元编写测试代码&#xff0c;在JAVA中最小的功能单…

openpose姿态估计【学习笔记】

文章目录 1、人体需要检测的关键点2、Top-down方法3、Openpose3.1 姿态估计的步骤3.2 PAF&#xff08;Part Affinity Fields&#xff09;部分亲和场3.3 制作PAF标签3.4 PAF权值计算3.5 匹配方法 4、CPM&#xff08;Convolutional Pose Machines&#xff09;模型5、Openpose5.1 …

博客系统之功能测试

博客系统共有&#xff1a;用户登录功能、发布博客功能、查看文章详情功能、查看文章列表功能、删除文章功能、退出功能 1.登录功能&#xff1a; 1.1测试对象&#xff1a;用户登录 1.2测试用例 方法&#xff1a;判定表 用例 编号 操作步骤预期结果实际结果截图1 1.用户名正确…