arraylist能否接收强转类型_ArrayList 源码解析

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

技术干货每日送达!

前言

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

类图

d054bc0760b227ae5b5ecd9ca7db5289.png
  • 实现了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 ,就创建一个新的长度是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;
// 最后一个要返回元素的下标 没有元素返回 -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 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的这句话有毛病?

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

测试类代码:

840377ca98e5ba444b05b4d8aef20faf.png

一顿javacjavap之后(使用JDK8):

630d4f95422bc55dc6910e44d05b4b46.png

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

ef9df85a75e2a68dbc6574993ad85979.png

虽然字节码指令我还看不太懂,但是我能品出来,注释是没毛病的,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内存泄露,排查竟这么难!


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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mc有什么红石机器人_我的世界10月考试!来测测你的MC成绩吧~

考试规则&#xff1a;本次考试为闭卷考试&#xff0c;考生需要在30分钟内完成试卷。试卷总分为100分&#xff0c;其中包括单项选择题50分&#xff0c;多项选择题20分&#xff0c;判断题30分。考试内容包括《我的世界》手游1.11.0及以上版本在不添加任何模组的情况下的所有游戏内…

自定义分页 html,MVC 自定义HtmlHelper帮助类型之分页

方法一&#xff1a;在项目中增加App_Code文件夹&#xff0c;新增一个MyHtmlper.cshtml视图文件写入代码&#xff1a;helper Pagger(int pageIndex, int pageCount){for (int i 1; i < pageCount; i){if (i ! pageIndex){(i)}else{i}}}新增一个HomeControllerpublic class H…

js console 输出到文件_Node.js核心入门

正文核心模块是Node.js的心脏&#xff0c;主要是有一些精简高效的库组成(这方面和Python有很大的相似之处)&#xff0c;为Node.js提供了基础的API。主要内容包括&#xff1a;Node.js核心入门(一)全局对象常用工具事件机制Node.js核心入门(二)文件系统访问HTTP服务器与客户端全局…

eclipse中项目内存溢出问题

2019独角兽企业重金招聘Python工程师标准>>> SpringBoot项目热启动Perm区内存溢出。 Failed to instantiate [org.springframework.orm.jpa.JpaVendorAdapter]: Factory method jpaVendorAdapter threw exception; nested exception is java.lang.OutOfMemoryErro…

express rest_Express / Node中用于REST API的邮递员工具

express restWhen dealing with routes (like in express), we may use any of the REST verbs and at times, the browser is limited to facilitate testing the routes/REST API. 在处理路由时(如快速表达)&#xff0c;我们可以使用任何REST动词&#xff0c;有时浏览器会受到…

形象易懂讲解算法I——小波变换

https://zhuanlan.zhihu.com/p/22450818?referdong5 最早发于回答&#xff1a;能不能通俗的讲解下傅立叶分析和小波分析之间的关系&#xff1f; - 咚懂咚懂咚的回答现收入专栏。从傅里叶变换到小波变换&#xff0c;并不是一个完全抽象的东西&#xff0c;可以讲得很形象。小波变…

使用Linux命令行归档文件

存档文件 (Archiving Files) As we already understand what Compression (Compression techniques in Linux) is? We shall learn about Archives. We prefer compression as it is convenient to send file compressed through a network but sometimes it is not a smart w…

http缓存机制之304状态码

在网上看到一篇关于解释浏览器缓存更新机制304状态码的文章&#xff0c;里面说如果请求头中的If-Modified-Since字段和If-None-Match字段的值分别和响应头中的Last-Modified字段和Etag字段值一致&#xff0c;服务器就会返回304状态码(无响应体)&#xff0c;浏览器就从本地读取缓…

计算机选配 注意事项,选择鼠标注意事项有哪些

选择鼠标注意事项有哪些每台电脑旁边都有了一个忠实的伴侣&#xff0c;那就是“Mouse”--鼠标。选择鼠标最重要的一点就是质量&#xff0c;无论它的功能有多强大、外形多漂亮&#xff0c;如果质量不好那么一切都不用考虑了。那么&#xff0c;选择鼠标注意事项有哪些?笔记本鼠标…

js 验证护照_护照本地策略第2部分| Node.js

js 验证护照In my last article (Passport local strategy section 1 | Node.js), we started the implementation of the passport-local authentication strategy. We also looked at the various requirements to get started with the login form. In this article, we wil…

