第二章 ArrayList源码分析

一、对于ArrayList需要掌握的内容

  • ArrayList的创建:即构造器
  • 往ArrayList中添加对象:即add(E)方法
  • 获取ArrayList中的单个对象:即get(int index)方法
  • 删除ArrayList中的对象:即remove(E)方法
  • 遍历ArrayList中的对象:即iterator,在实际中更常用的是增强型的for循环去做遍历
  • 判断对象是否存在于ArrayList中:contain(E)

基本介绍

  • 底层使用数组,同时是动态数组
  • 由于数组是 Object 类型的,因此可以添加元素 null,可以添加相同元素
  • ArrayList 中的操作是线程不安全的
  • 实现了 RandomAccess 接口,即可以使用随机访问,通过索引访问效率更高

1.创建

//默认数组初始容量
private static final int DEFAULT_CAPACITY = 10;
//空数组
private static final Object[] EMPTY_ELEMENTDATA = {};
 /**
  * 共享空数组实例,用于默认大小的空实例。
  * 我们将其与EMPTY_ELEMENTDATA区分开来,
  * 以了解添加第一个元素时应该膨胀多少。
  */
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//负责存储元素的数组引用,不参与序列化
transient Object[] elementData;
//线性表长度,即已添加的元素数量,默认是0个元素
private int size;
//指定数组容量的构造方法
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);
        }
    }

//默认的构造方法,直接将0个元素的默认容量的数组对象赋值给elementData
 public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }


