历史文章:

Collection 源码分析

AbstractList 源码分析

介绍

ArrayList是一个数组队列,相当于动态数组,与Java的数组对比,他的容量可以动态改变。

继承关系

  • ArrayList继承AbstractList
  • 实现了List,RandomAccess,Cloneable,Serializable接口

特点

  • 基于数组实现速度快
  • 实现了RandomAccess接口,提供了随机访问功能
  • 实现了Cloneable接口,能被克隆
  • 实现了Serializable接口,支持序列化传输
  • 非线程安全(ps:线程安全类:CopyOnWriteArrayList)
  • 适用于频繁查询和获取数据
  • 查询效率在众多List中效率还是非常不错

构造函数以及常用的方法

构造函数

public ArrayList();//无元素默认为0,有元素初始化默认容量为10
public ArrayList(Collection<? extends E> c);//默认为c这个集合的list(浅拷贝)
public ArrayList(int initialCapacity);//设置一个初始化为initialCapacity集合

注意点

    public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
//解决bug问题,由于其c.toArray()可能出现返回值不为Object[]的错误,所以采用如下方法
if (elementData.getClass() != Object[].class)
//使用数组拷贝来进行
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}

在这个方法中出现if (elementData.getClass() != Object[].class)这样一组判断,查阅资料发现,这是一个bug才这么判断的地址,改问题已经在JDK9已经进行了修复了。

成员变量

    private static final long serialVersionUID = 8683452581122892189L;

    /**
* 默认初始化容量10
*/
private static final int DEFAULT_CAPACITY = 10; /**
* 共享的空数据容器
*/
private static final Object[] EMPTY_ELEMENTDATA = {}; /**
* 共享空数据容器
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; /**
* 缓存数据集合
*/
transient Object[] elementData; // non-private to simplify nested class access /**
* 容器数据集大小
*
* @serial
*/
private int size;
/**
* 该容器能够承受的最大容量
* 为什么是Integer.MAX_VALUE - 8;
* 因为有些VM虚拟机会在一个数组中存储一些头部信息,所以采用这个值
* 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;

值得注意的点:

  1. MAX_ARRAY_SIZE设置为Integer.MAX_VALUE - 8是因为有些VM虚拟机会在数组中存储一些头部信息,从而占用一些空间,所以-8

常用方法

值得注意的方法

trimToSize():

缩小容器大小让容器释放多余的空间,会触发一次数组的变化

    /**
* 缩小容器大小
* 例如当你一开始创建了一个100个的List但是你只使用了10个,想将这个容器缩减为10个
*/
public void trimToSize() {
//这个参数和我们并发控制的时候version一个味道,用于判断是否并发修改的标志1
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}

clone():

返回数据的浅克隆的实例

    /**
* 返回数据的浅克隆的实例
*
* @return a clone of this <tt>ArrayList</tt> instance
*/
public Object clone() {
try {
//调用父类的克隆方法->Object的克隆方法
ArrayList<?> v = (ArrayList<?>) super.clone();
//拷贝数组,注意是直接通过Arrays的copyOf所以为浅克隆
v.elementData = Arrays.copyOf(elementData, size);
//设置并发version
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
//克隆异常则抛出error
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}

toArray():

同样为浅拷贝,拷贝出来的数组还是会被改变的,该方法返回的为Object[]数组

  /**
* 同样为浅拷贝,拷贝出来的数组还是会被改变的
* 将List转换成数组
* Demo[] cloneArr = (Demo[]) demos.toArray(); //ERROR
*
* @return an array containing all of the elements in this list in
* proper sequence
*/
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}

