当前位置: 首页 > news >正文

得物 一面

ArrayList 和 LinkedList 的区别是什么?

  • 实现原理

    • ArrayList 底层实现为一个 Object 类型的动态数组,内存连续。
    • LinkedList 底层实现为双向链表,基于节点(Node)。
  • 时间复杂度

    • ArrayList:尾部插入和删除 O(1)、其他位置插入和删除 O(n)、查找 O(1)
    • LinkedList:尾部和头部插入和删除 O(1)、其他位置插入和删除 O(n)、查找 O(n)

此外 ArrayList 添加元素时如果需要扩容,则需要复制原数组到更大的数组,操作的时间复杂度为 O(n)

ArrayList 的底层原理是什么?

ArrayList 底层实现是 Object 类型的动态数组实现的,其核心机制包括

  • 动态扩容:默认初始容量 10,当 ArrayList 容量不足时,会创建一个新的、更大的数组(默认扩容 1.5 倍),然后将原数组的数据复制到新数组。newCapacity = oldCapacity + (oldCapacity >> 1)
  • 缩容机制:ArrayList 不会自动缩容,需手动调用 trimToSize()插入操作

ArrayList 是线程安全的吗?

ArrayList 不是线程安全的,因为它的所有操作(如 add、remove)都没有同步控制。如果多个线程同时修改 ArrayList,可能会导致数据不一致或抛出 ConcurrentModificationException。

eg

线程A和B同时执行add():
线程A检查容量足够,准备插入位置index = size
线程B抢先插入数据,导致线程A写入时覆盖数据
最终size的值可能小于实际元素数量

为什么 ArrayList 不是线程安全的,具体来说是哪里不安全

首先来看源码,ArrayList 的 add 操作包含两个关键步骤

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // 检查并扩容
    elementData[size++] = e;           // 赋值并自增size
    return true;
}

在高并发情况下,ArrayList 会暴露出如下问题

  • size++ 非原子操作

    • 这个问题基本每次都会发生。因为 size++ 本身不为原子性操作。size 可分为三步:1️⃣ 获取 size 值,2️⃣ 将 size+1,3️⃣ 覆盖掉旧 size
      线程 A 和线程 B 拿到一样的 size 值同时完成了覆盖,就会导致实际上只 size++ 了一次,所以肯定和 add 的实际数量不同。
  • 数据覆盖

    • 假设 size=0,线程 A 将元素写入 test[0]后但是还未执行 size++,此时 B 线程也写入了 test[0],就会导致线程 A 的值被覆盖。然后 AB 线程先后执行 size++,但实际只写入了一个有效元素,但 size=2
  • 值覆盖

    • 线程 A 检查发现当前 size 为 9,无需扩容。线程写入 test[9],但未执行 size++。线程 B 进来也一样也发现不需要扩容,然后写入 test[9],导致数据覆盖。且 size 最终增加为 11,但 test[9]之后位置未被填充。
  • 数组越界

    • 线程 A 在扩容后 size=15,线程 B 未感知到扩容,尝试写入 test[10],导致 ArrayIndexoutofBoundsExceptio

ArrayList 和 LinkedList 的应用场景,什么时候该用哪个?

  • ArrayList

    • 高频按索引访问
    • 内存敏感(数组空间利用率高)
  • LinkedList

    • 频繁头部 / 尾部插入删除(如队列、栈)
    • 无需预知数据量大小(动态扩展无扩容开销)。

缓存雪崩、缓存击穿、缓存穿透是什么?对应的解决方案

📄((20250311173048-4wn721h ‘Redis三大件 穿透、雪崩、击穿’))

缓存雪崩

简单来说就是大量缓存在同一时间过期,或者 Redis 宕机,此时大量请求都会涌入数据库,造成数据库压力剧增

解决方法:

  • 可以给缓存额外加上一个随机的过期时间,保证数据不会在同一时间过期
  • 或者是可以用下面的逻辑过期时间或分布式锁的办法。但是基本上加一个随机过期时间就可以很便捷的解决这个问题。

缓存击穿

简单来说就是热点数据突然过期,导致大量请求涌入数据库

解决方法:

  • 分布式锁:控制一个线程去数据库获取资源。保证同一时间只有一个业务更新缓存,未获得锁的请求,要么等待锁释放后重新读取缓存,要么返回空值或默认值。

  • 逻辑过期时间:Redis 中热点数据不设置过期时间,转而在应用层面来设置一个过期时间。当应用层过期时,让新来的数据继续访问 Redis 中的旧数据,然后控制一个异步线程去数据库获取数据。

缓存穿透

简单来说就是访问一条不存在的数据,既然数据库中不存在这条数据,那么缓存中固然也不会存在。所以所有攻击就会被穿透到数据库。

