参考文档: https://cloud.tencent.com/developer/article/1145014

https://segmentfault.com/a/1190000018578944

http://www.importnew.com/9928.html

https://blog.csdn.net/zero__007/article/details/52166306

1. ArrayList简介

  ArrayList底层基于数组实现的一种线性数据结构,通过数组的索引原理实现了快速查找,是非线程安全的。

  由于数组创建时必须制定容量而且不可更改,ArrayList通过自动扩容的方式弥补了数组容量不可更改的弊端,但同时也带了性能方面的隐患。

2.ArrayList继承关系

  ArrayList继承自AbstractList,实现了List、RandomAccess、Cloneable、java.io.Serializable接口。  

  实现了所有List接口的操作,并ArrayList允许存储null值。除了没有进行同步,ArrayList基本等同于Vector。在Vector中几乎对所有的方法都进行了同步,但ArrayList仅对writeObject和readObject进行了同步,其它比如add(Object)、remove(int)等都没有同步。

  1. AbstractList提供了List接口的默认实现(个别方法为抽象方法)。
  2. List接口定义了列表必须实现的方法。
  3. 实现了RandomAccess接口:提供了随机访问功能。RandmoAccess是java中用来被List实现,为List提供快速访问功能的。在ArrayList中,我们即可以通过元素的序号快速获取元素对象;这就是快速随机访问。
  4. 实现了Cloneable接口:可以调用Object.clone方法返回该对象的浅拷贝。
  5. 实现了 java.io.Serializable 接口:可以启用其序列化功能,能通过序列化去传输。未实现此接口的类将无法使其任何状态序列化或反序列化。序列化接口没有方法或字段,仅用于标识可序列化的语义。

3. ArrayList实现

1. 核心属性    

 transient Object[] elementData;
private int size;

  elementData是ArrayList中用来存储数据的底层数组,size代表数组中存储的元素个数。

  有个关键字需要解释:transient。Java的serialization提供了一种持久化对象实例的机制。当持久化对象时,可能有一个特殊的对象数据成员,我们不想用serialization机制来保存它。为了在一个特定对象的一个域上关闭serialization,可以在这个域前加上关键字transient。

  ArrayList在序列化的时候会调用writeObject,直接将size和element写入ObjectOutputStream;反序列化时调用readObject,从ObjectInputStream获取size和element,再恢复到elementData。

  为什么不直接用elementData来序列化,而采用上诉的方式来实现序列化呢?原因在于elementData是一个缓存数组,它通常会预留一些容量,等容量不足时再扩充容量,那么有些空间可能就没有实际存储元素,采用上诉的方式来实现序列化时,就可以保证只序列化实际存储的那些元素,而不是整个数组,从而节省空间和时间。

2. 构造函数

private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
} 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);
}
} public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}

  ArrayList有三个构造函数,一个无参构造,一个指定容量的有参构造,一个指定集合的有参构造。

  无参构造会创建一个的列表,内部数组容量为0,当调用add方法添加元素时会扩容成默认容量10(为何网上都说是构造一个默认初始容量为10的空列表???)。

  指定容量的有参构造会创建一个内部数组为指定容量大小的空列表。

  指定集合的有参构造会创建一个包含指定collection的元素的列表,这些元素按照该collection的迭代器返回它们的顺序排列的。

3. 存储元素

  当对ArrayList进行元素添加的时候,都会检查底层数组的容量是否足够,若是不够则进行自动扩容,每次对数组进行增删改的时候都会增加modCount(继承自AbstractList的属性,用来统计修改次数),添加单个元素时一般会扩容1.5倍[oldCapacity + (oldCapacity >> 1)],添加集合时,如果(原数组的长度 + 添加的集合长度 > 原数组的长度的1.5倍)则会扩容至(原数组的长度 + 添加的集合长度)

  存储元素分为三种类型:追加,插入,替换,其中第二种和第三种类型都会检查下标是否越界(根据size属性而不是数组的长度)

  (1)追加:这种方式最常用,直接添加到数组中最后一个元素的后面。

  public boolean add(E paramE)
{
ensureCapacityInternal(this.size + 1);
this.elementData[(this.size++)] = paramE;
return true;
} public boolean addAll(Collection<? extends E> paramCollection)
{
Object[] arrayOfObject = paramCollection.toArray();
int i = arrayOfObject.length;
ensureCapacityInternal(this.size + i);
System.arraycopy(arrayOfObject, 0, this.elementData, this.size, i);
this.size += i;
return i != 0;
}

  (2)插入:当调用下面这两个方法向数组中添加元素或集合时,会先查找索引位置,然后将元素添加到索引处,最后把添加前索引后面的元素追加到新元素的后面。

   public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
} 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;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved); System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}

  (3)替换:调用该方法会将index位置的元素用新元素替代。

     public void set(E e) {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification(); try {
ArrayList.this.set(lastRet, e);
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}

4. 元素读取

       public E get(int index) {
rangeCheck(index);
checkForComodification();
return ArrayList.this.elementData(offset + index);
}

5. 元素删除

  ArrayList提供了5种方式的删除功能。如下:

  (1)romove(int index),首先是检查范围,修改modCount,保留将要被移除的元素,将移除位置之后的元素向前挪动一个位置,将list末尾元素置空(null),返回被移除的元素。

    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);