add方法

    /**
* 追加一个元素在列表的最后面
*
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
//这里会导致其并发版本+1,
// 因为需要先确认容器大小操作,并确定是否需要扩容。
//对数据有修改,因而其并发版本也就会+1
ensureCapacityInternal(size + 1); // Increments modCount!!
//设置值
elementData[size++] = e;
return true;
}
/**
* 在index位置后插入元素,并移动后面元素的位置
* 1. 需要对index后面的所有的元素index+1,需要拷贝工作产生
* 2. 如果你的List中有大量的这样的插入工作建议采用
* @see LinkedList
*
* @param index index at which the specified element is to be inserted
* @param element element to be inserted
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public void add(int index, E element) {
//校验index
rangeCheckForAdd(index);
//确定扩容权限
ensureCapacityInternal(size + 1); // Increments modCount!!
//数组拷贝,耗时工作
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
//赋值
elementData[index] = element;
//长度
size++;
}
  1. 该方法每次都要确定容器大小,会导致并发版本的count+1
  2. add 方法有两个
    1. add(E e):直接将数据插入到List的尾部
    2. add(int index,E e):将数据插入到index后面
  3. 如果是插入,则会导致数组进行复制操作,由于ArrayList基于数组,所以会导致数组复制,而数组复制必定是一个耗时的操作

remove()

    public E remove(int index) {
//校验
rangeCheck(index);
//并发参数+1
modCount++;
//获取旧值
E oldValue = elementData(index);
//移动的长度为=数组长度-需要删除元素下标-1
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//让gc回收这个数据的内存
elementData[--size] = null; // clear to let GC do its work
//返回旧值
return oldValue;
}
  1. 同样删除会导致数组复制

subList

该方法主要用于将数组进行分割,对于数组分割后,其实该数组为浅拷贝操作,如果在该SubList中操作相关数据,将会导致ArrayList中的数据改变!!

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

ArrayList扩容机制

规则

  1. 如果其数组需要进行扩容,则会扩容为原数组的1.5倍
  2. 如果用户指定了容器的大小,且用于指定的数值大于容器的最小容量,则将用于容量作为该容器容量
  3. 如果容器容量扩容后大于Integer.MAX_VALUE - 8,则会尝试扩容为Integer.MAX_VALUE大小

解析

  1. 确定minCapacity值是否比容器中的数据容量大

  2. 如果大则扩容,否则什么也不做

  3. 扩容

    1. 如果minCapacity比newCapacity小则直接使用minCapacity作为扩容容量

    2. 如果其数组个数大于最大的数组的长度,则尝试使用Integer.MAX_VALUE作为数组的容器大小

源码解析

    /**
* 1.确定minCapacity值是否比容器中的数据容量大
* 2.如果大则扩容,否则什么也不做
* @param minCapacity
*/
private void ensureExplicitCapacity(int minCapacity) {
modCount++; // overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
/**
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
* 扩容
* 1. 如果minCapacity比newCapacity小则直接使用minCapacity作为扩容容量
* 2. 如果其数组个数大于最大的数组的长度,则尝试使用Integer.MAX_VALUE作为数组的容器大小
* @param minCapacity the desired minimum capacity
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
//新容器容量扩容为现在容器的容量的1.5倍
//eg 旧10个->新15个
int newCapacity = oldCapacity + (oldCapacity >> 1);
//看看谁大
if (newCapacity - minCapacity < 0)
//如果min比new小则直接复制min
newCapacity = minCapacity;
//如果新的容器比最大的数组大小还要打
if (newCapacity - MAX_ARRAY_SIZE > 0)
//只能复制为最大容器大小,但是可能会抛出oom
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
/**
* 大数组容器扩容
* @param minCapacity
* @return
*/
private static int hugeCapacity(int minCapacity) {
//校验参数合法性
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}

List的序列化操作

由于Java默认序列化以及反序列化的时候回分别调用对应的writeObject方法以及readObject()方法,所以以下将对这两个方法进行分析。

6.1 为什么需要自定义序列化规则

由于在ArrayList中的elementData数组中可能存在一些空的元素(由于ArrayList扩容机制)

6.2 源码分析

  1. 序列化操作:

    1. ArrayList内部存储数据元素为transient不会被序列化
    /**
* 缓存数据集合
*/
transient Object[] elementData; // non-private to simplify nested class access
  1. 如何支持序列化操作

     	1. 序列化数量
    2. 获取数据数组,然后使用for循环一个一个序列化该对象到数据中。
        /**
    * Save the state of the <tt>ArrayList</tt> instance to a stream (that
    * is, serialize it).
    * 保证其可以被序列化到对象中
    *
    * @serialData The length of the array backing the <tt>ArrayList</tt>
    * instance is emitted (int), followed by all of its elements
    * (each an <tt>Object</tt>) in the proper order.
    */
    private void writeObject(java.io.ObjectOutputStream s)
    throws java.io.IOException{
    // Write out element count, and any hidden stuff
    //数据被序列化的数量
    int expectedModCount = modCount;
    //使用默认的模式进行序列化,只序列化非静态化变量以及非transient修饰的数据
    s.defaultWriteObject(); // Write out size as capacity for behavioural compatibility with clone()
    //写入长度
    s.writeInt(size); // Write out all elements in the proper order.
    for (int i=0; i<size; i++) {
    //写入每一个object数据
    s.writeObject(elementData[i]);
    }
    //如果发现序列化的modCount与expectedModCount可能是并发导致
    if (modCount != expectedModCount) {
    throw new ConcurrentModificationException();
    }
    }
  2. 如何进行的反序列化

    1. 重新创建List,然后读取长度以及多个对象对对象进行相关赋值操作
        /**
    * Reconstitute the <tt>ArrayList</tt> instance from a stream (that is,
    * deserialize it).
    * 反序列化数据
    */
    private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    //设置默认的数组长度为空数组长度
    elementData = EMPTY_ELEMENTDATA; // Read in size, and any hidden stuff
    //使用默认的模式进行序列化,只序列化非静态化变量以及非transient修饰的数据
    s.defaultReadObject(); // Read in capacity
    //读取list长度
    s.readInt(); // ignored if (size > 0) {
    // be like clone(), allocate array based upon size not capacity
    //开始计算并克隆
    int capacity = calculateCapacity(elementData, size);
    SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, 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();
    }
    }
    }

