看图学源码—Java集合 之 ArrayList 源码分析

ArrayList

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
  
}

实现的接口

Serializable

  • 类的序列化由实现java.io.Serializable接口的类启用。 不实现此接口的类将不会使任何状态 序列化 或 反序列化。 可序列化类的所有子类型都是可序列化的。 序列化接口没有方法或字段,仅用于标识可串行化的语义。
  • 序列化:将对象的数据写入到文件(写对象)
  • 反序列化:将文件中对象的数据读取出来(读对象)

RandomAccess

  • 目的:可以 随机访问列表 或者 顺序访问列表的时候提供良好的性能
for (int i = 0; i < list.size(); i++) { list.get(i); }     // -----随机访问
Iterator<String> it = list.iterator();  while (it.hasNext()){  it.next(); }   // -----顺序访问

/*
ArrayList 中随机访问 更快
  
LinkedList 中 顺序访问 更快  
-- 由于随机访问的时候源码底层每次都需要进行折半的动作,再经过判断是从头还是从尾部一个个寻找。 
-- 而顺序访问只会在获取迭代器的时候进行一次折半的动作,以后每次都是在上一次的基础上获取下一个元素。 
-- 因此顺序访问要比随机访问快得多 
*/

总结: 对返回的集合进行判断,如果返回的集合实现了 RandomAccess 就使用 普通for ; 否则使用迭代器(增强for)

Cloneable

浅拷贝

1、 被克隆对象所在的类必须实现 Cloneable 接口

2、 必须重写 clone 方法

class  Student implements Cloneable{
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

}
class clone_{
    public static void main(String[] args) throws CloneNotSupportedException {
        Student student = new Student();
        Student student1 = (Student) student.clone();
        System.out.println(student == student1); // fasle
    }
}

存在的问题(局限性): 基本数据类型可以完全复制,但是引用数据类型不可以,仅仅只会拷贝一分引用

深拷贝
class  Skill implements Cloneable{

    String skillname;

    public Skill(String skillname) {
        this.skillname = skillname;
    }

    public String getSkillName() {
        return skillname;
    }

    public void setSkillName(String skillname) {
        this.skillname = skillname;
    }
    @Override
    protected Object clone() throws CloneNotSupportedException {
      return super.clone();
    }

    @Override
    public String toString() {
        return "Skill{" +
                "skillname='" + skillname + '\'' +
                '}';
    }
}





class  Student implements Cloneable{
    public Skill skill;
    public  int age;

    public Student(int age,Skill skill) {
        this.age = age;
        this.skill = skill;
    }

    public Skill getSkill() {
        return skill;
    }

    public void setSkill(Skill skill) {
        this.skill = skill;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        // super.clone()  深拷贝不可以直接调用 super 的方法,浅拷贝 是可以的
        //先 clone 一个学生对象
        Student stu = (Student) super.clone();
        //再clone 一个 Skill 对象
        Skill skill = (Skill)this.skill.clone();
        // 将 clone  出来的 skill_ 对象赋值给 stu 对象的成员变量
        stu.setSkill(skill);
        return  stu;
    }
    @Override
    public String toString() {
        return "Student{" +
                "skill=" + skill +
                ", age=" + age +
                '}';
    }
}




class Clone_{
    public static void main(String[] args) throws CloneNotSupportedException {
        Skill skill = new Skill("ry123");
        Student  student = new Student(23,skill);
        Object student1 = student.clone();

        System.out.println(student == student1); // fasle
        System.out.println(student1);
        System.out.println(student);

        skill.setSkillName("ry");
        System.out.println(student1);
        System.out.println(student);
    }
}
false
Student{skill=Skill{skillname='ry123'}, age=23}
Student{skill=Skill{skillname='ry123'}, age=23}
Student{skill=Skill{skillname='ry123'}, age=23}
Student{skill=Skill{skillname='ry'}, age=23}

源码分析