elementData[--size] = null; // clear to let GC do its work return oldValue;
}

  (2)remove(Object o),这里为了防止equals方法空指针异常,对remove对象为空的情况做了特殊处理,然后遍历底层数组找到与remove对象相同的元素调用fastRemove后返回true,fastRemove的逻辑和romove(int index)的逻辑一致,只是少了范围检查以及没有返回值。

    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);
elementData[--size] = null; // clear to let GC do its work
}

  (3)removeRange(int fromIndex, int toIndex),修改modCount,把toIndex后面的元素通过System.arraycopy复制到toIndex位置后面,计算移除后的数组大小newSize,数组中下标大于等于newSize的元素全部置为空,方便垃圾回收,重置size属性。

    protected void removeRange(int fromIndex, int toIndex) {
modCount++;
int numMoved = size - 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;
}
size = newSize;
}

  (4)removeAll(Collection<?> c),首先检查参数,为空则抛出异常,然后调用batchRemove(c, false),在batchRemove中,先把底层数组元素赋给一个final修饰的局部变量elementData,然后遍历elementData,把elementData中除了c包含的元素从下标为0开始依次存入elementData进行重排序,最后通过(r != size)判断try块中是否出现过异常,出现过异常则把elementData中未遍历过的元素全部复制到下标为w后面,通过(w != size)判断原数组是否已经改变,如果已改变则修改modCOunt,将下标为w后面的元素全部置为空,重置size属性,把modified属性设为true代表移除成功并返回。

    public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, false);
} private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
int r = 0, w = 0;
boolean modified = false;
try {
for (; r < size; r++)
if (c.contains(elementData[r]) == complement)
elementData[w++] = elementData[r];
} finally {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
if (r != size) {
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
if (w != size) {
// clear to let GC do its work
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}

  (5)removeIf(Predicate<? super E> filter),在JDK1.8中,Collection以及其子类新加入了removeIf方法,作用是通过lambda表达式移除集合中符合条件的元素。如移除List<Integer>中所有大于10的元素: list.removeIf(Integer -> Integer > 10);

    @Override
public boolean removeIf(Predicate<? super E> filter) {
Objects.requireNonNull(filter);
// figure out which elements are to be removed
// any exception thrown from the filter predicate at this stage
// will leave the collection unmodified
int removeCount = 0;
final BitSet removeSet = new BitSet(size);
final int expectedModCount = modCount;
final int size = this.size;
for (int i=0; modCount == expectedModCount && i < size; i++) {
@SuppressWarnings("unchecked")
final E element = (E) elementData[i];
if (filter.test(element)) {
removeSet.set(i);
removeCount++;
}
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
} // shift surviving elements left over the spaces left by removed elements
final boolean anyToRemove = removeCount > 0;
if (anyToRemove) {
final int newSize = size - removeCount;
for (int i=0, j=0; (i < size) && (j < newSize); i++, j++) {
i = removeSet.nextClearBit(i);
elementData[j] = elementData[i];
}
for (int k=newSize; k < size; k++) {
elementData[k] = null; // Let gc do its work
}
this.size = newSize;
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
modCount++;
} return anyToRemove;
}

6. 调整数组容量

  (1)ensureCapacity(int minCapacity),首先自定义一个变量,如果内部数组elementData为空则赋值为0,否则赋值为10,如果数组需要的容量大于自定义变量的值,则调用ensureExplicitCapacity(minCapacity)。ensureExplicitCapacity方法会先修改modCount,然后当需要的容量大于elementData的长度时调用grow(minCapacity),grow方法是最终进行扩容算法的方法。grow方法内先定义了两个变量oldCapacity(当前数组长度)和newCapacity(扩容后的数组长度),newCapacity的值是oldCapacity的1.5倍(oldCapacity >> 1 左移1位相当于除以2),如果需要扩容的容量大于newCapacity,则把newCapacity赋值为minCapacity(也就是说ArrayList扩容不一定每次扩容都是1.5倍,ArrayList内部默认扩容1.5倍,但由于ensureCapacity是一个public方法,我们可以外部手动调用,当需要向ArrayList中添加大量元素时,我们可以提前根据需要添加的元素数量调用ensureCapacity,根据需要添加的元素数量提前调用ensureCapacity来进行手动扩容,避免递增式自动扩容反复调用Arrays.copyOf带来的性能损耗,提高程序效率),然后就是判断需要扩容的容量是否大于int的最大值,如果超过最大值则抛出内存溢出异常,如果大于(int最大值 - 8),小于等于int最大值,则把newCapacity设为int最大值,最后也是最核心的一步,通过Arrays.copyOf进行数组的扩容。

    public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
// any size if not default element table
? 0
// larger than default for default empty table. It's already
// supposed to be at default size.
: DEFAULT_CAPACITY; if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
} private void ensureExplicitCapacity(int minCapacity) {
modCount++; // overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
} 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);
} private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}

  (2)trimToSize(),这个方法很简单,就是把底层数组的长度调整为元素实际个数大小,这个方法主要是为了防止ArrayList进行扩容时产生的空间浪费。由于elementData的长度会被拓展,size标记的是其中包含的元素的个数。所以会出现size很小但elementData.length很大的情况,将出现空间的浪费。一般只有当确定了ArrayList的元素不再增加时进行调用。

    public void trimToSize() {
modCount++;
int oldCapacity = elementData.length;
if (size < oldCapacity) {
elementData = Arrays.copyOf(elementData, size);
}
}

