Interview系列 - 06 Java | ArrayList底层源码分析 | 遍历集合时如何删除集合中的元素

news/2024/3/29 19:47:31/文章来源:https://blog.csdn.net/qq_42764468/article/details/129172485

文章目录

    • 1. 底层源码分析
      • 01. 属性
      • 02. 构造方法
      • 03. 在数组的末尾添加元素 add(E e)
      • 04. 在数组的指定位置添加元素 add(int index, E element)
      • 05. 替换指定位置的元素 set(int index, E element)
      • 06. 获取指定索引位置处的元素 get(int index)
      • 07. 删除指定位置的元素 remove(int index)
      • 08. 把集合所有数据转换成字符串 toString()
      • 09. 迭代器 iterator() 方法
    • 2. 如何遍历集合并删除List中的元素?
      • 01. 普通 for 循环删除(不可靠)
      • 02. 普通 for 循环提取变量删除(抛异常)
      • 03. 普通 for 循环倒序删除(可靠)
      • 04. 增强 for 循环删除(抛异常)
      • 05. 迭代器循环迭代器删除(可靠)
      • 06. 迭代器循环集合删除(抛异常)
      • 07. 集合 forEach 方法循环删除(抛异常)

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

(1) ArrayList底层的数据结构为动态数组,数组一旦初始化长度就不可以发生改变,而ArrayList是可调整大小的数组实现。

(2) ArrayList不是线程安全的,只能用在单线程环境下,多线程环境下可以考虑用Collections.synchronizedList(List l)函数返回一个线程安全的ArrayList类,也可以使用concurrent并发包下的CopyOnWriteArrayList类。

(3) ArrayList实现了Serializable接口,因此它支持序列化,能够通过序列化传输,实现了RandomAccess接口,支持快速随机访问,实际上就是通过下标序号进行快速访问,实现了Cloneable接口,能被克隆。

增删慢:每次删除元素,都需要更改数组长度、拷贝以及移动元素位置。
查询快:由于数组在内存中是一块连续空间,因此可以根据地址+索引的方式快速获取对应位置上的元素。

1. 底层源码分析

01. 属性

public class ArrayList<E> extends AbstractList<E>implements List<E>, RandomAccess, Cloneable, java.io.Serializable{private static final long serialVersionUID = 8683452581122892189L;// 在创建数组时,若没有指明数组的长度,则默认初始化容量为10private static final int DEFAULT_CAPACITY = 10;// 空数组,使用带参构造方法new ArrayList(0)时使用:elementData = EMPTY_ELEMENTDATAprivate static final Object[] EMPTY_ELEMENTDATA = {};// 空数组,使用空参构造方法new ArrayList()时使用:elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA// 与EMPTY_ELEMENTDATA的区别:添加第一个元素时会初始化为默认容量(DEFAULT_CAPACITY)大小private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};// 真正存放元素的地方,数组的容量就是该数组的长度transient Object[] elementData; // 真正存放元素的个数private int size;
}

02. 构造方法

空参构造方法:

// 空参构造器:构造一个初始化容量为10的空数组 
// 不传初始容量,初始化为DEFAULTCAPACITY_EMPTY_ELEMENTDATA空数组,该数组会在添加第一个元素的时候扩容为默认的大小10
public ArrayList() {this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

带参构造方法:

// 构造一个指定容量大小的数组
// 如果传入的容量为0,则 elementData = EMPTY_ELEMENTDATA
// 如果传入的容量大于0,则 elementData = new Object[initialCapacity]
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,这里会使用拷贝把传入集合的元素拷贝到elementData数组中// 如果元素个数为0,则初始化为EMPTY_ELEMENTDATA空数组。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;}
}

03. 在数组的末尾添加元素 add(E e)

在这里插入图片描述

public boolean add(E e) {// 确保数组能否存放添加的元素ensureCapacityInternal(size + 1);  // Increments modCount!!elementData[size++] = e;return true;
}

① 确保数组的长度加1后内存是充足的,就是说保证能够数组还能存放一个元素