//接受一个Collection为参数的构造方法
public ArrayList(Collection<? extends E> c) {
        //将集合转化为数组并赋值给elementData,数组的长度就是集合的size
        elementData = c.toArray();
        //将数组长度赋值给size,并判断是否为0
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            //如果size不为0,判断elementData的Class对象是否是Object[]的Class对象
            if (elementData.getClass() != Object[].class)
                //不是则使用 Arrays.copyOf 创建一个新的数组并赋值给elementData
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // 如果size为0,则直接创建一个空数组对象
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

2.扩容

//确认数组容量是否能满足实际元素数量
public void ensureCapacity(int minCapacity) { 
        //如果elementData不是用的默认空数组对象
        int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) 
            // any size if not default element table   
            ? 0                 //如果为true,默认传入0
            // larger than default for default empty table. It's already
            // supposed to be at default size.
            : DEFAULT_CAPACITY; //false的话,使用默认的容量,即存储10个元素
        //如果最少需要的元素容量 大于 当前数组的容量
        if (minCapacity > minExpand) { 
             //把最少需要的容量传去,去做数组扩容
            ensureExplicitCapacity(minCapacity);
        }
    }

    private void ensureCapacityInternal(int minCapacity) {
        //如果elementData指向的是默认的空数组对象
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { 
            //最小容量与默认容量的最大值赋值给minCapacity
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); 
        }
        //把最少需要的容量传过来,在ensureExplicitCapacity中扩容
        ensureExplicitCapacity(minCapacity); 
    }

    private void ensureExplicitCapacity(int minCapacity) {
        //首先修改次数+1
        modCount++; 
        // overflow-conscious code
        //如果需要的最小容量 减去 实际的数组容量 大于 0(即实际元素数量大于当前的数组容量)
        if (minCapacity - elementData.length > 0) 
            // 将需要的最小容量传进grow方法中做扩容
            grow(minCapacity); 
    }

    //数组的最大容量,最大长度,用的Integer的最大值 - 8
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; 
    //这是扩容数组的逻辑实现
    private void grow(int minCapacity) { 
        // overflow-conscious code
        //首先拿到当前数组的长度 ,记录到oldCapacity中
        int oldCapacity = elementData.length; 
        //新的newCapacity 是 旧的数组长度 + 旧的数组长度/2,即扩展1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
         //如果新的容量,还没有实际需要的容量大
        if (newCapacity - minCapacity < 0)
            //那么新的数组容量,就直接用实际元素需要的容量
            newCapacity = minCapacity; 
        //如果新的数组容量大于上限容量
        if (newCapacity - MAX_ARRAY_SIZE > 0) 
            //调用hugeCapacity方法,拿到一个新的容量
            newCapacity = hugeCapacity(minCapacity); 
        // minCapacity is usually close to size, so this is a win:
        //将原来的数组,按照新的容量长度,生成一个新的数组对象,并且元素逐个copy到新数组中
        elementData = Arrays.copyOf(elementData, newCapacity); 
    } 

    private static int hugeCapacity(int minCapacity) {
        //要是需要的最小容量小于0,直接抛出OutOfMemoryError(),内存溢出
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        //如果需要的最小容量已经超过上限,仍然把上限作为返回值
        return (minCapacity > MAX_ARRAY_SIZE) ? 
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

3.增加

在表的尾部添加元素

public boolean add(E e) {
        //检查是否需要扩容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

在指定位置添加元素

public void add(int index, E element) {
        //检查下标是否合法
        rangeCheckForAdd(index);
        //检查是否需要扩容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //将当前index处及以后的所有元素,均向后移动一位
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        //将新的元素插入index位置
        elementData[index] = element;
        size++;
    }

 //检查下标是否合法
 private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

添加一个集合

public boolean addAll(Collection<? extends E> c) {
        //先把Collection转换为数组,长度就是Collection的实际元素的数量
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }

//从指定位置添加一个集合
public boolean addAll(int index, Collection<? extends E> c) {
        rangeCheckForAdd(index);

        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount
        //计算原数组需要移动的元素数量
        int numMoved = size - index;
        //将原数组所有要移动的元素,往后移动到index+numNew开始处
        if (numMoved > 0)
            System.arraycopy(elementData, index, elementData, index + numNew,
                             numMoved);
        //将传入List中的元素添加到elementData,从index下标开始,一共添加numNew个元素
        System.arraycopy(a, 0, elementData, index, numNew);
        size += numNew;
        return numNew != 0;
    }

4.删除

删除指定下标的元素

public E remove(int index) {
        //又是先检查下标是否合法
        rangeCheck(index);
        //修改次数+1
        modCount++;
        //拿到要移除的元素对象
        E oldValue = elementData(index);
        //计算要挪动的元素数量,index之前的元素不用移动,之后的元素都得移动
        int numMoved = size - index - 1;
        //如果移动元素的数量大于0,开始执行,向前移动元素,逐个的赋值
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        //移动完元素之后,还要把原来空出来的元素位置,赋值null,释放内存
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }

删除指定元素

public boolean remove(Object o) {
        if (o == null) {
            //遍历数组找到第一个为null的元素并删除
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            //遍历数组找到第一个equals(o)的元素并删除
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }

//私有方法,根据下标,移除掉元素,和直接移除指定下标位置的元素相同
private void fastRemove(int index) {
        modCount++;
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work
    }

删除所有元素(清空)

public void clear() {
        modCount++;
        //遍历数组并置空,内存回收
        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;
    }

删除一个区间的元素

//删除一个区间的元素,fromIndex是开始下标,toIndex是结束下标 
protected void removeRange(int fromIndex, int toIndex) { 
        //先把修改次数+1
        modCount++; 
        //计算要移动元素的数量
        int numMoved = size - toIndex; 
        //将结束下标处的元素,向开始下标处移动,移动元素的数量是numMoved,显然toIndex下标的元素是 
        //不移除的
        System.arraycopy(elementData, toIndex, elementData, fromIndex,
                         numMoved); 

        // clear to let GC do its work
        //计算新的线性表长度
        int newSize = size - (toIndex-fromIndex); 
        for (int i = newSize; i < size; i++) { //遍历移除元素后,剩下的空间
            elementData[i] = null;// 将其均置为null
        }
        size = newSize;//线性表长度改为新的元素数量
    } //该方法是protected,所以只有继承ArrayList时,才能访问

5.修改

public E set(int index, E element) {
        rangeCheck(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

6.获取

public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }

7.遍历iterator()

 public Iterator<E> iterator() {
        return new Itr();//返回一个内部类对象
    }

 private class Itr implements Iterator<E> {
        //标记位:标记遍历到哪一个元素
        int cursor = 0;
        //标记位:用于判断是否在遍历的过程中,是否发生了add、remove操作
        int expectedModCount = modCount;

        //检测对象数组是否还有元素
        public boolean hasNext() {
            //如果cursor==size,说明已经遍历完了,上一次遍历的是最后一个元素
            return cursor != size();
        }

        //获取元素
        public E next() {
            checkForComodification();//检测在遍历的过程中,是否发生了add、remove操作
            try {
                E next = get(cursor++);
                return next;
            } catch (IndexOutOfBoundsException e) {//捕获get(cursor++)方法的IndexOutOfBoundsException
                checkForComodification();
                throw new NoSuchElementException();
            }
        }

        //检测在遍历的过程中,是否发生了add、remove等操作
        final void checkForComodification() {
            //发生了add、remove操作,这个我们可以查看add等的源代码,发现会出现modCount++
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

iterator()方法是在AbstractList中实现的,该方法返回AbstractList的一个内部类Itr对象

8.trimToSize()

//将剩余数组空间压缩成实际元素占用的空间
public void trimToSize() {
        modCount++;
        if (size < elementData.length) {
            elementData = (size == 0)
              ? EMPTY_ELEMENTDATA
              : Arrays.copyOf(elementData, size);
        }
    }

三、总结

  • ArrayList基于数组方式实现,无容量的限制(会扩容)
  • 添加元素时可能要扩容(所以最好预判一下),删除元素时不会减少容量(若希望减少容量,trimToSize()),删除元素时,将删除掉的位置元素置为null,下次gc就会回收这些元素所占的内存空间。
  • 线程不安全
  • add(int index, E element):添加元素到数组中指定位置的时候,需要将该位置及其后边所有的元素都整块向后复制一位
  • get(int index):获取指定位置上的元素时,可以通过索引直接获取(O(1))
  • remove(Object o)需要遍历数组
  • remove(int index)不需要遍历数组,只需判断index是否符合条件即可,效率比remove(Object o)高
  • contains(E)需要遍历数组

参考文章:叫我王员外就行 ArrayList源码分析(JDK1.8)

                 赵计刚 第二章 ArrayList源码解析

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值