7. 转为静态数组的两种方法

  (1)直接将底层数组拷贝一份大小为size的新数组并返回

    public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}

  (2)如果传入数组的长度小于size,返回一个新的数组,大小为size,类型与传入数组相同。所传入数组长度与size相等,则将elementData复制到传入数组中并返回传入的数组。若传入数组长度大于size,除了复制elementData外,还将把返回数组的第size个元素置为空。这里需要注意的是参数中的T类型要与ArrayList中存储的数据类型一致,否则会出现ArrayStoreException。

    // 返回ArrayList的模板数组。所谓模板数组,即可以将T设为任意的数据类型
public <T> T[] toArray(T[] a) {
// 若数组a的大小 < ArrayList的元素个数;
// 则新建一个T[]数组,数组大小是“ArrayList的元素个数”,并将“ArrayList”全部拷贝到新数组中
if (a.length < size)
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
// 若数组a的大小 >= ArrayList的元素个数;
// 则将ArrayList的全部元素都拷贝到数组a中。
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}

8.实现了Cloneable接口,进行数据浅拷贝

  如果ArrayList中存的是引用类型数据,则把拷贝后的ArrayList中的对象的属性修改后,源ArrayList中对应的对象的属性也会改变,当然移除和添加元素没有影响,若是基本数据类型则没有这问题。

    // 克隆函数
public Object clone() {
try {
ArrayList<E> v = (ArrayList<E>) super.clone();
// 将当前ArrayList的全部元素拷贝到v中
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError();
}
}

9.实现Serializable 接口,启用其序列化功能

    // java.io.Serializable的写入函数
// 将ArrayList的“容量,所有的元素值”都写入到输出流中
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException {
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
s.defaultWriteObject();
// 写入“数组的容量”
s.writeInt(elementData.length);
// 写入“数组的每一个元素”
for (int i = 0; i < size; i++)
s.writeObject(elementData[i]);
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
} // java.io.Serializable的读取函数:根据写入方式读出
// 先将ArrayList的“容量”读出,然后将“所有的元素值”读出
private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException {
// Read in size, and any hidden stuff
s.defaultReadObject();
// 从输入流中读取ArrayList的“容量”
int arrayLength = s.readInt();
Object[] a = elementData = new Object[arrayLength];
// 从输入流中将“所有的元素值”读出
for (int i = 0; i < size; i++)
a[i] = s.readObject();
}