解决方法:

  • 布隆过滤器:将数据库中存在的元素添加到布隆过滤器中,新来的请求先去过滤器里查找,不存在直接拒绝,存在再进行后续的查找
  • 缓存空值:即使查到的值为空,也缓存到 Redis 中(设置一个较短的过期时间)。
  • 非法请求限制:在 API 入口处进行初步的非法请求的检查,判断请求参数是否合理、请求字段是否存在等等。

HTTP1.1 怎么对请求做拆包,具体来说怎么拆的?

好的在说拆包之前,可以先看一看粘包问题。

什么是粘包问题呢?由于 TCP 是面向字节流的协议,数据没有明确的消息边界,多个应用层报文可能被合并为一个 TCP 包发送(粘包)。

客户端发送两个 HTTP 请求GET /a和GET /b,TCP 可能合并为一个数据包发送:[GET /aGET /b]

了解了粘包问题,我们就可以来看拆包,HTTP 拆包粘包问题的解决方案之一。

  • 固定长度模式

    • 原理:通过头部字段 Content-Length 明确制定消息体的字节长度

    • 流程

      • 客户端发送请求时,在 Header 中生命 Content-Length:1024(假设)
      • 服务端读取 Header 时,严格按照 Content-Length 读取后续字节流
      • 读取完指定长度后,视为请求结束,后续数据处理下一个请求
    • HTTP/1.1 200 OK
      Content-Length: 11
      Content-Type: text/plain
      
      Hello World
      
  • 分块传输编码(适用于长度不固定的情况)

    • 原理:将消息体分割为多个块,每块包含长度 + 数据,最后以空块结束标识

    • 流程

      • 客户端在 Header 中声明 kuaiTransfer-Encoding: chunked
      • 5\r\n        // 第一块长度(十六进制)
        Hello\r\n    // 数据
        6\r\n        // 第二块长度
        World!\r\n   // 数据
        0\r\n        // 结束块
        \r\n         // 结束符
        
      • 服务端按块长度逐块读取,直到遇到长度为 0 的块。

三次握手要实现什么目的

  • 阻止重复历史连接的初始化

    • eg:客户端先发送了 SYN(seq=90)的报文,然后客户端宕机,然后这个 SYN 报文还被网络阻塞了,服务端并没收到这个请求。接着客户端重启后,又重新向服务端建立连接,发送了 SYN(seq=100)。这时旧的 seq=90 的 SYN 先到了服务端

      • 一个旧报文比新报文先到了服务端,服务端就会向客户端返回一个 SYN+ACK 报文给客户端,确认号为 91(90+1)
      • 客户端收到后,发现自己期望的确认好应该是 101(100+1),而不是 91,于是返回 RST 报文,中断连接。
      • 服务端收到 RST 报文后释放连接
      • 后续最新的 SYN 到达后,就可以正常建立连接了
  • 同步双方序列号

    若客户端发送 SYN 后崩溃,服务端未收到第三次握手,客户端重启后使用新 ISN 重新发起连接,旧 ISN 对应的报文会被视为过期

    • 同步序列号的作用:

      • 接收方可以保证顺序和去重
      • 接收方可以根据数据包的序列号按序接收
      • 可以标识发送出去的数据包中,哪些是已经被对方所接收了的
  • 避免资源浪费

    如果只有两次握手,如果客户端发送的 SYN 在网络阻塞或因其他原因没及时送达,就会导致服务端没有收到请求,所以也就不会返回 ACK 客户端机会重复发送一条 SYN。由于没有三次挥手,所以服务端不知道客户端到底有没有收到自己回复的 ACK,所以服务端每收到一个 SYN 就建立一个连接。这会造成什么情况呢?如果客户端因为网络阻塞发送了多个 SYN,那么最后服务端就会对每个 SYN 都建立起连接,造成不必要的资源浪费

用一个例子来说就像

我和朋友玩游戏,但是我们玩之前需要测试一下麦克风是不是好的,看看能不能听到对方说话。

  • 我先说喂喂喂能听到吗?
  • 朋友听到了就说:OKOK 能听到。(我知道了我说话对方能听到)
  • 我说:OK 能听到赶快上号。(朋友知道了我能听到)

至此三次握手,可靠连接完成。

JWT 令牌和传统令牌有什么区别?

  • 无状态:JWT 无需在服务端存储信息,而是全部依赖于 JWT 令牌,减轻了服务端压力
  • 安全性能:JWT 会使用密钥对令牌进行签名,确保令牌不会被篡改。只有服务端持有正确密钥才能进行解析。此外因为 JWT 不存储于 Cookie,也结局了 CSRF 跨站请求伪造攻击
  • 跨域支持:只需要在请求头携带 JWT 令牌即可

JWT 令牌为什么能解决集群部署,什么是集群部署?

