点击上方"IT牧场",选择"设为星标"

技术干货每日送达!

前言

JDK源码解析系列文章,都是基于JDK8分析的,虽然JDK14已经出来,但是JDK8我还不会,我…

类图


  • 实现了RandomAccess接口,可以随机访问

  • 实现了Cloneable接口,可以克隆

  • 实现了Serializable接口,可以序列化、反序列化

  • 实现了List接口,是List的实现类之一

  • 实现了Collection接口,是Java Collections Framework成员之一

  • 实现了Iterable接口,可以使用for-each迭代

属性

// 序列化版本UIDprivate static final long        serialVersionUID = 8683452581122892189L;

/** * 默认的初始容量 */private static final int        DEFAULT_CAPACITY = 10;

/** * 用于空实例的共享空数组实例 * new ArrayList(0); */private static final Object[]        EMPTY_ELEMENTDATA = {};

/** * 用于提供默认大小的实例的共享空数组实例 * new ArrayList(); */private static final Object[]        DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

/** * 存储ArrayList元素的数组缓冲区 * ArrayList的容量,是数组的长度 *  * non-private to simplify nested class access */transient Object[] elementData;

/** * ArrayList中元素的数量 */private int size;

小朋友,你四否有很多问号?

  1. 为什么空实例默认数组有的时候是EMPTY_ELEMENTDATA,而又有的时候是DEFAULTCAPACITY_EMPTY_ELEMENTDATA

  2. 为什么elementData要被transient修饰

  3. 为什么elementData没有被private修饰?难道正如注释所写的non-private to simplify nested class access

带着问题,我们继续往下看。

构造方法

带初始容量的构造方法

/** * 带一个初始容量参数的构造方法 * * @param  initialCapacity  初始容量 * @throws  如果初始容量非法就抛出 *          IllegalArgumentException */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);    }}
  • 如果initialCapacity ,就创建一个新的长度是initialCapacity的数组

  • 如果initialCapacity == 0,就使用EMPTY_ELEMENTDATA

  • 其他情况,initialCapacity不合法,抛出异常

无参构造方法

/** * 无参构造方法 将elementData 赋值为 *   DEFAULTCAPACITY_EMPTY_ELEMENTDATA */public ArrayList() {    this.elementData =            DEFAULTCAPACITY_EMPTY_ELEMENTDATA;}

带一个集合参数的构造方法