10. 实现了RandomAccess接口,启用随机访问

  实际上RandomAccess是一个空的接口,它到的作用是做一个标记,用来区分List的实现类是否支持随机访问。事实上不同实现方式的不同遍历方式性能差异较大,如ArrayList使用for循环遍历比用iterator迭代器要快,而LinkedList使用iterator迭代器遍历比for循环要快的多,所以List不同子类需要采用不同遍历方式以提高性能。可是我们如何区分List的实现子类是ArrayList还是LinkedList呢?这时候RandomAccess接口就派上用场了,用instanceof方法来判断该子类是否实现了RandomAccess接口,进而更好的判断集合是否ArrayList或者LinkedList,从而能够更好选择更优的遍历方式,提高性能!

4. ArrayList常见问题

1. ArrayList的默认初始长度是多少?最大长度是多少?

  ArrayList默认长度为10,但新创建的ArrayList容量其实默认为0,首次添加元素时会自动扩容至默认容量10;最大长度为int的最大值(2147483647),而不是ArrayList内部定义的常量MAX_ARRAY_SIZE(Integer.MAX_VALUE - 8)

2. ArrayList是如何扩容的?

  ArrayList底层是通过ensureCapacity方法来进行扩容的(具体扩容实现前面已经说过了),这个方法一般是在向ArrayList中添加元素时首先调用以确保容量足以容纳新增的元素,同时这个方法我们也可以手动调用来进行扩容,一般在知道数据量较大的情况下提前手动扩容以避免频繁扩容带来的性能损耗。大体的扩容思路就是每次添加元素时,检查底层数组容量是否足够,足够则直接添加,若是不够则扩容1.5倍,若是要添加的也是一个集合,则取这个集合的大小与当前ArrayList的大小之和与底层数组长度的1.5倍两者之间的最大值进行扩容。

3. ArrayList扩容后是否会自动缩容?如果不能怎样进行缩容?

  ArrayList只能自动扩容,不能自动缩容。如果需要进行缩容,可以调用ArrayList提供的trimToSize()方法。

4. ArrayList底层数组扩容时是如何保证高效复制数组的?

  表面上是调用Arrays.copyOf()方法,实际上是Arrays.copyOf()通过调用System.arraycopy()方法复制数组的,但貌似并不高效~~。

5. 什么情况下你会使用ArrayList?什么时候你会选择LinkedList?

  这个问题实际上考察的是ArrayList与LinkedList的区别,也就是数组与链表的区别。数组由于索引的存在,查找较快,而链表查找时需要进行遍历,所以当查找操作较多的情况下用ArrayList更合适;如果查找较少,增删较多的情况选择LinkedList则更为合适,因为在ArrayList中增加或者删除某个元素,通常会调用System.arraycopy方法,这是一种极为消耗资源的操作。

6. 当传递ArrayList到某个方法中,或者某个方法返回ArrayList,什么时候要考虑安全隐患?如何修复安全违规这个问题呢?

  当array被当做参数传递到某个方法中,如果array在没有被复制的情况下直接被分配给了成员变量,那么就可能发生这种情况,即当原始的数组被调用的方法改变的时候,传递到这个方法中的数组也会改变。下面的这段代码展示的就是安全违规以及如何修复这个问题。

pulic void setArr(String[] arr){
this.arr = arr;
}

  修复这个安全隐患:

public void setArr(String[] newArr){
if(newArr == null)
this.arr = new String[0];
else
this.arr = Arrays.copyOf(newArr, newArr.length);
}

7. 如何复制某个ArrayList到另一个ArrayList中去?写出你的代码? 

  下面就是把某个ArrayList复制到另一个ArrayList中去的几种技术:

  1. 使用clone()方法,比如ArrayList newArray = oldArray.clone();
  2. 使用ArrayList构造方法,比如:ArrayList myObject = new ArrayList(myTempObject);
  3. 使用Collection的copy方法。

  注意1和2是浅拷贝(shallow copy)。

8. 在索引中ArrayList的增加或者删除某个对象的运行过程?效率很低吗?解释一下为什么?

  在ArrayList中增加或者是删除元素,要调用System.arraycopy这种效率很低的操作

