前言

JDK源码解析系列文章,都是基于JDK8分析的,虽然JDK15马上要出来了,但是JDK8我还不会,我...

类图

  • 实现了RandomAccess接口,可以随机访问
  • 实现了Cloneable接口,可以克隆
  • 实现了Serializable接口,可以序列化、反序列化
  • 实现了List接口,是List的实现类之一
  • 实现了Collection接口,是Java Collections Framework成员之一
  • 实现了Iterable接口,可以使用for-each迭代

属性

// 序列化版本UID
private 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 > 0,就创建一个新的长度是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<String> 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<String> 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> T[] copyOf(T[] original, int newLength) {
// original.getClass() 是 class [Ljava.lang.Object
return (T[]) copyOf(original, newLength, original.getClass());
}

copyOf()的具体实现:

public static <T,U> 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> T requireNonNull(T obj) {
if (obj == null)
throw new NullPointerException();
return obj;
} @Override
public Object[] toArray() {
// 返回 a 的克隆对象
return a.clone();
} }

这是Arrays.asList()方法源码

public static <T> List<T> 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<ArrayList> 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 < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
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);
// 将引用置空,让GC回收
elementData[--size] = null;
}

查找方法

查找指定元素的所在位置

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

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

创建子数组

public List<E> 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<E> parent;
private final int parentOffset;
private final int offset;
int size; SubList(AbstractList<E> 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<E> iterator() {
return new Itr();
}

Itr属性

// 下一个要返回的元素的下标
int cursor;
// 最后一个要返回元素的下标 没有元素返回 -1
int lastRet = -1;
// 期望的 modCount
int 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<Integer> list) {
Iterator<Integer> 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修饰,可以简化内部类的访问

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

欢迎关注个人微信公众号【如逆水行舟】,用心输出基础、算法、源码系列文章。

深入学习JDK源码系列之、ArrayList的更多相关文章

  1. JDK源码系列总索引

    一 目标 记录学习jdk源码的一些笔记和心得,jdk版本使用11.0.1,工具idea Class后面序号为优先级1-4,优先级递减 目录转载自博客: https://blog.csdn.net/qq ...

  2. Java源码系列1——ArrayList

    本文简单介绍了 ArrayList,并对扩容,添加,删除操作的源代码做分析.能力有限,欢迎指正. ArrayList是什么? ArrayList 就是数组列表,主要用来装载数据.底层实现是数组 Obj ...

  3. 手牵手,从零学习Vue源码 系列一(前言-目录篇)

    系列文章: 手牵手,从零学习Vue源码 系列一(前言-目录篇) 手牵手,从零学习Vue源码 系列二(变化侦测篇) 手牵手,从零学习Vue源码 系列三(虚拟DOM篇) 陆续更新中... 预计八月中旬更新 ...

  4. 手牵手,从零学习Vue源码 系列二(变化侦测篇)

    系列文章: 手牵手,从零学习Vue源码 系列一(前言-目录篇) 手牵手,从零学习Vue源码 系列二(变化侦测篇) 陆续更新中... 预计八月中旬更新完毕. 1 概述 Vue最大的特点之一就是数据驱动视 ...

  5. 【jdk源码分析】ArrayList的size()==0和isEmpty()

    先看结果 分析源码 [jdk源码解析]jdk8的ArrayList初始化长度为0 java的基本数据类型默认值 无参构造 size()方法 isEmpty()方法

  6. JDK源码分析(一)——ArrayList

    目录 ArrayList分析 ArrayList继承结构 ArrayList字段属性 ArrayList构造函数 重要方法 ArrayList Iterator迭代器 总结 ArrayList分析   ...

  7. 学习JDK源码(一):String

    用了好久的Java了,从来没有看过jdk的源码,趁着今天有点时间,拿出了jdk的源码看了下,今天先看了关于String的,毕竟开发中String类型使用最广泛.在我们下载安装jdk的时候,部分源码也已 ...

  8. jdk源码阅读笔记-ArrayList

    一.ArrayList概述 首先我们来说一下ArrayList是什么?它解决了什么问题?ArrayList其实是一个数组,但是有区别于一般的数组,它是一个可以动态改变大小的动态数组.ArrayList ...

  9. JDK源码解析(一)ArrayList源码解析

    这里为了方便写注释,我是把ArrayList的源码复制下来放到自己创建的类里面的 这个变量则指向具体存放数据的数组 看下构造函数吧 点进去看下LinkedList是怎么数组化的 很弱智吧,就是创建一个 ...

随机推荐

  1. JVM 专题十二:运行时数据区(七)对象的实例化内存布局与访问定位

    1. 对象的实例化 1.1 创建对象的方式 new 最常见的方式 变形1 : Xxx的静态方法 变形2 : XxBuilder/XxoxFactory的静态方法 Class的newInstance() ...

  2. MYSQL 之 JDBC(十四):批量处理JDBC语句提高处理效率

    1.当需要成批插入或者更新记录时.可以采用java的批量更新机制,这一机制允许多条语句一次性提交给数据库批量处理.通常情况下比单独提交处理更有效率. 2.JDBC的批量处理语句包括下面两个方法: ad ...

  3. 数据分析07 /matplotlib绘图

    数据分析07 /matplotlib绘图 目录 数据分析07 /matplotlib绘图 1. 绘制线性图:plt.plot() 2. 绘制柱状图:plt.bar() 3. 绘制直方图:plt.his ...

  4. CRM开发系列

    CRM,客户关系管理系统(Customer Relationship Management).企业用CRM技术来管理与客户之间的关系,以求提升企业成功的管理方式,其目的是协助企业管理销售循环:新客户的 ...

  5. matlab中的静态变量与全局变量

    matlab中的静态变量和全局变量 1.静态变量 在matlab中,和其他语言一样,函数中的变量一把都是局部变量,也就是说,在函数调用完毕后,变量就会被释放.但是有些时候回希望上次改变的变量在下一次调 ...

  6. 静态代理,动态代理和CGLIB代理模式

    代理模式 一.概述 代理是一种模式,提供了对目标对象的间接访问方式,即通过代理访问目标对象.如此便于在目标实现的基础上增加额外的功能操作,前拦截,后拦截等,以满足自身的业务需求,同时代理模式便于扩展目 ...

  7. java 之 实例方法和类方法

    类方法:使用static修饰(静态方法),属于整个类的,不是属于某个实例的,只能处理static域或调用static方法: 实例方法:属于对象的方法,由对象来调用. 判断类方法,类方法的前面有stat ...

  8. CUDA Programming Guide 学习笔记

    CUDA学习笔记 GPU架构 GPU围绕流式多处理器(SM)的可扩展阵列搭建,每个GPU有多个SM,每个SM支持数百个线程并发执行.目前Nvidia推出了6种GPU架构(按时间顺序,详见下图):Fer ...

  9. Spring事务源码分析专题(一)JdbcTemplate使用及源码分析

    Spring中的数据访问,JdbcTemplate使用及源码分析 前言 本系列文章为事务专栏分析文章,整个事务分析专题将按下面这张图完成 对源码分析前,我希望先介绍一下Spring中数据访问的相关内容 ...

  10. u盘安装 osx 出现 “不能验证”

    关于安装是出现关于出现“不能验证”错误: 解决办法 :打开终端 在"终端"里面修改时间 输入:date 032208102015.20