成员变量

 private static final long serialVersionUID = 8683452581122892189L;

    /**
     * Default initial capacity.   默认初始容量。
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * 创建一个大小为 0 的 list  ,容量从 0 开始增加
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
     * 创建一个 默认空参 的 list  ,容量从10 开始增加
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

	
    /**
     存储 ArrayList 元素的数组缓冲区。 ArrayList的容量就是该数组缓冲区的长度。
     添加第一个元素时,任何具有 elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 的空 ArrayList 都将扩展为 DEFAULT_CAPACITY。
     */
    transient Object[] elementData; // non-private to simplify nested class access

/*

// 将ArrayList实例的状态保存到流中(即将其序列化)
         private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{
                // 写出元素计数以及任何隐藏的内容
                int expectedModCount = modCount;
                s.defaultWriteObject();

                // 将大小写为与 clone() 行为兼容的容量
                s.writeInt(size);

                // 按正确的顺序写出所有元素。
                for (int i=0; i<size; i++) {
                    s.writeObject(elementData[i]);
                }

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


*/

    /**
     * ArrayList 的大小(它包含的元素数量)
     */
    private int size;


transient 作用:被修饰的属性不会序列化到硬盘

因为 ArrayList 会扩容,其中元素可以删除但是集合不会缩容,所以不序列化到硬盘就可以减少占用的硬盘,减少大量删除之后出现的 null 值,可以优化磁盘

构造函数

/**
 * 构造一个具有指定初始容量的空列表。
 */
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);
    }
}

/**
 * 构造一个初始容量为 10 的空列表
 */
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;  // 默认的空数组
}

/**
 * 构造一个包含指定集合的元素的列表,按照集合迭代器返回这些元素的顺序排列。
 */
public ArrayList(Collection<? extends E> c) {
 // 把集合变为数组,然后赋值给 ArrayList 
    Object[] a = c.toArray();
    if ((size = a.length) != 0) {
        if (c.getClass() == ArrayList.class) {
            elementData = a;
        } else {
          //调用copy 进行赋值
            elementData = Arrays.copyOf(a, size, Object[].class);
        }
    } else { //a.length == 0  传入的是空的集合
        // 那么赋值一个容量为 EMPTY_ELEMENTDATA 类型的空数组
        elementData = EMPTY_ELEMENTDATA;
    }
}

//将集合转数组的方法
public Object[] toArray() {
//调用数组工具类方法进行拷贝
  return Arrays.copyOf(elementData, size);
}

添加方法

add(E e)

image-20231206230057883

/*
该列表的结构被修改的次数,迭代时的修改会产生 ConcurrentModificationException来响应next 、 remove 、 previous 、 set或add操作,是快速失败行为,而不是在迭代过程中面对并发修改时的非确定性行为。

子类对该字段的使用是可选的。如果子类希望提供快速失败迭代器(和列表迭代器),那么它只需在其add(int, E)和remove(int)方法(以及它覆盖的导致结构性错误的任何其他方法)中增加此字段。列表的修改)。对add(int, E)或remove(int)单次调用必须向该字段添加不超过 1 个,否则迭代器(和列表迭代器)将抛出虚假的ConcurrentModificationExceptions 。如果实现不希望提供快速失败迭代器,则可以忽略该字段。
*/
protected transient int modCount = 0;


// 将指定元素追加到此列表的末尾。
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    
  	elementData[size++] = e; // 数组操作,后移 赋值
    return true;
}

	//扩容 + 数组拷贝 、数组容量判断、初始化数组