在传统基于会话和 Cookie 的身份验证中,会话信息会存于服务器内存或数据库中。但是在集群中不同服务器之间无法共享会话信息,就会导致用户在不同服务器之间切换需要重新登录,或者引入其他的共享机制(如 Redis),增加了性能开销。

而 JWT 令牌由于具有无状态性,不依赖于服务端存储信息。JWT 令牌自身就携带了身份验证和会话信息。不同的服务器之间只需要有正确的密钥就可以正确的解析出 JWT 令牌,而无需引入额外的共享机制。

JWT 令牌有哪些字段?

有三个字段,

  • 头部:用于描述令牌元数据,通常包含两个字段

    • 签名算法:如 HS256
    • 令牌类型:JWT
  • 载荷:用来携带一些额外的信息,比如用户名、用户 ID 等。但需要注意载荷是不加密的,所以不能存放敏感信息。此外过期时间是放在载荷里的注册声明中。

  • 签名:对头部、载荷 Base64 编码后,再对编码后的结果和密钥一起进行签名后的结果

JWT 令牌如果泄露了,怎么解决,JWT 是怎么做的

首先可以直接更换服务器密钥,使所有令牌失效

但是这个方法太暴力了,那有其他办法吗?有,但是需要牺牲 JWT 的无状态性

  • 黑名单:在 Redis 或其他地方存储需要加入黑名单的 jwt 或者 jti(jwt 唯一标识)

  • 刷新令牌:服务端存储长效令牌、给客户端办法短期令牌。当短令牌泄露后,服务端就标记长令牌失效。即使令牌泄露,也能最小的降低损失。

  • 去掉无状态:JWT 中存储一个 Key,redis 中键使用 jwt 中的 key,值为之前放在载荷里的信息。每次验证时查询 redis,如果令牌泄露直接让 reids 中的值过期即可。

SHA256 是加密还是签名?加密和签名有什么区别?

SHA256 属于哈希算法(Hash Algorithm),并非直接的加密或签名算法。

SHA256 的作用是将任意长度的字符转为固定长度(256)的唯一哈希值,确保数据完整。其中如果某一位发生微小的改动,也会导致最后的哈希值发生显著的变化。且无法从哈希值反推出原数据。

SHA256 在签名中的作用。

  • 生成摘要:对消息计算 SHA256 哈希值。
  • 签名:使用私钥对哈希值加密(如 RSA 签名)。
  • 验证:接收方用公钥解密签名得到哈希值,重新计算消息哈希并比对

签名 VS 加密?

维度加密签名
目的加密数据,防止被未授权读取确保数据完整性和真实性(防篡改)不保证机密性
密钥使用对称加密:单密钥
非对称加密:公钥加密、私钥解密
私钥签名,公钥验证
数据流向加密后数据可逆(需要密钥解密)签名不可逆(仅用于验证)
应用场景传输敏感数据合同签署、软件发布

相关文章:

  • 第2章 网络安全评估平台(网络安全评估)
  • 归并排序:数据排序的高效之道
  • 光场中的核心概念:Macro Pixel与SAI的深度解析与实例应用
  • float x_number 转换成char*
  • Topo2Seq:突破DETR局限,车道拓扑推理新高度
  • thefuck是如何帮助你提高命令行效率
  • 实践-给图片右下角加opencv-logo
  • Spring AI 与 LangChain4j 选型对比分析
  • QT:动态属性和对象树
  • Excel ScriptLab学习笔记
  • Linux驱动编程 - Framebuffer子系统
  • 2025-03-16 学习记录--C/C++-PTA 练习4-7 求e的近似值
  • RabbitMQ(补档)
  • 设计模式-组件协作
  • 《灵珠觉醒:从零到算法金仙的C++修炼》卷三·天劫试炼(57)乾坤尺量会议室 - 会议室安排(贪心排序)
  • 麒麟服务器操作系统Node.js环境部署手册
  • 3.16-线程同步
  • Excel(函数篇):COUNTIF与CONUTIFS函数、SUMIF与SUMIFS函数、ROUND函数、MATCH与INDEX函数、混合引用与条件格式
  • ollama注册自定义模型(safetensors)
  • 基于大模型的分泌性中耳炎全流程预测与治疗管理研究报告
  • 特朗普称美军舰商船应免费通行苏伊士运河,外交部:反对任何霸凌言行
  • 印巴冲突升级,巴防长称已击落5架印度战机
  • 李云泽:将尽快推出支持小微企业民营企业融资一揽子政策
  • 山东滕州车祸致6人遇难,醉驾肇事司机已被刑事拘留
  • 五一假期上海两大机场客流量超193万人次,创历年同期最高
  • 江西省文化和旅游厅厅长梅亦已任省委宣传部副部长