学习JDK1.8集合源码之--ArrayList的更多相关文章

  1. 【JDK1.8】JDK1.8集合源码阅读——ArrayList

    一.前言 在前面几篇,我们已经学习了常见了Map,下面开始阅读实现Collection接口的常见的实现类.在有了之前源码的铺垫之后,我们后面的阅读之路将会变得简单很多,因为很多Collection的结 ...

  2. 学习JDK1.8集合源码之--LinkedHashSet

    1. LinkedHashSet简介 LinkedHashSet继承自HashSet,故拥有HashSet的全部API,LinkedHashSet内部实现简单,核心参数和方法都继承自HashSet,只 ...

  3. 学习JDK1.8集合源码之--HashMap

    1. HashMap简介 HashMap是一种key-value结构存储数据的集合,是map集合的经典哈希实现. HashMap允许存储null键和null值,但null键最多只能有一个(HashSe ...

  4. 学习JDK1.8集合源码之--Vector

    1. Vector简介 Vector是JDK1.0版本就推出的一个类,和ArrayList一样,继承自AbstractList,实现了List.RandomAccess.Cloneable.java. ...

  5. 学习JDK1.8集合源码之--WeakHashMap

    1. WeakHashMap简介 WeakHashMap继承自AbstractMap,实现了Map接口. 和HashMap一样,WeakHashMap也是一种以key-value键值对的形式进行数据的 ...

  6. 学习JDK1.8集合源码之--ArrayDeque

    1. ArrayDeque简介 ArrayDeque是基于数组实现的一种双端队列,既可以当成普通的队列用(先进先出),也可以当成栈来用(后进先出),故ArrayDeque完全可以代替Stack,Arr ...

  7. 学习JDK1.8集合源码之--HashSet

    1. HashSet简介 HashSet是一个不可重复的无序集合,底层由HashMap实现存储,故HashSet是非线程安全的,由于HashSet使用HashMap的Key来存储元素,而HashMap ...

  8. 学习JDK1.8集合源码之--LinkedList

    1. LinkedList简介 LinkedList是一种可以在任何位置进行高效地插入和移除操作的有序序列,它是基于双向链表实现的.因为它实现了Deque接口,所以也是双端队列的一种实现. 2.Lin ...

  9. 学习JDK1.8集合源码之--TreeMap

    1. TreeMap简介 TreeMap继承自AbstractMap,实现了NavigableMap.Cloneable.java.io.Serializable接口.所以TreeMap也是一个key ...

随机推荐

  1. python-web-selenium模拟控制浏览器

    用 selenium 模块控制浏览器 启动 selenium 控制的浏览器 from selenium import webdriver brower = webdriver.Firefox() br ...

  2. 服务器的tomcat调优和jvm调化

    下面讲述的是tomcat的优化,及jvm的优化 Tomcat 的缺省配置是不能稳定长期运行的,也就是不适合生产环境,它会死机,让你不断重新启动,甚至在午夜时分唤醒你.对于操作系统优化来说,是尽可能的增 ...

  3. springBoot 项目 jar/war打包 并运行

    一:idea  打jar  包 简述:springboor  项目最好的打包方式就是打成jar  ,下边就是简单的过程 1.打开idea工具 ,选着要打开的项目, 然后打开view--> too ...

  4. Java基础(spring事物和锁)

    使用步骤: 步骤一.在spring配置文件中引入<tx:>命名空间<beans xmlns="http://www.springframework.org/schema/b ...

  5. [BZOJ2164]采矿【模拟+树链剖分+线段树】

    Online Judge:Bzoj2164 Label:模拟,树链剖分,线段树 题目描述 浩浩荡荡的cg大军发现了一座矿产资源极其丰富的城市,他们打算在这座城市实施新的采矿战略.这个城市可以看成一棵有 ...

  6. Luogu P4782 【模板】2-SAT 问题(2-SAT)

    P4782 [模板]2-SAT 问题 题意 题目背景 \(2-SAT\)问题模板 题目描述 有\(n\)个布尔变量\(x_1\sim x_n\),另有\(m\)个需要满足的条件,每个条件的形式都是&q ...

  7. Leetcode953. Verifying an Alien Dictionary验证外星语词典

    某种外星语也使用英文小写字母,但可能顺序 order 不同.字母表的顺序(order)是一些小写字母的排列. 给定一组用外星语书写的单词 words,以及其字母表的顺序 order,只有当给定的单词在 ...

  8. IDEA2018激活码

    请复制如下内容到文本编辑器(如notepad++)把博客的内容去掉 N757JE0KCT-eyJsaWNlbnNlSWQiOiJONzU3SkUwS0NUIiwibGljZW5zZWVOYW1lIjo ...

  9. python图像翻转

    准备跟着台湾的一个机器学习课程好好学学python,链接在这http://speech.ee.ntu.edu.tw/~tlkagk/courses_ML16.html 该课程开始有一个作业,叫做HW0 ...

  10. Django项目:CRM(客户关系管理系统)--29--21PerfectCRM实现King_admin查看页面美化

    {#table_data_list.html#} {## ————————08PerfectCRM实现King_admin显示注册表的字段表头————————#} {% extends 'king_m ...