/** * 带一个集合参数的构造方法 * * @param c 集合,代表集合中的元素会被放到list中 * @throws 如果集合为空,抛出NullPointerException */public ArrayList(Collection extends E> c) {    elementData = c.toArray();    // 如果 size != 0    if ((size = elementData.length) != 0) {        // c.toArray 可能不正确的,不返回 Object[]        // https://bugs.openjdk.java.net/browse/JDK-6260652        if (elementData.getClass() != Object[].class)            elementData = Arrays.copyOf(                    elementData, size, Object[].class);    } else {        // size == 0        // 将EMPTY_ELEMENTDATA 赋值给 elementData        this.elementData = EMPTY_ELEMENTDATA;    }}
  • 使用将集合转换为数组的方法

  • 为了防止c.toArray()方法不正确的执行,导致没有返回Object[],特殊做了处理

  • 如果数组大小等于0,则使用 EMPTY_ELEMENTDATA

那么问题来了,什么情况下c.toArray()会不返回Object[]呢?

public static void main(String[] args) {    List list = new ArrayList<>(Arrays.asList("list"));// class java.util.ArrayList    System.out.println(list.getClass());    Object[] listArray = list.toArray();// class [Ljava.lang.Object;    System.out.println(listArray.getClass());    listArray[0] = new Object();    System.out.println();    List asList = Arrays.asList("asList");// class java.util.Arrays$ArrayList    System.out.println(asList.getClass());    Object[] asListArray = asList.toArray();// class [Ljava.lang.String;    System.out.println(asListArray.getClass());// java.lang.ArrayStoreException    asListArray[0] = new Object();}

我们通过这个例子可以看出来,java.util.ArrayList.toArray()方法会返回Object[]没有问题。而java.util.Arrays的私有内部类ArrayList的toArray()方法可能不返回Object[]

为什么会这样?

我们看ArrayList的toArray()方法源码:

public Object[] toArray() {    // ArrayLisy中 elementData是这样定义的    // transient Object[] elementData;    return Arrays.copyOf(elementData, size);}

使用了Arrays.copyOf()方法:

public static  T[] copyOf(T[] original, int newLength) {// original.getClass() 是 class [Ljava.lang.Objectreturn (T[]) copyOf(original, newLength, original.getClass());}

copyOf()的具体实现:

public static  T[] copyOf(U[] original, int newLength, Class extends T[]> newType) {@SuppressWarnings("unchecked")/**     * 如果newType是Object[] copy 数组 类型就是 Object      * 否则就是 newType 类型     */    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;}

我们知道ArrayList中elementData就是Object[]类型,所以ArrayList的toArray()方法必然会返回Object[]

我们再看一下java.util.Arrays的内部ArrayList源码(截取的部分源码):

private static class ArrayList<E> extends AbstractList<E>implements RandomAccess, java.io.Serializable {

    // 存储元素的数组    private final E[] a;

    ArrayList(E[] array) {        // 直接把接收的数组 赋值 给 a        a = Objects.requireNonNull(array);    }

    /**     * obj 为空抛出异常     * 不为空 返回 obj     */    public static  T requireNonNull(T obj) {if (obj == null)throw new NullPointerException();return obj;    }@Overridepublic Object[] toArray() {// 返回 a 的克隆对象return a.clone();    }}

这是Arrays.asList()方法源码

public static  List asList(T... a) {return new ArrayList<>(a);}

不难看出来java.util.Arrays的内部ArrayList的toArray()方法,是构造方法接收什么类型的数组,就返回什么类型的数组。

所以,在我们上面的例子中,实际上返回的是String类型的数组,再将其中的元素赋值成Object类型的,自然报错。

我们还是继续看ArrayList吧…

插入方法

在列表最后添加指定元素

/** * 在列表最后添加指定元素 * * @param e 要添加的指定元素 * @return true */public boolean add(E e) {    // 增加 modCount !!    ensureCapacityInternal(size + 1);     elementData[size++] = e;    return true;}
  • 在父类AbstractList上,定义了modCount 属性,用于记录数组修改的次数。

在指定位置添加指定元素

/** * 在指定位置添加指定元素 * 如果指定位置已经有元素,就将该元素和随后的元素移动到右面一位 * * @param index 待插入元素的下标 * @param element 待插入的元素 * @throws 可能抛出 IndexOutOfBoundsException */public void add(int index, E element) {    rangeCheckForAdd(index);

    // 增加 modCount !!    ensureCapacityInternal(size + 1);    System.arraycopy(elementData, index, elementData, index + 1,                     size - index);    elementData[index] = element;    size++;}

插入方法调用的其他私有方法

/** * 计算容量 */private static int calculateCapacity(        Object[] elementData, int minCapacity) {

    if (elementData ==            DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {        return Math.max(DEFAULT_CAPACITY, minCapacity);    }    return minCapacity;}

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

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

    // overflow-conscious code    if (minCapacity - elementData.length > 0)        grow(minCapacity);}

扩容方法

/** * 数组可以分配的最大size * 一些虚拟机在数组中预留一些header words * 如果尝试分配更大的size,可能导致OutOfMemoryError */private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

/** * 增加容量,至少保证比minCapacity大 * @param minCapacity 期望的最小容量 */private void grow(int minCapacity) {    // 有可能溢出的代码    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);}

/** * 最大容量返回 Integer.MAX_VALUE */private static int hugeCapacity(int minCapacity) {    if (minCapacity 0) // overflow        throw new OutOfMemoryError();    return (minCapacity > MAX_ARRAY_SIZE) ?        Integer.MAX_VALUE :        MAX_ARRAY_SIZE;}
  • 通常情况新容量是原来容量的1.5倍

  • 如果原容量的1.5倍比minCapacity小,那么就扩容到minCapacity

  • 特殊情况扩容到Integer.MAX_VALUE

看完构造方法、添加方法、扩容方法之后,上文第1个问题终于有了答案。原来,new ArrayList()会将elementData 赋值为 DEFAULTCAPACITY_EMPTY_ELEMENTDATA,new ArrayList(0)会将elementData 赋值为 EMPTY_ELEMENTDATA,EMPTY_ELEMENTDATA添加元素会扩容到容量为1,而DEFAULTCAPACITY_EMPTY_ELEMENTDATA扩容之后容量为10

通过反射我们可以验证这一想法。如下:

public static void main(String[] args) {    printDefaultCapacityList();    printEmptyCapacityList();}

public static void printDefaultCapacityList() {    ArrayList defaultCapacity = new ArrayList();    System.out.println(            "default 初始化长度:" + getCapacity(defaultCapacity));

    defaultCapacity.add(1);    System.out.println(            "default add 之后 长度:" + getCapacity(defaultCapacity));}

public static void printEmptyCapacityList() {    ArrayList emptyCapacity = new ArrayList(0);    System.out.println(            "empty 初始化长度:" + getCapacity(emptyCapacity));

    emptyCapacity.add(1);    System.out.println(            "empty add 之后 长度:" + getCapacity(emptyCapacity));}

public static int getCapacity(ArrayList> arrayList) {    Class arrayListClass = ArrayList.class;try {// 获取 elementData 字段        Field field = arrayListClass.getDeclaredField("elementData");// 开启访问权限        field.setAccessible(true);// 把示例传入get,获取实例字段elementData的值        Object[] objects = (Object[]) field.get(arrayList);//返回当前ArrayList实例的容量值return objects.length;    } catch (Exception e) {        e.printStackTrace();return -1;    }}

移除方法

移除指定下标元素方法

/** * 移除列表中指定下标位置的元素 * 将所有的后续元素,向左移动 * * @param 要移除的指定下标 * @return 返回被移除的元素 * @throws 下标越界会抛出IndexOutOfBoundsException */public E remove(int index) {    rangeCheck(index);

    modCount++;    E oldValue = elementData(index);

    int numMoved = size - index - 1;    if (numMoved > 0)            System.arraycopy(elementData,                     index+1, elementData, index,  numMoved);    // 将引用置空,让GC回收    elementData[--size] = null;

    return oldValue;}

移除指定元素方法

/** * 移除第一个在列表中出现的指定元素 * 如果存在,移除返回true * 否则,返回false * * @param o 指定元素 */public boolean remove(Object o) {    if (o == null) {        for (int index = 0; index             if (elementData[index] == null) {                fastRemove(index);                return true;            }    } else {        for (int index = 0; 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);    // 将引用置空,让GC回收    elementData[--size] = null;}

查找方法

查找指定元素的所在位置

/** * 返回指定元素第一次出现的下标 * 如果不存在该元素,返回 -1 * 如果 o ==null 会特殊处理 */public int indexOf(Object o) {    if (o == null) {        for (int i = 0; i             if (elementData[i]==null)                return i;    } else {        for (int i = 0; i             if (o.equals(elementData[i]))                return i;    }    return -1;}

查找指定位置的元素

/** * 返回指定位置的元素 * * @param  index 指定元素的位置  * @throws index越界会抛出IndexOutOfBoundsException */public E get(int index) {    rangeCheck(index);

    return elementData(index);}

该方法直接返回elementData数组指定下标的元素,效率还是很高的。所以ArrayList,for循环遍历效率也是很高的。

序列化方法

/** * 将ArrayLisy实例的状态保存到一个流里面 */private void writeObject(java.io.ObjectOutputStream s)throws java.io.IOException{    // Write out element count, and any hidden stuff    int expectedModCount = modCount;    s.defaultWriteObject();

    // Write out size as capacity for behavioural compatibility with clone()    s.writeInt(size);

    // 按照顺序写入所有的元素    for (int i=0; i        s.writeObject(elementData[i]);    }

    if (modCount != expectedModCount) {        throw new ConcurrentModificationException();    }}

反序列化方法

/** * 根据一个流(参数)重新生成一个ArrayList */private void readObject(java.io.ObjectInputStream s)throws java.io.IOException, ClassNotFoundException {    elementData = EMPTY_ELEMENTDATA;

    // Read in size, and any hidden stuff    s.defaultReadObject();

    // Read in capacity    s.readInt();

    if (size > 0) {        // be like clone(), allocate array based upon size not capacity        ensureCapacityInternal(size);

        Object[] a = elementData;        // Read in all elements in the proper order.        for (int i=0; i            a[i] = s.readObject();        }    }}

看完序列化,反序列化方法,我们终于又能回答开篇的第二个问题了。elementData之所以用transient修饰,是因为JDK不想将整个elementData都序列化或者反序列化,而只是将size和实际存储的元素序列化或反序列化,从而节省空间和时间。

创建子数组

public List subList(int fromIndex, int toIndex) {    subListRangeCheck(fromIndex, toIndex, size);    return new SubList(this, 0, fromIndex, toIndex);}

我们看一下简短版的SubList

private class SubList extends AbstractList<E> implements RandomAccess {    private final AbstractList parent;private final int parentOffset;private final int offset;int size;    SubList(AbstractList parent,int offset, int fromIndex, int toIndex) {this.parent = parent;this.parentOffset = fromIndex;this.offset = offset + fromIndex;this.size = toIndex - fromIndex;this.modCount = ArrayList.this.modCount;    }public E set(int index, E e) {        rangeCheck(index);        checkForComodification();        E oldValue = ArrayList.this.elementData(offset + index);        ArrayList.this.elementData[offset + index] = e;return oldValue;    }// 省略代码...}
  • SubList的set()方法,是直接修改ArrayListelementData数组的,使用中应该注意

  • SubList是没有实现Serializable接口的,是不能序列化的

迭代器

创建迭代器方法

public Iterator iterator() {    return new Itr();}

Itr属性

// 下一个要返回的元素的下标int cursor;// 最后一个要返回元素的下标 没有元素返回 -1int lastRet = -1;// 期望的 modCountint expectedModCount = modCount;

Itr的hasNext() 方法

public boolean hasNext() {    return cursor != size;}

Itr的next()方法

public E next() {    checkForComodification();    int i = cursor;    if (i >= size)        throw new NoSuchElementException();    Object[] elementData = ArrayList.this.elementData;    if (i >= elementData.length)        throw new ConcurrentModificationException();    cursor = i + 1;    return (E) elementData[lastRet = i];}

final void checkForComodification() {    if (modCount != expectedModCount)        throw new ConcurrentModificationException();}

在迭代的时候,会校验modCount是否等于expectedModCount,不等于就会抛出著名的ConcurrentModificationException异常。什么时候会抛出ConcurrentModificationException

public static void main(String[] args) {    ArrayList arrayList = new ArrayList();    for (int i = 0; i 10; i++) {        arrayList.add(i);    }    remove(arrayList);    System.out.println(arrayList);}

public static void remove(ArrayList list) {    Iterator iterator = list.iterator();while (iterator.hasNext()) {        Integer number = iterator.next();if (number % 2 == 0) {// 抛出ConcurrentModificationException异常            list.remove(number);        }    }}

那怎么写才能不抛出ConcurrentModificationException?很简单,将list.remove(number);换成iterator.remove();即可。why?请看Itr的remove()源码…

Itr的remove()方法

public void remove() {    if (lastRet 0)        throw new IllegalStateException();    checkForComodification();

    try {        ArrayList.this.remove(lastRet);        cursor = lastRet;        lastRet = -1;        // 移除之后将modCount 重新赋值给 expectedModCount        expectedModCount = modCount;    } catch (IndexOutOfBoundsException ex) {        throw new ConcurrentModificationException();    }}

原因就是因为Itr的remove()方法,移除之后将modCount重新赋值给 expectedModCount。这就是源码,不管单线程还是多线程,只要违反了规则,就会抛异常。

源码看的差不多了,开篇的问题却还剩一个!到底为什么elementData没有用private修饰呢?

我们知道的,private修饰的变量,内部类也是可以访问到的。难道注释中non-private to simplify nested class access的这句话有毛病?

当我们看表面看不到什么东西的时候,不妨看一下底层。

测试类代码:


一顿javacjavap之后(使用JDK8):


再一顿javacjavap之后(使用JDK11):


虽然字节码指令我还看不太懂,但是我能品出来,注释是没毛病的,private修饰的确会影响内部类的访问。

ArrayList类注释翻译

类注释还是要看的,能给我们一个整体的了解这个类。我将ArrayList的类注释大概翻译整理了一下:

  • ArrayList是实现List接口的可自动扩容的数组。实现了所有的List操作,允许所有的元素,包括null值。

  • ArrayList大致和Vector相同,除了ArrayList是非同步的。

  • size isEmpty get set iteratorlistIterator 方法时间复杂度是O(1),常量时间。其他方法是O(n),线性时间。

  • 每一个ArrayList实例都有一个capacity(容量)。capacity是用于存储列表中元素的数组的大小。capacity至少和列表的大小一样大。

  • 如果多个线程同时访问ArrayList的实例,并且至少一个线程会修改,必须在外部保证ArrayList的同步。修改包括添加删除扩容等操作,仅仅设置值不包括。这种场景可以用其他的一些封装好的同步的list。如果不存在这样的Object,ArrayList应该用Collections.synchronizedList包装起来最好在创建的时候就包装起来,来保证同步访问。

  • iterator()listIterator(int)方法是fail-fast的,如果在迭代器创建之后,列表进行结构化修改,迭代器会抛出ConcurrentModificationException

  • 面对并发修改,迭代器快速失败、清理,而不是在未知的时间不确定的情况下冒险。请注意,快速失败行为不能被保证。通常来讲,不能同步进行的并发修改几乎不可能做任何保证。因此,写依赖这个异常的程序的代码是错误的,快速失败行为应该仅仅用于防止bug

总结

  • ArrayList底层的数据结构是数组

  • ArrayList可以自动扩容,不传初始容量或者初始容量是0,都会初始化一个空数组,但是如果添加元素,会自动进行扩容,所以,创建ArrayList的时候,给初始容量是必要的

  • Arrays.asList()方法返回的是的Arrays内部的ArrayList,用的时候需要注意

  • subList()返回内部类,不能序列化,和ArrayList共用同一个数组

  • 迭代删除要用,迭代器的remove方法,或者可以用倒序的for循环

  • ArrayList重写了序列化、反序列化方法,避免序列化、反序列化全部数组,浪费时间和空间

  • elementData不使用private修饰,可以简化内部类的访问

源码系列第一篇,一不小心就写的有点长。但是懵懂到深刻的过程还是挺耐人寻味的。文章中没有展开的点,或者你有什么其他好奇的地方,欢迎留言讨论。我们下篇文章再见…

干货分享

最近将个人学习笔记整理成册,使用PDF分享。关注我,回复如下代码,即可获得百度盘地址,无套路领取!

•001:《Java并发与高并发解决方案》学习笔记;•002:《深入JVM内核——原理、诊断与优化》学习笔记;•003:《Java面试宝典》•004:《Docker开源书》•005:《Kubernetes开源书》•006:《DDD速成(领域驱动设计速成)》•007:全部•008:加技术讨论群

近期热文

•彻底解决 GitHub 拉取代码网速慢的问题•基于 SpringBoot2 和 Netty 实现一个简易的RPC通信框架•一本彻底搞懂MySQL索引优化EXPLAIN百科全书•盘点 10 个代码重构的小技巧•性能测试如何定位瓶颈?偶发超时?看高手如何快速排查问题•震精!Spring Boot内存泄露,排查竟这么难!


想知道更多?长按/扫码关注我吧↓↓↓>>>技术讨论群<<<喜欢就点个"在看"呗^_^

arraylist能否接收强转类型_ArrayList 源码解析相关推荐

  1. java arraylist 赋值_ArrayList源码解析,老哥,来一起复习一哈?

    点击上方"码农沉思录",选择"设为星标" 优质文章,及时送达 前言 JDK源码解析系列文章,都是基于JDK8分析的,虽然JDK14已经出来,但是JDK8我还不会 ...

  2. .net core 源码解析-mvc route的注册,激活,调用流程(三)

    .net core mvc route的注册,激活,调用流程 mvc的入口是route,当前请求的url匹配到合适的route之后,mvc根据route所指定的controller和action激活c ...

  3. 面试必备:ArrayList源码解析(JDK8)

    概述 很久没有写博客了,准确的说17年以来写博客的频率降低到一个不忍直视的水平.这个真不怪我,给大家解释一下.  一是自从做了leader,整天各种事,开会,过需求,无限循环.心很累,时间也被无线压榨 ...

  4. 增加数组下标_数组以及ArrayList源码解析

    点击上方"码之初"关注,···选择"设为星标" 与精品技术文章不期而遇 前言 前一篇我们对数据结构有了个整体的概念上的了解,没看过的小伙伴们可以看我的上篇文章: ...

  5. Java 集合系列03之 ArrayList详细介绍(源码解析)和使用示例

    转载自  Java 集合系列03之 ArrayList详细介绍(源码解析)和使用示例 第1部分 ArrayList介绍 ArrayList简介 ArrayList 是一个数组队列,相当于 动态数组.与 ...

  6. 面试官系统精讲Java源码及大厂真题 - 05 ArrayList 源码解析和设计思路

    05 ArrayList 源码解析和设计思路 耐心和恒心总会得到报酬的. --爱因斯坦 引导语 ArrayList 我们几乎每天都会使用到,但真正面试的时候,发现还是有不少人对源码细节说不清楚,给面试 ...

  7. 深入Java集合ArrayList的源码解析

    现在由大恶人付有杰来从增删改查几个角度轻度解析ArrayList的源码 首先ArrayList的底层数据结构非常简单,就是一个数组. 从源码第115行我们可以得出信息,他的默认数组长度是10. /** ...

  8. ArrayList源码解析与相关知识点

    ArrayList源码解析于相关知识点(超级详细) 文章目录 ArrayList源码解析于相关知识点(超级详细) ArrayList的继承关系 Serializable标记接口 Cloneable标记 ...

  9. 安卓开发者必看:Android的数据结构与算法——ArrayList源码解析

    作者:JerryloveEmily 原文链接:https://www.jianshu.com/p/159426e2aaf6 文章有点长,比较啰嗦,请耐心看完! 一.概述 首先得明白ArrayList在 ...

最新文章

  1. 最近在招标中评审出一个政府信息化软件
  2. PowerShell2.0之维护网络(三)设置网络适配器
  3. C++中关键字的理解--Static
  4. 唯品会2017年双11大促技术保障实践,全域提供25万QPS服务能力
  5. bzoj2662:[BeiJing wc2012]冻结
  6. 自适应高度表格_React实现自适应高度的lt;textareagt;
  7. 读书:《从零到百亿-Facebook创业故事》
  8. 量化交易策略matlab交易方案,【策略分享】Matlab量化交易策略源码分享
  9. iOS 系统爆 Bug!
  10. 亚太杯数学建模竞赛优秀论文_【优秀论文】2019数维杯国际大学生数学建模竞赛C题优秀论文...
  11. 实例化反射类class的集中方法
  12. android下md5加密
  13. IOS改地区:美国、新西兰等等
  14. 你印象中的程序员是什么样子的?
  15. 模型数据处理之数据格式转换——SuperMap iDesktop
  16. MFC中picture控件如何响应鼠标移动?
  17. 009 向量:定义、内积、相关性与线性表示及性质
  18. 机器学习算法基础之使用python代码
  19. 【例16 Java从键盘读入学生成绩,找出最高分,并输出学生成绩等级】
  20. linux脱离终端运行程序

热门文章

  1. 条件控制与条件传送详解
  2. Qt图形界面编程入门(创建一个简单的程序)
  3. python3.0下载用什么浏览器_无法让Python下载网页源代码:“不支持浏览器版本”...
  4. 6-搭建一个私有registry
  5. 基于百度语音识别API的Python语音识别小程序
  6. 超简单的mysql多实例布置
  7. MySQL数据库学习笔记
  8. linux命令之which
  9. I00037 亏数(Deficient number)
  10. 黑色30s高并发IIS设置