private void ensureCapacityInternal(int minCapacity) {
      ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

//计算数组容量
private static int calculateCapacity(Object[] elementData, int minCapacity) {
  if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { // 要是 这里的条件是成立:elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
    return Math.max(DEFAULT_CAPACITY, minCapacity);  // 返回的就是 max (10 , minCapacity) 直接扩容到 10 
  }
  // 要是 elementData == EMPTY_ELEMENTDATA   返回的就是 minCapacity,从 0 开始一步步扩容上去
  return minCapacity;
}


private void ensureExplicitCapacity(int minCapacity) {  // minCapacity = size +1  ,加1:可以进行提前扩容,为下一次做准备
  	modCount++;  // 记录修改次数
  	// overflow-conscious code
  	if (minCapacity - elementData.length > 0)
    grow(minCapacity); // 进行扩容
}

/**
 * 要分配的数组的最大大小。一些虚拟机在数组中保留一些头字。尝试分配更大的数组可能会导致 OutOfMemoryError:请求的数组大小超出 VM 限制
 */
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

// 扩容方法的具体实现
private void grow(int minCapacity) {
  // overflow-conscious code
  int oldCapacity = elementData.length;
  int newCapacity = oldCapacity + (oldCapacity >> 1);  // 大约1.5 倍,源码的 原数组+原数组右移一位
  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;
}

public static <T> T[] copyOf(T[] original, int newLength) {
    return (T[]) copyOf(original, newLength, original.getClass());
}


public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> 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;
}

add(int index, E element)

image-20231206230239279

/**
 * 在此列表中的指定位置插入指定元素。将当前位于该位置的元素(如果有)和任何后续元素向右移动(将其索引加一)
 */
public void add(int index, E element) {
    rangeCheckForAdd(index); // 检查下标

    ensureCapacityInternal(size + 1); //扩容 、数组容量判断、初始化数组  指定位置之前的数组进行拷贝
    System.arraycopy(elementData, index, elementData, index + 1, size - index); // 指定位置之后的数组进行拷贝
    elementData[index] = element;  
    size++;
}

//   检查下标是否越界
 private void rangeCheck(int index) {
   if (index >= size)  // size  是数组的实际容量  length  才是数组的长度
     throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
//越界提示
    private String outOfBoundsMsg(int index) {
        return "Index: "+index+", Size: "+size;
    }

addAll(Collection<? extends E> c)

image-20231206230157539

public boolean addAll(Collection<? extends E> c) {
  // 转为数组
    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;
}
addAll(int index, Collection<? extends E> c)

image-20231206230219066

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)
      //从 下标位置开始后移 numMoved 个位置
        System.arraycopy(elementData, index, elementData, index + numNew,numMoved);
  // 要是移动的元素个数是0 ,就直接添加到后面
    System.arraycopy(a, 0, elementData, index, numNew);
    size += numNew;
    return numNew != 0;
}
设置方法
set(int index, E element)

image-20231206230303739

public E set(int index, E element) {
  // 检查是否越界
    rangeCheck(index);
	// 获取原来位置的元素
    E oldValue = elementData(index);
  // 新值取代 旧值
    elementData[index] = element;
  // 返回旧值
    return oldValue;
}

   E elementData(int index) {
        return (E) elementData[index];
    }

获取方法

get(int index)
public E get(int index) {
    rangeCheck(index);  // 越界检查

    return elementData(index);  // 返回对应的 下标位置 的元素
}

删除方法

remove(int index)

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

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

   // 计算得到需要移除的元素的位置
    int numMoved = size - index - 1;
  // 删除 最后的一个元素 就 = 0,不用移动了
    if (numMoved > 0)
          // 开始移动元素       原数组   (源地址)删除元素的后一个位置为  		目标数组   		(目标位置)删除元素的位置   	 移动元素的个数(尺寸)
        System.arraycopy(elementData, index+1,                      elementData,	     index,                    numMoved);
  //将后面的元素置为 null  方便进行 gc 回收
  elementData[--size] = null; // clear to let GC do its work

    return oldValue;
}
// 找到这个位置的元素
E elementData(int index) {
  return (E) elementData[index];
}
remove(Object o)

只会移除第一个 匹配到的元素

image-20231206230505356

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;
}
// 根据下标 移动(copy) 中的元素
 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
}
removeAll(Collection<?> c)

image-20231206230849263

public boolean removeAll(Collection<?> c) {
    Objects.requireNonNull(c);  // 判空   空则抛出空指针
    return batchRemove(c, false);
}