JDK1.8新增的方法

forEach()

lambda循环方法

    /**
* JDK新增方法 ForEach方法
* @param action
*/
@Override
public void forEach(Consumer<? super E> action) {
//校验lambda不为空
Objects.requireNonNull(action);
//并发version版本统计
final int expectedModCount = modCount;
@SuppressWarnings("unchecked")
final E[] elementData = (E[]) this.elementData;
final int size = this.size;
//循环每次判断一下并发参数是否进行了修改,如果进行了修改则直接退出for循环
for (int i=0; modCount == expectedModCount && i < size; i++) {
action.accept(elementData[i]);
}
//并抛出并发异常
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}

spliterator

该方法用于返回进行并发计算时候的分割器

/**
* Creates a <em><a href="Spliterator.html#binding">late-binding</a></em>
* and <em>fail-fast</em> {@link Spliterator} over the elements in this
* list.
*
* <p>The {@code Spliterator} reports {@link Spliterator#SIZED},
* {@link Spliterator#SUBSIZED}, and {@link Spliterator#ORDERED}.
* Overriding implementations should document the reporting of additional
* characteristic values.
* 并发分割方法
* 懒加载加入,只有当数据
* @return a {@code Spliterator} over the elements in this list
* @since 1.8
*/
@Override
public Spliterator<E> spliterator() {
/**
* 1. 参数1 this
* 2. origin
* 3. fence 当使用的时候才进行初始化
* 4. 并发参数
*/ return new ArrayListSpliterator<>(this, 0, -1, 0);
}

removeIf方法

lambda 移除符合某个规则的方法

/**
* liambad方法移除元素
* @param filter
* @return
*/
@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];
}
//释放gc
for (int k=newSize; k < size; k++) {
elementData[k] = null; // Let gc do its work
}
this.size = newSize;
//并发version检查
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
//并发值+1
modCount++;
} return anyToRemove;
}