svn版利用什么技术实现_金葱粉涂料印花利用了什么技术?

金葱粉涂料印花利用了什么技术:金葱粉用涂料而不是用染料来生产印花布已经非常广泛&#xff0c;以致开始把它当作一种独立的印花方式。涂料印花是用涂料直接印花&#xff0c;该工艺通常叫做干法印花&#xff0c;以区别于湿法印花(或染料印花)。通过比较同一块织物上印花部位和未…

网站换服务器需要注意什么问题,网站更换服务器要注意哪些事项

原标题&#xff1a;网站更换服务器要注意哪些事项网站在运营的过程中&#xff0c;出于某种考虑&#xff0c;我们会将网站进行服务器的变更&#xff0c;那么在进行服务器变成过程中&#xff0c;需要注意哪些事项。一、如果是跨服务商更换网站服务器&#xff0c;需要做备案迁移。…

kafka分区与分组原理_大数据技术-Kafka入门

在大数据学习当中&#xff0c;主要的学习重点就是大数据技术框架&#xff0c;针对于大数据处理的不同环节&#xff0c;需要不同的技术框架来解决问题。以Kafka来说&#xff0c;主要就是针对于实时消息处理&#xff0c;在大数据平台当中的应用也很广泛。大数据学习一般都有哪些内…

ActiveReports 报表控件官方中文新手教程 (1)-安装、激活以及产品资源

&#xfeff;&#xfeff;本系列文章主要是面向初次接触 ActiveReports 产品的用户&#xff0c;能够帮助您在三天之内轻松的掌握ActiveReports控件的基本用法&#xff0c;包含安装、激活、创建报表、绑定数据源以及公布等内容。本篇文章我们就从安装产品開始带您开启轻松的 Ac…

如何在React Native中使用React JS Hooks?

In my articles, Im going to be using either expo or snack online IDE and android emulator. 在我的文章中&#xff0c;我将使用expo或点心在线IDE和android模拟器。 React Hooks is simply an awesome tool that helps us use states and other react features without w…

华为P40pro 手机云台_2020年目前拍照最好的手机推荐!华为P40 Pro!DXO全球榜首

目前最热门的拍照手机自然是华为P40 Pro&#xff0c;其相机性能直接问鼎DXOMARK手机相机评分榜首。对于拍照要极求高的用户&#xff0c;华为P40 Pro将是一个非常不错的选择。那么&#xff0c;华为P40 Pro除了出色的相机之外&#xff0c;其它方面表现如何呢&#xff1f;下面&…

Centos 7安装与配置nagios监控(一)

目 录序言(必备知识)一、安装规划1.1系统环境1.2所需软件包二、配置安装环境2.1同步时间2.2禁用SElinux2.3 xftp上传软件包2.4安装邮件服务三、监控主机安装3.1安装nagios的运行环境3.2增加用户3.3安装nagios3.4配置权限3.5安装插件3.6安装nrpe四、远程主机安装4.1配置运行环境…

备份linux系统报错_Linux 系统如何快速入门?分享民工哥总结的经验

大家好&#xff0c;我是民工哥。认识或熟悉我的人都知道&#xff0c;是做运维出身的&#xff0c;所以&#xff0c;很多时候&#xff0c;有很多朋友喜欢问我一些有关运维的问题&#xff0c;比如&#xff1a;我应该如何入门Linux系统运维&#xff1f;Linux系统运维到底需要学哪些…

pe联想服务器装系统教程视频,演示联想电脑u盘重装系统xp教程

联想电脑U盘重装XP系统的方法很多朋友询问&#xff0c;其实现在很多电脑已经不支持XP系统的安装了&#xff0c;如果你的联想电脑是近几年购买的&#xff0c;还是安装win10系统比较保险。当然联想电脑安装系统过程中遇到问题也可以联系人工客服。联想电脑如何使用U盘重装系统XP呢…

springboot公共模块打包_解决SpringBoot多模块发布时99%的问题?

每天都会分享Java架构文章&#xff0c;喜欢的朋友关注我。ps&#xff1a;文末有彩蛋&#xff0c;惊喜等着你如果使用的是 SpringBoot 多模块的项目&#xff0c;在发布的时候可能遇到各种各样的问题。本文归纳了以下 8 个原则和发布时经常出现的 4 个问题的解决方案&#xff0c;…