public static <T> T requireNonNull(T obj) {
  if (obj == null)
    throw new NullPointerException();
  return obj;
}

// 批量删除
 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++)
              // 数组中的元素,要是 elementData[r] 不在 要删除的元素集合中,
                if (c.contains(elementData[r]) == complement)
                 // 将这个元素 覆盖到 elementData 中
                    elementData[w++] = elementData[r];
        } finally {
          // c.contains 出现 异常的时候  就会导致 r != size ,此时 w 只等于异常出现之前满足条件时记录的元素个数,
            if (r != size) {  // r != size判断是为了在出现异常的情况下也可以正常的进行元素移除
                System.arraycopy(elementData, r, elementData, w,  size - r); // elementData 没有全部删除完,只删除了一部分,没删除的部分要进行前移
                w += size - r;
            }
          // 要是有元素被删除,就将 w 下标之后的元素 置为 null  方便进行 gc 
            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;
    }

removeRange(int fromIndex, int toIndex)

image-20231206230659951

//删除一个范围的元素
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;
}

清空方法

clear()

image-20231206230909517

public void clear() {
    modCount++;
  // 遍历置为 null 方便进行垃圾回收
    // clear to let GC do its work
    for (int i = 0; i < size; i++)
        elementData[i] = null;
// size 置为 0 
    size = 0;
}

是否包含

contains(Object o)

底层也是通过循环遍历集合,取出一个个的元素和要找的元素进行比较

image-20231206230928878

/*
如果此列表包含指定元素,则返回true 。当且仅当此列表包含至少一个元素 e 且满足(o==null ? e==null : o.equals(e)) 时,才返回true 
*/
public boolean contains(Object o) {
    return indexOf(o) >= 0;
}
/*
返回此列表中指定元素第一次出现的索引,如果此列表不包含该元素,则返回 -1。更正式地说,返回满足(o==null ? get(i)==null : o.equals(get(i))) 的最低索引i ,如果没有这样的索引,则返回 -1 。
  */
 public int indexOf(Object o) {
   // 如果元素是null,也进行遍历操作  因为集合中有可能够会存储null
        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;
        }
   //如果没有走if,也没有走else,那么就说明o该元素在集合中不存在
        return -1;
    }

继承自 AbstractCollection 的 toString()

public String toString() {
    Iterator<E> it = iterator();
  //没有元素:   返回 "[]"
    if (! it.hasNext())
        return "[]";

  // 创建  StringBuilder
    StringBuilder sb = new StringBuilder();
    sb.append('[');
    for (;;) {
      //无限循环找到下一个元素
        E e = it.next();
      // sb添加元素  e == this防止自己引用自己
        sb.append(e == this ? "(this Collection)" : e);
      // 没有下一个元素了
        if (! it.hasNext())
          //结尾标记符,并在 将整个缓冲区转为 String 
            return sb.append(']').toString();
      // sb添加 标记符号
        sb.append(',').append(' ');
    }
}

迭代器

image-20231206230954784

image-20231206231100720

image-20231206231049830

public Iterator<E> iterator() {
    return new Itr();
}