ArrayListSpliterator

其为并发分割器,用于我们使用并发调用parallelStream方法时候调用该方法

static final class ArrayListSpliterator<E> implements Spliterator<E> {

    private final ArrayList<E> list;
private int index; // current index, modified on advance/split
private int fence; // -1 until used; then one past last index
private int expectedModCount; // initialized when fence set /** Create new spliterator covering the given range */
ArrayListSpliterator(ArrayList<E> list, int origin, int fence,
int expectedModCount) {
this.list = list; // OK if null unless traversed
this.index = origin;
this.fence = fence;
this.expectedModCount = expectedModCount;
} /**
* 当获取的时候才进行fence的初始化操作
* @return
*/
private int getFence() { // initialize fence to size on first use
int hi; // (a specialized variant appears in method forEach)
ArrayList<E> lst;
if ((hi = fence) < 0) {
//之前的数组为空则说明没有进行分割,则从0开始
if ((lst = list) == null)
hi = fence = 0;
else {
//否则则
expectedModCount = lst.modCount;
//则将该值赋值为lst的长度
hi = fence = lst.size;
}
}
return hi;
} /**
* 尝试分割
* 1、总长度为数组长度
* 2、分割成两份
* 3、二分法分割
* 3、中间值为数组长度+分割后的数组长度,就是二分法啦
* @return
*/
public ArrayListSpliterator<E> trySplit() {
int hi = getFence(), lo = index, mid = (lo + hi) >>> 1;
return (lo >= mid) ? null : // divide range in half unless too small
new ArrayListSpliterator<E>(list, lo, index = mid,
expectedModCount);
} /**
* 并发执行操作
* @param action
* @return
*/
public boolean tryAdvance(Consumer<? super E> action) {
if (action == null)
throw new NullPointerException();
int hi = getFence(), i = index;
//如果没有超过这个分割的长度则继续操作否则返回false
if (i < hi) {
index = i + 1;
@SuppressWarnings("unchecked") E e = (E)list.elementData[i];
action.accept(e);
//如果出现了并发改变,则抛出异常
if (list.modCount != expectedModCount)
throw new ConcurrentModificationException();
return true;
}
return false;
} /**
* 并发ForEach输出
* @param action
*/
public void forEachRemaining(Consumer<? super E> action) {
int i, hi, mc; // hoist accesses and checks from loop
ArrayList<E> lst; Object[] a;
if (action == null)
throw new NullPointerException();
if ((lst = list) != null && (a = lst.elementData) != null) {
if ((hi = fence) < 0) {
mc = lst.modCount;
hi = lst.size;
}
else
mc = expectedModCount;
if ((i = index) >= 0 && (index = hi) <= a.length) {
for (; i < hi; ++i) {
@SuppressWarnings("unchecked") E e = (E) a[i];
action.accept(e);
}
if (lst.modCount == mc)
return;
}
}
throw new ConcurrentModificationException();
} public long estimateSize() {
return (long) (getFence() - index);
} public int characteristics() {
return Spliterator.ORDERED | Spliterator.SIZED | Spliterator.SUBSIZED;
}
}

ArrayList遍历

8.1 使用迭代器进行遍历

Iterator<Double> iterator = list.iterator();
while (iterator.hasNext()){
Double next = iterator.next();
System.out.println(next);
}

使用fori进行遍历

for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}

8.3 使用for进行遍历

for (Double next : list) {
System.out.println(next);
}

8.4 使用流进行访问(JDK1.8)

list.forEach(System.out::println);

8.5 以上几种模式的效率比较

测试代码如下

package cn.lonecloud;