private void ensureCapacityInternal(int minCapacity) {ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

② 计算添加元素后所需的最小容量

private static int calculateCapacity(Object[] elementData, int minCapacity) {// 如果调用的是空参数构造函数,则第一次调用add()方法时会比较默认初识容量10和所需最小容量的大小if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {return Math.max(DEFAULT_CAPACITY, minCapacity);}// 返回添加元素后所需的最小容量return minCapacity;
}

③ 确保数组有足够的容量添加元素

private void ensureExplicitCapacity(int minCapacity) {// 集合的快速失败机制modCount++;// 如果数组的容量小于所需的最小容量,则扩容// 使得存在剩余的内存存放要添加的元素if (elementData.length < minCapacity)grow(minCapacity);
}

④ 增加容量,以确保它至少可以容纳最小容量参数指定的元素数。

private void grow(int minCapacity) {// 获取当前数组的容量int oldCapacity = elementData.length;// 先将数组容量扩容1.5倍int newCapacity = oldCapacity + (oldCapacity >> 1);// 如果扩容后还不满足需求,则直接扩容到数组添加元素时所需的最小容量// 如果调用空参数构造方法,则这里会直接将数组容量扩容到默认初识容量10,因为此时最小容量为10// 如果调用的带参构造方法,会将容量扩容为原来的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指向这个新数组// 原来的旧的数组由于没有引用变量指向他,就会被垃圾回收机制回收掉elementData = Arrays.copyOf(elementData, newCapacity);
}

在这里插入图片描述

在扩容时会申请一块更大内存容量的数组,将原来数组中的元素拷贝到这个新的数组中,同时让elementData指向该数组,而原来的数组由于没有新的指针指向它,就会被垃圾回收机制回收。

在扩容时,首先考虑扩容1.5倍(如果扩容的太大,可能会浪费更多的内存,如果扩容的太小,又会导致频繁扩容,申请数组拷贝元素等对添加元素的性能消耗较大,因此1.5倍刚好)。如果扩容1.5倍还是内存不足,就会直接扩容到添加元素所需的最小的容量。同时不能超过数组的最大容量Integer.MAX_VALUE - 8。

面试题:ArrayList是如何扩容的?

答:如果调用的是空参数构造方法,第一次调用add方法添加元素时会将容量扩容10,以后每次都是原容量的1.5倍。如果调用的带参构造方法,在容量不满足时,会将容量扩容为原来的1.5倍。

面试题:ArrayList频繁扩容导致添加性能急剧下降,如何处理?

答:创建集合的时候指定足够大的容量。

04. 在数组的指定位置添加元素 add(int index, E element)

在此列表中的指定位置插入指定元素。将当前位于该位置的元素(如果有)和任何后续元素向右移动(将一个元素添加到其索引中)

public void add(int index, E element) {// 判断索引是否越界rangeCheckForAdd(index);// 确保添加元素时容量充足ensureCapacityInternal(size + 1);  // Increments modCount!!// 源数组 elementData 从传入形参index处开始复制,复制size-index个元素// 目标数组 elementData 从index+1处开始粘贴,粘贴从源数组赋值的元素System.arraycopy(elementData, index, elementData, index + 1,size - index);// /把index处的元素替换成新的元素。elementData[index] = element;size++;
}

① 判断索引是否越界:

private void rangeCheckForAdd(int index) {if (index > size || index < 0)throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
public class IndexOutOfBoundsException extends RuntimeException {private static final long serialVersionUID = 234122996006267687L;// 使用super关键字调用父类的空参数构造方法public IndexOutOfBoundsException() {super();}// 使用super关键字调用父类的带参构造方法public IndexOutOfBoundsException(String s) {super(s);}
}

② 确保数组的容量充足,至少保证能再装下一个元素。

③ 数组时一片连续的内存空间,如果要在指定的位置插入元素,需要移动数组中的元素:

在这里插入图片描述

elementData数组中的元素,从要插入的位置index开始,将index索引元素及其后面的元素向后移动一个位置,给要插入的元素腾出一个位置,将该元素插入到index位置处。

05. 替换指定位置的元素 set(int index, E element)

用指定的元素替换此列表中指定位置的元素。

public E set(int index, E element) {// 判断索引是否越界rangeCheck(index);// 获取指定索引处的元素E oldValue = elementData(index);// 用指定的元素替换指定索引的元素elementData[index] = element;// 返回旧值return oldValue;
}

06. 获取指定索引位置处的元素 get(int index)

因为数组的内存是连续的,因此可以根据索引直接获取元素。

public E get(int index) {// 判断索引是否越界rangeCheck(index);// 返回指定索引处的元素return elementData(index);
}

07. 删除指定位置的元素 remove(int index)

删除该列表中指定位置的元素,将所有后续元素向前移动

public E remove(int index) {// 判断索引是否越界rangeCheck(index);// 集合的快速失败机制modCount++;// 获取指定索引处的元素E oldValue = elementData(index);int numMoved = size - index - 1;// 源数组 elementData 从传入形参index+1处开始复制,复制size-index-1个元素// 目标数组 elementData 从index处开始粘贴,粘贴从源数组赋值的元素if (numMoved > 0)System.arraycopy(elementData, index+1, elementData, index,numMoved);// 将最后一个元素置为null,再将元素size-1elementData[--size] = null; // clear to let GC do its workreturn oldValue;
}

在这里插入图片描述

是将elementData数组中的元素,从要删除的元素的后面一个位置开始到末尾的元素结束都向前移动一个位置,然后将最后一个元素置为null,再将元素size-1。

08. 把集合所有数据转换成字符串 toString()

返回此集合的字符串表示形式。字符串形式由集合元素的列表组成,这些元素按迭代器返回的顺序排列,用方括号(“[]”)括起来。相邻元素由字符“,”(逗号和空格)分隔。元素通过String.valueOf(Object)转换为字符串。

public String toString() {// 获取遍历集合的迭代器Iterator<E> it = iterator();// 判断集合中是否有元素,如果没有则返回:[]if (! it.hasNext())return "[]";// 创建 StringBuilder 拼接字符串StringBuilder sb = new StringBuilder();// 先拼接一个:[sb.append('[');for (;;) {// 指针向下移动并返回迭代器指针指向的元素E e = it.next();// 拼接元素sb.append(e == this ? "(this Collection)" : e);// 如果集合中没有了元素,则追加:]if (! it.hasNext())return sb.append(']').toString();// 元素之间拼接一个逗号和空格sb.append(',').append(' ');}
}

该方法调用的是AbstractCollection抽象类中的方法:

在这里插入图片描述

09. 迭代器 iterator() 方法

按正确顺序返回此列表中元素的迭代器,返回的迭代器快速失败。

public Iterator<E> iterator() {return new Itr();
}
private class Itr implements Iterator<E> {// 下一个返回元素的索引int cursor;   // 最后一个返回元素的索引int lastRet = -1;  // 将集合实际修改次数赋值给预期修改次数:调用add()方法,remove()方法时modCount都会加1// 在迭代的过程中,只要实际修改次数和预期修改次数不一致就会产生并发修改异常int expectedModCount = modCount;Itr() {}public boolean hasNext() {return cursor != size;}// 调用next() 方法时会先返回光标处的元素,然后将光标向下移动@SuppressWarnings("unchecked")public E next() {// 集合迭代器的快速失败机制checkForComodification();int i = cursor;// 将光标赋值给iif (i >= size)throw new NoSuchElementException();Object[] elementData = ArrayList.this.elementData;if (i >= elementData.length)throw new ConcurrentModificationException();// 光标向下移动cursor = i + 1;// 返回索引i位置的元素// 将最后一个返回的元素索引lastRet设置为ireturn (E) elementData[lastRet = i];}public void remove() {if (lastRet < 0)throw new IllegalStateException();// 集合迭代器的快速失败机制checkForComodification();try {// 移除调用next()方法获取的元素ArrayList.this.remove(lastRet);// 将光标指向删除元素处cursor = lastRet;lastRet = -1;// 将集合实际修改次数赋值给预期修改次数// 因此在迭代的过程中调用迭代器的remove()方法不会抛出异常// 而调用集合的remove()方法会抛出ConcurrentModificationException异常expectedModCount = modCount;} catch (IndexOutOfBoundsException ex) {throw new ConcurrentModificationException();}}final void checkForComodification() {if (modCount != expectedModCount)throw new ConcurrentModificationException();}
}

2. 如何遍历集合并删除List中的元素?

问题主要在于remove(int index)方法的实现:删除该列表中指定位置的元素,会将其所有后续元素向前移动,然后将最后一个元素的值置为null

public E remove(int index) {// 判断索引是否越界rangeCheck(index);// 集合的快速失败机制modCount++;// 获取指定索引处的元素E oldValue = elementData(index);int numMoved = size - index - 1;// 源数组 elementData 从传入形参index+1处开始复制,复制size-index-1个元素// 目标数组 elementData 从index处开始粘贴,粘贴从源数组赋值的元素if (numMoved > 0)System.arraycopy(elementData, index+1, elementData, index,numMoved);// 将最后一个元素置为null,再将元素size-1elementData[--size] = null; // clear to let GC do its workreturn oldValue;
}

01. 普通 for 循环删除(不可靠)

public class Main {public static void main(String[] args) {List<String> list = Arrays.asList("张三", "李四", "周一", "刘四", "李强", "李白");for (int i = 0; i < list.size(); i++) {String str = list.get(i);if (str.startsWith("李")) {list.remove(i);}}System.out.println(list);}
}

在这里插入图片描述

我们发现李白没有删掉,因为删除列表中指定位置的元素,会将其所有后续元素向前移动,最后一个元素的值置为null,数组的实际大小size在减小,因此李白没有删除掉。

02. 普通 for 循环提取变量删除(抛异常)

public class Main {public static void main(String[] args) {List<String> initList = Arrays.asList("张三", "李四", "周一", "刘四", "李强", "李白");List<String> list = new ArrayList(initList);int size = list.size();for (int i = 0; i < size; i++) {String str = list.get(i);if (str.startsWith("李")) {list.remove(i);}}System.out.println(list);}
}

在这里插入图片描述

抛出下标越界异常,因为size 变量是固定的,但 list 的实际大小是不断减小的,而 i 的大小是不断累加的,一旦 i >= list 的实际大小肯定就异常了。

03. 普通 for 循环倒序删除(可靠)

public class Main {public static void main(String[] args) {List<String> initList = Arrays.asList("张三", "李四", "周一", "刘四", "李强", "李白");List<String> list = new ArrayList(initList);for (int i = list.size() - 1; i > 0; i--) {String str = list.get(i);if (str.startsWith("李")) {list.remove(i);}}System.out.println(list);}
}

输出正确,从数组的最后一个元素开始删除,就不会出现问题,可以再看下remove()方法的源码。

04. 增强 for 循环删除(抛异常)

public class Main {public static void main(String[] args) {List<String> initList = Arrays.asList("张三", "李四", "周一", "刘四", "李强", "李白");List<String> list = new ArrayList(initList);for (String element : list) {if (element.startsWith("李")) {list.remove(element);}}System.out.println(list);}
}

在这里插入图片描述
这个是集合操作中很常见的异常之一,即并发修改异常!

其实,for(xx in xx) 就是增强的 for循环,即迭代器 Iterator 的加强实现,其内部是调用的 Iterator 的方法。使用迭代器进行遍历集合时,除了通过迭代器自身的 remove() 方法之外,对集合进行任何其他方式的结构性修改,则会抛出ConcurrentModificationException异常。

每次迭代器使用 next() 方法获取下个元素的时候都会去判断要修改的数量(modCount)和期待修改的数量(expectedModCount)是否一致,不一致则会报错,而 ArrayList 中的 remove 方法并没有同步期待修改的数量(expectedModCount)值,所以会抛异常了。

原理可以看ArrayList源码:remove() 方法,add() 方法,iterator() 方法

05. 迭代器循环迭代器删除(可靠)

public class Main {public static void main(String[] args) {List<String> initList = Arrays.asList("张三", "李四", "周一", "刘四", "李强", "李白");List<String> list = new ArrayList(initList);for (Iterator<String> iterator = list.iterator(); iterator.hasNext(); ) {String str = iterator.next();if (str.contains("李")) {iterator.remove();}}System.out.println(list);}
}

结果输出正常,这是因为迭代器中的 remove 方法将期待修改的数量(expectedModCount)值进行了同步。

06. 迭代器循环集合删除(抛异常)

public class Main {public static void main(String[] args) {List<String> initList = Arrays.asList("张三", "李四", "周一", "刘四", "李强", "李白");List<String> list = new ArrayList(initList);for (Iterator<String> ite = list.iterator(); ite.hasNext(); ) {String str = ite.next();if (str.contains("李")) {list.remove(str);}}System.out.println(list);}
}

在这里插入图片描述
又是那个并发修改异常,这个示例虽然使用了 Iterator 循环,但删除的时候却使用了 list.remove 方法,同样是有问题的。

07. 集合 forEach 方法循环删除(抛异常)

public class Main {public static void main(String[] args) {List<String> initList = Arrays.asList("张三", "李四", "周一", "刘四", "李强", "李白");List<String> list = new ArrayList(initList);list.forEach((e) -> {if (e.contains("李")) {list.remove(e);}});System.out.println(list);}
}

在这里插入图片描述
forEach 方法的背后其实就是增强的 for 循环,底层即迭代器,所以使用 list.remove 同样抛出 ConcurrentModificationException 异常。

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

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

相关文章

华为OD机试真题 用 C++ 实现 - 众数和中位数 | 多看题,提高通过率

最近更新的博客 华为OD机试 - 入栈出栈(C++) | 附带编码思路 【2023】 华为OD机试 - 箱子之形摆放(C++) | 附带编码思路 【2023】 华为OD机试 - 简易内存池 2(C++) | 附带编码思路 【2023】 华为OD机试 - 第 N 个排列(C++) | 附带编码思路 【2023】 华为OD机试 - 考古…

SpringCloud - Feign远程调用

目录 Feign的远程调用 RestTemplate方式调用存在的问题 介绍与初步使用 Feign的自定义配置 Feign运行自定义配置来覆盖默认配置&#xff0c;可以修改的配置如下&#xff1a; 配置Feign日志有两种方式&#xff1a; Feign性能优化 Feign底层的客户端实现&#xff1a; 连…

AI作画—中国画之山水画

山水画&#xff0c;简称“山水”&#xff0c;中国画的一种&#xff0c;描写山川自然景色为主体的绘画。山水画在我国绘画史中占有重要的地位。 山水画形成于魏晋南北朝时期&#xff0c;但尚未从人物画中完全分离。隋唐时始终独立&#xff0c;五代、北宋时趋于成熟&#xff0c;…

Linux多媒体子系统01:从用户空间使用V4L2子系统

1 V4L2应用编程基础1.1 概述V4L2应用编程需要使用如下系统调用&#xff0c;open(): 打开V4L2设备 close(): 关闭V4L2设备 ioctl(): 向V4L2设备驱动程序发送控制命令 mmap(): 将V4L2设备驱动程序分配的缓冲区内存映射到用户空间 read()或write(): 这2个系统调用是否支持取决于流…

领导催我优化SQL语句,我求助了ChatGPT。这是ChatGPT给出的建议,你们觉得靠谱吗

作为一个程序员&#xff0c;无论在面试还是工作中&#xff0c;优化SQL都是绕不过去的难题。 为啥&#xff1f;工作之后才会明白&#xff0c;随着公司的业务量增多&#xff0c;SQL的执行效率对程系统运行效率的影响逐渐增大&#xff0c;相对于改造代码&#xff0c;优化SQL语句是…

LeetCode-93. 复原 IP 地址

目录题目思路回溯法题目来源 93. 复原 IP 地址 题目思路 意识到这是切割问题&#xff0c;切割问题就可以使用回溯搜索法把所有可能性搜出来&#xff0c;和131.分割回文串就十分类似了。 回溯法 1.递归参数 startIndex一定是需要的&#xff0c;因为不能重复分割&#xff0c…

实战:手把手教你colossal-AI复现Chatgpt的流程

相信很多人都看了使用colossal-AI复现Chatgpt的流程的文章&#xff0c;但实际上看过了&#xff0c;不免有人发出“说得贼明白&#xff0c;就是自己做不出来”的感叹吧。本人公开一下实战过程&#xff0c;给有兴趣复现chatgpt流程的朋友一个参考。 一、环境搭建&#xff1a; 1…

ES6-ES11基本全部语法

在进入es6语法之前&#xff0c;先走一波es5遍历迭代Api&#xff0c;&#xff0c;它们的作用&#xff0c;应用场景&#xff0c;参数&#xff0c;以及返回值分别是什么。&#xff08;forEach、map、some、every、filter&#xff09;我们统一设定一个初始数组&#xff1a;let arra…

【likeshop多商户】电子面单商家直播上线啦~

likeshop多商户商城v2.2.0版本更新啦&#xff01; 新增功能&#xff1a; 商家直播 单子面单 优化&#xff1a; 个人中心优惠券数量统计优化 修复&#xff1a; 秒杀商品待审核时&#xff0c;下单价格计算错误 个人中心修改头像后地址保存错误 「商家直播」 提升品牌知名度…

华为OD机试真题 用 C++ 实现 - 子序列长度 | 多看题,提高通过率

最近更新的博客 华为OD机试 - 入栈出栈(C++) | 附带编码思路 【2023】 华为OD机试 - 箱子之形摆放(C++) | 附带编码思路 【2023】 华为OD机试 - 简易内存池 2(C++) | 附带编码思路 【2023】 华为OD机试 - 第 N 个排列(C++) | 附带编码思路 【2023】 华为OD机试 - 考古…

2-并发篇

线程有哪些状态 java的线程状态有6种&#xff1a; 操作系统中有5状态的说明 注意java的runnable对应了就绪、运行、阻塞I/O 线程池的核心参数 主要是说线程池的一个实习类 threadPoolExecutor.class 1.corePoolSize 核心线程数据&#xff08;可以为0&#xff09; 最多保…

JavaTCP通信程序

3 TCP通信程序 3.1 TCP通信原理 TCP通信协议是一种可靠的网络协议&#xff0c; 它在通信的两端名建立一个Socke对象&#xff0c; 从而在通信的两端形成网络虚拟链路一旦建立了 虚拟的网络链路&#xff0c;两端的程序就可以通过虚拟链路进行通信Java对基于TCP协议的的网络提供…

Python-生成列表

1.生成列表使用列表前必须先生成列表。1.1使用运算符[ ]生成列表在运算符[ ]中以逗号隔开各个元素会生成包含这些元素的新列表。另外&#xff0c;如果[ ]中没有元素就会生成空列表示例>>> list01 [] >>> list01 [] >>> list02 [1, 2, 3] >>…

LeetCode 206. 反转链表

LeetCode 206. 反转链表 难度&#xff1a;easy\color{Green}{easy}easy 题目描述 给你单链表的头节点 headheadhead &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[5,4,3,2,1]示例 2&a…

Java Stream、File、IO 超详细整理,适合新手入门

目录 Java Stream Java File Java IO Java Stream Java Stream 是 Java 8 中引入的一种新的抽象数据类型&#xff0c;它允许开发人员使用函数式编程的方式来处理集合数据。 使用 Java Stream 可以方便地进行过滤、映射、排序和聚合等操作。下面是一个简单的示例&#xff1a;…

BatchNorm与LayerNorm的比较

Batch Normalization存在的一些问题 &#xff08;1&#xff09;BN在mini-batch较小的情况下不太适用 BN是对整个mini-batch的样本统计均值和方差 当训练样本数很少时&#xff0c;样本的均值和方差不能反映全局的统计分布信息&#xff0c;从而导致效果下降 &#xff08;2&am…

IM即时通讯构建企业协同生态链

在当今互联网信息飞速发展的时代&#xff0c;随着企业对协同办公要求的提高&#xff0c;协同办公的定义提升到了智能化办公的范畴。大多企业都非常重视构建连接用户、员工和合作伙伴的生态平台&#xff0c;利用即时通讯软件解决企业内部的工作沟通、信息传递和知识共享等问题。…

【NestJS】JWT 鉴权

Passport 是一个 NodeJS 鉴权库 JWT 认证的交互流程&#xff1a;浏览器发起请求&#xff0c;服务端对用户名和密码进行验证。如果身份验证通过&#xff0c;服务端会基于用户信息生成 token 字符串&#xff0c;并将其响应给浏览器。浏览器会将 token 字符串存储起来。往后的每次…

vscode远程调试python

目的 注意&#xff1a;这里我们想要实现的是&#xff1a;用vscode 使用remote ssh打开project&#xff0c;然后直接在project里面进行debug&#xff0c;而不需要 在本地vscode目录打开一样的project。 假设大家已经会使用remote ssh打开远程服务器的代码了&#xff0c;那么只…

Photon Vectorized Engine 学习记录

Photon Hash Aggregation Vectorization Photon Hash Join 的向量化的要点是&#xff1a;使用开放地址法。步骤&#xff1a; 向量化计算 hash 值基于 hash 向量化计算 bucket 下标&#xff0c;得到 bucket index 向量基于 bucket index 向量中记录的下标找到 bucket&#xff…