// 一个内部类
 private class Itr implements Iterator<E> {
        int cursor;       // 下一个要返回元素的索引。默认 0 
        int lastRet = -1; // 最后一个返回元素的索引
   // 在迭代的过程中,只要实际修改次数和预期修改次数不一致就会产生并发修改异常,
   // 由于expectedModCount是Itr的成员变量,那么只会被赋值一次!!!
        int expectedModCount = modCount;  //expectedModCount预期的修改次数   modCount实际的修改次数

        Itr() {}

   // 判断集合是不是 有元素
        public boolean hasNext() { 
            return cursor != size;  // 索引是否达到 数组长度 
        }

        @SuppressWarnings("unchecked")
        public E next() {
          
            checkForComodification(); 
          /*
          有没有出现  ConcurrentModificationException 问题 ,就是根据  modCount != expectedModCount判断的
         	 final void checkForComodification() {
                if (modCount != expectedModCount)
                    throw new ConcurrentModificationException();
            }
          */
            int i = cursor;  //下一个元素的索引赋值给i
            if (i >= size)  // 索引达到 数组长度,表示没有下一个元素了
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData; //集合底层存储数据的数组的地址 赋值 给 局部变量 []elementData
            if (i >= elementData.length)
                throw new ConcurrentModificationException(); // 下一个元素的索引 大于 集合底层存储元素的长度 并发修改异常
          //索引后移  
          cursor = i + 1;
          // 取出数组中的 元素 并 返回
            return (E) elementData[lastRet = i];
        }

   // 迭代器的删除方法
        public void remove() {
          // 最后返回元素的索引是不是  < 0 ;满足条件就产生 非法状态异常
            if (lastRet < 0)
                throw new IllegalStateException();
          // 检查 是否会产生并发修改异常 
            checkForComodification();

          尝试执行 ArrayListremove() 方法,将 lastRet 所指向的元素从 ArrayList 中移除。
          
            try {
              // 调用  ArrayList 的remove 进行删除
                ArrayList.this.remove(lastRet);
              // 下一个要返回元素的索引   就是  最后一个返回元素的索引
                cursor = lastRet;
              // 最后一个返回元素的索引 置为 -1 
                lastRet = -1;
              // 赋值  ==> 此时 不管集合自身是否删除成功 ;实际修改次数和预期修改次数又一致了,所以并不会产生并发修改异常
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        @Override
        @SuppressWarnings("unchecked")
   //用于对ArrayList中的元素进行迭代操作,并将每个元素传递给指定的Consumer函数。
        public void forEachRemaining(Consumer<? super E> consumer) {
          
          //首先检查传入的consumer是否为null,如果是null则抛出NullPointerException
            Objects.requireNonNull(consumer);
          //获取ArrayList的大小和当前游标位置。
            final int size = ArrayList.this.size;
            int i = cursor;
          //如果游标位置大于等于ArrayList的大小,表示已经迭代完所有元素,直接返回。
            if (i >= size) {
                return;
            }
          //获取ArrayList的元素数组elementData。
            final Object[] elementData = ArrayList.this.elementData;
          //如果游标位置大于等于elementData的长度,表示在迭代过程中ArrayList发生了结构性变化(比如增加或删除元素),抛出ConcurrentModificationException异常。
            if (i >= elementData.length) {
                throw new ConcurrentModificationException();
            }
          //当游标位置不等于ArrayList的大小   &&   modCount等于expectedModCount时
            while (i != size && modCount == expectedModCount) {
                consumer.accept((E) elementData[i++]);  // 将元素传递给consumer。
            }
            // update once at end of iteration to reduce heap write traffic
          // 更新游标位置cursor和上一个返回的元素位置lastRet。
            cursor = i;
            lastRet = i - 1;
          //调用checkForComodification方法检查是否发生了结构性变化,如果发生了则抛出ConcurrentModificationException异常。
            checkForComodification();
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }
特色
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()){
    Integer next = iterator.next();
    System.out.println(next);
    if (next == ArrayList中的元素值){
        // 此处 会发生 并发修改异常,原因,就在于  底层判断 是否有并发修改异常 是根据  modCount != expectedModCount 判断的
        // 但是这里执行了  ArrayList 的remove  remove 的底层 会对 modCount++  从而就导致了 modCount != expectedModCount
        // 所以就有 有并发修改异常 
        list1.remove(ArrayList中的元素值);
    }
}

//ArrayList中的元素值 不是 list 中的 倒数第二个值

注意:

  • 结论:

    只有当要删除的元素在集合的倒数第二个位置的时候,不会产生并发修改异常

  • 原因:
    是因为在调用hasNext()方法的时候,光标的值和集合的长度一样,那么就会返回false;因此就不会再去调用next()获取集合的元素,既然不会调用next()那么底层就不会产生并发修改异常

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/224482.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

区块链密码学:基础知识、应用与未来发展

一、引言 区块链技术&#xff0c;作为一种分布式、去中心化的数据管理方式&#xff0c;密码学在其安全性和可靠性方面发挥着至关重要的作用。本文将详细介绍区块链密码学的基础知识、应用以及未来发展趋势。 二、区块链密码学基础知识 区块链密码学是区块链技术的核心组成部分…

02 CSS基础入门

文章目录 一、CSS介绍1. 简介2. 相关网站3. HTML引入方式 二、选择器1. 标签选择器2. 类选择器3. ID选择器4. 群组选择器 四、样式1. 字体样式2. 文本样式3. 边框样式4. 表格样式 五、模型和布局1. 盒子模型2. 网页布局 一、CSS介绍 1. 简介 CSS主要用于控制网页的外观&#…

联通宽带+老毛子Padavan固件 开启IP v6

联通宽带开启IP v6 参考&#xff1a; 联通宽带开启 IPV6 的方法_联通ipv6怎么开通-CSDN博客 个人宽带如何开启IPv6网络访问 - 知乎 (zhihu.com) 首先&#xff0c;你要确定当前你所在的地区运营商已经开通了IPV6&#xff0c;可以使用手机流量 IP查询(ipw.cn) | IPv6测试 | IPv…

ELK 日志解决方案

ELK 是目前最流行的集中式日志解决方案&#xff0c;提供了对日志收集、存储、展示等一站式的解决方案。 ELK 分别指 Elasticsearch、Logstash、Kibana。 Elasticsearch&#xff1a;分布式数据搜索引擎&#xff0c;基于 Apache Lucene 实现&#xff0c;可集群&#xff0c;提供…

【FPGA图像处理实战】- VGA接口与时序详解

VGA接口是一个很有历史的接口&#xff0c;全称为Video Graphics Array&#xff08;VGA&#xff09;视频图形阵列&#xff0c;是IBM公司在1987年随着PS/2一起推出的使用模拟信号的一种视频传输标准。 时至今日&#xff0c;这个接口依然还在大量使用&#xff0c;因为这个接口具有…

【PTA-C语言】编程练习4 - 数组Ⅰ

如果代码存在问题&#xff0c;麻烦大家指正 ~ ~有帮助麻烦点个赞 ~ ~ 编程练习4 - 数组Ⅰ&#xff08;1~7&#xff09; 7-1 评委打分&#xff08;分数 10&#xff09;7-2 组合数的和&#xff08;分数 10&#xff09;7-3 找不同&#xff08;分数 15&#xff09;7-4 利用二分查找…

Linux shell编程学习笔记33:type 命令

目录 0 引言1 type 命令的功能和格式 1.1 type命令的功能1.2 type 命令的格式2 type命令用法实例 2.1用type命令查看shell内置命令&#xff08;以echo命令为例&#xff09;2.2 用type命令查看别名&#xff08;以ls命令为例&#xff09;2.3 用type命令同时查看shell内置命令和别…

Kubernetes实战(八)-防止k8s namespace被误删除

1 背景 运维新同学在预发环境操作删除pod的时候&#xff0c;不知道什么原因把kubectl delete pod命令敲成了kubectl delete ns pre把预发环境删了&#xff0c;几十个模块&#xff0c;将近一个小时才恢复。幸亏是测试环境啊&#xff0c;如果是生产可以可以跑路了。 2 解决方案…

Redis核心知识点总结

1.Redis介绍 Redis 是 NoSQL&#xff0c;但是可处理 1 秒 10w 的并发&#xff08;数据都在内存中&#xff09; 使用 java 对 redis 进行操作类似 jdbc 接口标准对 mysql&#xff0c;有各类实现他的实现类&#xff0c;我们常用的是 druid 其中对 redis&#xff0c;我们通常用 J…

深信服技术认证“SCSA-S”划重点:XSS漏洞

为帮助大家更加系统化地学习网络安全知识&#xff0c;以及更高效地通过深信服安全服务认证工程师考核&#xff0c;深信服特别推出“SCSA-S认证备考秘笈”共十期内容&#xff0c;“考试重点”内容框架&#xff0c;帮助大家快速get重点知识~ 划重点来啦 *点击图片放大展示 深信服…

彻底解决公网ip无法访问服务器的问题

用服务器的公网ip访问突然提示页面无法访问了&#xff0c;之前还是ok的&#xff1a; 解决方案&#xff1a; 步骤1. 检查云服务器的安全组规则是否有添加80端口映射&#xff0c;如果没有需要手动添加&#xff0c;否则不能使用公网访问&#xff0c;检查了一下是有的&#xff1…

STM32 定时器总结

缩写 ARR: Auto-Reload Register&#xff08;保存定时器的计数范围&#xff09; PSC: Prescaler register&#xff08;预分频器寄存器&#xff0c;根据设置的分频因子N&#xff0c;计数N个定时器时钟脉冲后&#xff0c;产生一个CNT计数&#xff0c;以此实现分频功能&#xff0…

Python中函数添加超时时间,Python中signal使用

from time import time, sleepimport signal# 模拟要删除5条数据,中间有超时的i 5# 超时后执行的方法def timeout_handler(signal, frame):# 引发异常raise TimeoutError("删除第" str(i) "条,超时!")# 或者执行其他操作,不往外抛异常(超时的函数不会被…

nodejs+vue+微信小程序+python+PHP的Sd球鞋销售平台的设计与实现-计算机毕业设计推荐

此网站系统的开发方式和信息管理方式&#xff0c;借鉴前人设计的信息和研发。以网站商品信息为主&#xff0c;购物商品为核心功能来进行设计和研发&#xff0c;把网站信息和技术整合&#xff0c;开发出一套Sd球鞋销售平台。用目前现有的新技术进行系统开发&#xff0c;   目…

外包干了3个月,技术倒退2年。。。

先说情况&#xff0c;大专毕业&#xff0c;18年通过校招进入湖南某软件公司&#xff0c;干了接近6年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能测试&#xf…

淘宝用户体验VOC标签体系

本专题共10篇内容&#xff0c;包含淘宝APP基础链路过去一年在用户体验数据科学领域&#xff08;包括商详、物流、性能、消息、客服、旅程等&#xff09;一些探索和实践经验。 在商详页基于用户动线和VOC挖掘用户决策因子带来浏览体验提升&#xff1b;在物流侧洞察用户求助时间与…

UI自动化Selenium 数据驱动读取Excel

selenium 自动化&#xff0c;希望通过Excel进行数据驱动&#xff1b; 即代码自动读取并循环所有数据&#xff1b; 如下为Excel读取封装的函数 # Excel数据读取 def ExcelRead(filename, sheetname):# current_path os.getcwd()current_path "D:\WORK\自动化\pythonsel…

反序列化 [网鼎杯 2020 朱雀组]phpweb 1

打开题目 我们发现这个页面一直在不断的刷新 我们bp抓包一下看看 我们发现index.php用post方式传了两个参数上去&#xff0c;func和p 我们需要猜测func和p两个参数之间的关系&#xff0c;可以用php函数MD5测一下看看 我们在响应处得到了一串密文&#xff0c;md5解密一下看看 发…

深入理解Sentinel系列-1.初识Sentinel

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱吃芝士的土豆倪&#xff0c;24届校招生Java选手&#xff0c;很高兴认识大家&#x1f4d5;系列专栏&#xff1a;Spring源码、JUC源码、Kafka原理、分布式技术原理&#x1f525;如果感觉博主的文章还不错的话&#xff…

2.postman环境变量及接口关联

一、环境变量以及全局变量 操作流程 1.点击environment 2.点击environment右侧号&#xff0c;新增环境变量 3.在变量中输入变量名以及变量值 4.回到collection页面&#xff0c;修改变量环境 5.在collection中通过{{变量名}}调用变量 变量定义 环境变量&#xff1a;环境变量…