import java.time.Duration;
import java.time.Instant;
import java.util.*; /**
* @author lonecloud
* @version v1.0
* @date 2019/4/3 19:56
*/
public class ListTest {
public static void main(String[] args) {
int len=20000000;
List<Integer> list=new ArrayList<>(len);
for (int i = 0; i < len; i++) {
list.add(i);
}
test(list);
}
public static void test(List<Integer> list){ long itrBegin = System.currentTimeMillis();
//1. 使用迭代器
Iterator<Integer> iterator = list.iterator();
Integer a=0;
while (iterator.hasNext()){
Integer next = iterator.next();
a=next;
}
long itrEnd=System.currentTimeMillis();
long foriStart=System.currentTimeMillis();
//2. fori
for (int i = 0; i < list.size(); i++) {
a=list.get(i);
}
long foriEnd=System.currentTimeMillis(); long forStart=System.currentTimeMillis();
//3. for
for (Integer next : list) {
a=next;
}
long forEnd=System.currentTimeMillis(); long streamstart=System.currentTimeMillis(); //4. stream
list.forEach((value)->{
Integer b=value;
}); long streamEnd=System.currentTimeMillis();
System.out.println("迭代器时间:"+(itrEnd-itrBegin));
System.out.println("fori时间:"+(foriEnd-foriStart));
System.out.println("for时间:"+(forEnd-forStart));
System.out.println("stream时间:"+(streamEnd-streamstart));
}
}

结果:

迭代器时间:31
fori时间:32
for时间:32
stream时间:74

总结:

效率:

  1. fori和for和迭代器大致相同,由于事先了RandomAccess

  2. stream时间高一些,原因是需要进行更多的方法调用产生的时间

使用到的设计模式

  • 迭代器设计模式
  • 模板设计模式

总结

  1. ArrayList是基于数组的集合,适合循环迭代多的场景,不适合修改多的场景

  2. 在使用ArrayList时候需要注意在创建的时候(预估一下你需要的容器大小)

    由于如果你在使用的时候超过了初始化容量(10),这将会导致容器进行一次(容器扩容),而数组复制是一件非常耗时的操作

  3. ArrayList中的clone()方法以及copy方法,都是浅克隆的。

  4. 在一般情况下,如果集合容器出现容量不足需要扩容的时候,其集合会扩容为原集合的1.5倍大小

  5. 如果需要将List转换成数组,推荐使用泛型方法T[] toArray(T[] a)而不是Object[] toArray()方法

  6. 如果涉及在指定位置上插入指定元素的操作,如果这种操作比较多,推荐使用LinkedList而不是使用ArrayList,因为你每次在指定的位置上插入元素会导致数组拷贝操作。

  7. 如果List涉及到频繁修改的时候,建议使用LinkedList,而不是使用ArrayList。

  8. ArrayList是一个非线程安全类,如果需要设计到线程安全,请使用并发包相关的类

  9. subList(int fromIndex, int toIndex)方法返回的SubList类,其中如果你对该List操作时候,原集合也会改变

集合源码分析[3]-ArrayList 源码分析的更多相关文章

  1. JAVA常用集合源码解析系列-ArrayList源码解析(基于JDK8)

    文章系作者原创,如有转载请注明出处,如有雷同,那就雷同吧~(who care!) 一.写在前面 这是源码分析计划的第一篇,博主准备把一些常用的集合源码过一遍,比如:ArrayList.HashMap及 ...

  2. 转:【Java集合源码剖析】ArrayList源码剖析

    转载请注明出处:http://blog.csdn.net/ns_code/article/details/35568011   本篇博文参加了CSDN博文大赛,如果您觉得这篇博文不错,希望您能帮我投一 ...

  3. Java集合框架——jdk 1.8 ArrayList 源码解析

    前言:作为菜鸟,需要经常回头巩固一下基础知识,今天看看 jdk 1.8 的源码,这里记录 ArrayList 的实现. 一.简介 ArrayList 是有序的集合: 底层采用数组实现对数据的增删查改: ...

  4. 【源码解析】- ArrayList源码解析,绝对详细

    ArrayList源码解析 简介 ArrayList是Java集合框架中非常常用的一种数据结构.继承自AbstractList,实现了List接口.底层基于数组来实现动态容量大小的控制,允许null值 ...

  5. Java集合基于JDK1.8的ArrayList源码分析

    本篇分析ArrayList的源码,在分析之前先跟大家谈一谈数组.数组可能是我们最早接触到的数据结构之一,它是在内存中划分出一块连续的地址空间用来进行元素的存储,由于它直接操作内存,所以数组的性能要比集 ...

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

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

  7. Java入门系列之集合ArrayList源码分析(七)

    前言 上一节我们通过排队类实现了类似ArrayList基本功能,当然还有很多欠缺考虑,只是为了我们学习集合而准备来着,本节我们来看看ArrayList源码中对于常用操作方法是如何进行的,请往下看. A ...

  8. ArrayList源码解析[一]

    ArrayList源码解析[一] 欢迎转载,转载烦请注明出处,谢谢. https://www.cnblogs.com/sx-wuyj/p/11177257.html 在工作中集合list集合用的相对来 ...

  9. 【集合框架】JDK1.8源码分析之ArrayList(六)

    一.前言 分析了Map中主要的类之后,下面我们来分析Collection下面几种常见的类,如ArrayList.LinkedList.HashSet.TreeSet等.下面通过JDK源码来一起分析Ar ...

随机推荐

  1. iframe中的a标签电话链接不能正常打开

    背景 经测试,android手机中没有这个问题, iphone手机中的Safari浏览器会出现这个问题. 例如: <a href = "tel://1-408-555-5555&quo ...

  2. Parcelable 小记

    Parcelable 类,接口类,用于数据的序列化封装.常见的Bundle,Intent类都实现了该类.   实现该类需要实现writeToParcel和describeContents方法,最后还需 ...

  3. Docker 创建 Bamboo6.7.1 以及与 Crowd3.3.2 实现 SSO 单点登录

    目录 目录 1.介绍 1.1.什么是 Bamboo? 2.Bamboo 的官网在哪里? 3.如何下载安装? 4.对 Bamboo 进行配置 4.1.获取授权许可 4.2.一般配置 4.3.数据库配置 ...

  4. java8及8之前日期相关类

    java 8日期相关类 Instant:精确到纳秒的时间戳 Duration:处理有关基于时间的时间量 LocalDate:只包含日期,比如:2016-10-20 LocalTime:只包含时间,比如 ...

  5. 关于LeetCode上链表题目的一些trick

    最近在刷leetcode上关于链表的一些高频题,在写代码的过程中总结了链表的一些解题技巧和常见题型. 结点的删除 指定链表中的某个结点,将其从链表中删除. 由于在链表中删除某个结点需要找到该结点的前一 ...

  6. 简单的C#实体映射 AutoMapper

    AutoMapper是对象到对象的映射工具.在完成映射规则之后,AutoMapper可以将源对象转换为目标对象. 要映射实体 public class SourceModel { public int ...

  7. SQLServer之创建数据库快照

    创建数据库快照注意事项 语法:set transaction isolation level snapshot; 指定事务中任何语句读取的数据都将是在事务开始时便存在的数据的事务上一致的版本. 事务只 ...

  8. Linux(三)——Unix&Linux 的基础命令

    Linux(三)--Unix&Linux 的基础命令 快捷键 Ctl-A 光标移动到行首 Ctl-C 终止命令 Ctl-D 注销登录 Ctl-E 光标移动到行尾 Ctl-U 删除光标到行首的所 ...

  9. apache环境下ssl证书链不完整问题解决,原因是缺少中间证书

    事情的起因是,对一个网站的升级,从http升级到https,苹果手机可以正常访问,唯独安卓手机出现空白,安卓访问https的时候是出现的空白. 服务器的系统是windows Server 2008 R ...

  10. MacOS 10.13.6 下装xcode 流程

    1.最好先安装brew https://github.com/Homebrew/brew/releases 自动安装脚本 /usr/bin/ruby -e "$(curl -fsSL htt ...