JAVA程序员防翻车指南

一、基础类型与字面量(1-10) int 最大值 2,147,483,647(2³¹-1),最小值 -2,147,483,648(-2³¹) long 最大值 9,223,372,036,854,775,807(2⁶³-1),字面量加 L float 字面量必须加 f,否则默认 double short 范围 -32,768 ~ 32,767,byte 范围 -128 ~ 127 字面量下划线:1_000_000 等于 1000000,仅 Java 7+ 0x 开头是十六进制,0b 开头是二进制,0 开头是八进制(容易误判) char 占 2 字节,Character.SIZE = 16,但 Unicode 扩展字符需 2 个 char(surrogate pair) boolean 只有 true/false,不能和 0/1 互转(和 C 不同) Integer.valueOf(127) == Integer.valueOf(127) 为 true,128 为 false(缓存池 -128~127) Integer.parseInt("") 抛 NumberFormatException,Integer.parseInt(null) 也抛 二、运算符与表达式(11-18) & 和 && 区别:& 两边都执行(位运算或逻辑与),&& 短路 | 和 || 同理,|| 短路 ^ 按位异或,~ 按位取反(包含符号位) >> 算术右移(补符号位),>>> 无符号右移(补 0) i++ 和 ++i 的字节码区别:前者先加载后自增,后者先自增后加载 += 隐式类型转换:short s = 1; s += 1; 合法,s = s + 1; 编译错误 三元运算符 ? : 必须返回相同类型,否则自动类型提升 字符串拼接用 + 在循环内会生成大量 StringBuilder,循环外编译器自动优化 三、String 相关(19-28) String 不可变,底层 char[] 被 final 修饰且不暴露修改方法 字符串常量池在堆中(Java 8+),intern() 手动入池 new String("abc") 创建 2 个对象(常量池 + 堆),"abc" 只创建 1 个(若池中没有) String.equals() 比较内容,== 比较引用地址 String.compareTo() 按字典序比较,返回差值 String.format() 使用 %s %d %f 占位符 substring() 在 Java 7 前共享 char[],Java 7+ 创建新数组(解决内存泄漏) split() 参数是正则,. 需转义为 \\. replace() 替换所有字符,replaceAll() 支持正则 StringBuilder 线程不安全,StringBuffer 线程安全(方法加 synchronized),但慢 四、equals 与 hashCode(29-34) 重写 equals 必须重写 hashCode,否则 HashMap/HashSet 失效 equals 必须满足:自反、对称、传递、一致、非空(x.equals(null) 返回 false) hashCode 相等的两个对象 equals 不一定相等(哈希冲突) Objects.equals(a, b) 自动判空,避免 null.equals() Arrays.deepEquals() 比较多维数组,Arrays.equals() 只比较一维 List.equals() 要求顺序相同,Set.equals() 仅要求元素相同(顺序无关) 五、异常处理(35-44) try-catch-finally 中 finally 在 return 之前执行,但返回值已在 finally 前计算好 finally 中有 return 会覆盖 try/catch 的返回值 finally 中修改返回的引用对象内容会生效(如 List.add()) 不要在 finally 中写 return,也不要在其中抛出异常(会掩盖原始异常) try-with-resources 要求资源实现 AutoCloseable,关闭顺序:先声明的后关闭 受检异常(Exception 子类)必须 try-catch 或 throws,RuntimeException 非受检 异常链:catch (SQLException e) { throw new RuntimeException(e); } 保留堆栈 printStackTrace() 不应用在生产日志中,用 log.error("msg", e) Throwable 包括 Error 和 Exception,Error 不应该捕获(如 OutOfMemoryError) finally 中关闭资源时可能抛异常,要用 try-catch 包裹或使用 try-with-resources 六、集合框架(45-62) ArrayList 初始容量 10,扩容 1.5 倍(oldCapacity + (oldCapacity >> 1)) ArrayList 随机访问 O(1),中间插入删除 O(n),LinkedList 相反 LinkedList 实现了 Deque,可作为队列/栈使用 HashMap 容量为 2 的幂,默认 16,负载因子 0.75,扩容 2 倍 HashMap 索引计算:(n - 1) & hash,hash = key.hashCode() ^ (h >>> 16) Java 8+ HashMap 链表长度 > 8 且容量 >= 64 时转红黑树,< 6 时退化为链表 HashMap 的 key 为 null 时,hash 值为 0,存在 table[0] ConcurrentHashMap 分段锁(Java 7)→ synchronized + CAS(Java 8+) Hashtable 线程安全但全表锁,已淘汰 TreeMap 基于红黑树,有序(按 key 自然序或 Comparator) LinkedHashMap 按插入顺序或访问顺序(accessOrder=true 可实现 LRU) HashSet 底层是 HashMap,TreeSet 底层是 TreeMap PriorityQueue 小顶堆,Comparator.reverseOrder() 可转大顶堆 ArrayDeque 优于 Stack(Stack 已废弃,ArrayDeque 线程不安全但更快) Collections.synchronizedList() 包装为线程安全,但迭代时仍需手动同步 CopyOnWriteArrayList 读多写少场景,写时复制整个数组 List.of() / Set.of()(Java 9+)返回不可变集合,不能增删改 ArrayList.subList() 返回视图,对子列表操作会影响原列表,反之亦然 七、并发与线程(63-80) 创建线程的三种方式:Thread、Runnable、Callable(有返回值) start() 启动线程,run() 只是普通方法调用 synchronized 可锁实例方法、静态方法(锁 Class)、代码块 wait() 释放锁,sleep() 不释放锁,yield() 让出 CPU 但不释放锁 wait()/notify() 必须在 synchronized 块中调用,否则抛 IllegalMonitorStateException notify() 随机唤醒一个,notifyAll() 唤醒全部 死锁四条件:互斥、请求与保持、不可抢占、循环等待 破坏死锁:按固定顺序加锁、tryLock() 超时、使用 ReentrantLock volatile 保证可见性和有序性(禁止指令重排),不保证原子性 AtomicInteger 用 CAS 实现原子操作,incrementAndGet() 底层是 Unsafe.compareAndSwapInt ThreadLocal 用完后必须 remove(),否则线程池复用导致内存泄漏 ThreadLocal 底层是 ThreadLocalMap,key 是弱引用,value 是强引用 ExecutorService 用 shutdown() 优雅关闭,shutdownNow() 立即关闭 线程池核心参数:核心线程数、最大线程数、存活时间、阻塞队列、拒绝策略 四种拒绝策略:AbortPolicy(抛异常)、CallerRunsPolicy(调用者执行)、DiscardPolicy(丢弃)、DiscardOldestPolicy(丢弃最旧) CachedThreadPool 最大线程数 Integer.MAX_VALUE,容易 OOM,慎用 FixedThreadPool 使用无界队列,任务积压可能导致 OOM Future.get() 阻塞,CompletableFuture 异步回调更灵活 八、I/O 与 NIO(81-88) File 只是路径抽象,不表示真实文件,exists() 判断是否存在 FileInputStream 读取字节,FileReader 读取字符(默认编码可能乱码) BufferedReader 用 readLine() 按行读取,Files.readAllLines() 更简洁 InputStreamReader 可指定字符集:new InputStreamReader(new FileInputStream("a.txt"), StandardCharsets.UTF_8) try-with-resources 自动关闭多个资源,用分号分隔 Path/Paths/Files(Java 7+)替代 File,Files.walk() 遍历目录树 NIO 核心:Channel + Buffer + Selector(多路复用) ByteBuffer 读写切换需调用 flip(),clear() 清空,compact() 压缩 九、JVM 与内存(89-95) JVM 内存区域:堆、栈、方法区(元空间)、程序计数器、本地方法栈 堆分代:年轻代(Eden + S0 + S1)→ 老年代,默认比例 8:1:1 OutOfMemoryError 常见原因:堆 OOM、元空间 OOM、直接内存 OOM、栈溢出 栈溢出 StackOverflowError,递归过深或无终止条件 System.gc() 仅建议 GC,不保证执行 finalize() 已废弃(Java 9+),对象自救不靠谱 类加载器:Bootstrap(rt.jar)→ Extension → Application,双亲委派模型 十、日期与时间(96-100) java.util.Date 可变、线程不安全,已废弃,用 java.time.*(Java 8+) LocalDate / LocalTime / LocalDateTime 不含时区,ZonedDateTime 含时区 Instant 时间戳(秒/纳秒),Duration 计算时间差,Period 计算日期差 格式化用 DateTimeFormatter 线程安全,替代 SimpleDateFormat(线程不安全) LocalDateTime.now() 获取当前时间,parse("2026-07-09") 按 ISO 标准解析 十一、语法糖与常见坑(101-110) 泛型编译期擦除,运行时无泛型信息(List<String> 和 List<Integer> 在运行时相同) 可变参数本质是数组,public void method(String... args) 可传数组或逗号分隔 枚举 enum 默认继承 Enum,不能被继承,构造器私有 匿名内部类持有外部类引用(this$0),可能导致内存泄漏 接口默认方法 default,静态方法 static,接口中变量默认 public static final 抽象类可有构造器,接口无构造器 switch 支持 byte/short/char/int/String/enum,不支持 long/float/double break 跳出循环,continue 跳过本次,return 结束方法 标签 label: 可跳出外层循环,但不推荐使用 数组协变:String[] 是 Object[] 的子类,但集合泛型不变 十二、网络与反射(111-118) URL 和 URI 区别:URI 是标识符,URL 是定位符(包含协议) HttpURLConnection 原生 HTTP 客户端,但 Apache HttpClient / OkHttp 更常用 Socket 阻塞 I/O,ServerSocket 监听端口,accept() 阻塞等待连接 反射 Class.forName() 初始化类,ClassLoader.loadClass() 不初始化 Method.invoke() 性能差,但反射能绕过私有访问(setAccessible(true)) Proxy.newProxyInstance() 动态代理只能代理接口,CGLIB 可代理类 注解保留策略:SOURCE(源码)、CLASS(字节码)、RUNTIME(运行时) 序列化实现 Serializable 接口,serialVersionUID 用于版本兼容,不声明则自动生成 十三、设计原则与模式(119-123) 单例双重检查需 volatile,防止指令重排导致半初始化对象被读取 工厂模式解耦,策略模式消除 if-else,观察者模式事件驱动 依赖倒置:依赖抽象而非具体实现 开闭原则:对扩展开放,对修改封闭 里氏替换:子类可以替换父类出现的地方 十四、Linux 与常用命令(124-128) nohup java -jar app.jar & 后台运行,输出到 nohup.out jps 查看 Java 进程,jstack 打印线程堆栈,jmap 查看堆内存,jstat 监控 GC kill -15 优雅关闭(SIGTERM),kill -9 强制杀死(SIGKILL) tail -f app.log 实时查看日志,grep -C 10 "error" app.log 显示上下文 netstat -tlnp 查看端口占用,lsof -i:8080 查看指定端口 十五、数据库与 JDBC(129-133) PreparedStatement 防 SQL 注入,占位符 ?,编译一次执行多次 Statement 拼接字符串有注入风险,不用 ResultSet 游标从 1 开始,next() 移动指针,getString(1) 按索引取 事务隔离级别:读未提交、读已提交、可重复读、串行化 连接池用 HikariCP(默认)、Druid,不要手动创建连接 十六、Maven / Gradle(134-137) Maven 生命周期:clean → compile → test → package → install → deploy provided 作用域表示运行时由容器提供(如 servlet-api) 依赖传递:compile 传递,test 不传递,provided 不传递 版本冲突用 dependencyManagement 锁定版本,或用 exclude 排除 十七、工具与调试(138-142) System.out.println() 性能差,生产用日志框架(SLF4J + Logback) 日志级别:ERROR > WARN > INFO > DEBUG > TRACE assert 默认禁用,需 -ea 开启,生产慎用 Objects.requireNonNull() 判空抛 NullPointerException,比手动 if 简洁 Optional 用来表示可能为 null 的返回值,不要用作字段或参数 十八、编码规范与常识(143-150) 包名全小写,类名大驼峰,方法/变量小驼峰,常量全大写 + 下划线 方法名用动词(getUser),变量名用名词(userName) 布尔变量用 is / has / can 开头(isDeleted) 一个 .java 文件只能有一个 public 类,文件名必须和 public 类名一致 构造器不能 static,不能被 final 修饰 static 块在类加载时执行一次,{} 实例块在构造器前执行 匿名对象(new User())用完即抛,适合单次使用 永远不要在线上环境用 System.out 打印大对象,更不要用 e.printStackTrace()

2026-07-09 10:15:46 PM · 4 分钟
翻车防御工事

漫谈AI上下文长度

flowchart TD A[2018–2021512–2K Token金鱼记忆时代] -->|只能处理短文、短句、单轮问答| B[2022–20234K–128K Token中上下文普及时代] B -->|可处理论文、合同、小说、中型代码库RAG 成为企业标准方案| C[2024–2025200K–1M Token百万上下文实验期] C -->|纸面支持 1M,但遗忘、成本、速度问题明显仅限量 Beta| D[2026–20271M–2M Token1M 量产普惠时代] D -->|1M 成为旗舰标配可处理整卷宗、整代码库、全年业务日志| E[2028–20292M–10M Token超百万窗口分化时代] E -->|2M 以上面向行业定制长视频、大型代码库、多年日志联合分析| F[2030+10M+ 弹性记忆原生长记忆智能体时代] 一、大模型上下文完整进化时间线(2018–2026,分4个时代) 1. 初代Transformer时代:512–2K(2018–2021,金鱼记忆) 2018 GPT-1 / BERT:512 token,仅短文、短句分类,多轮对话必丢前文 2019 GPT-2:1024 token,能写短篇故事,连续对话3–5轮就遗忘历史 2020 GPT-3:2048 token(2K),行业通用标准,仅支持短提示词、少量示例,长文档必须分段/摘要处理 2021 早期国产(文心一言初代、GLM-1):统一2K上限,无长文本能力 时代特征:没有原生长文本能力,所有长内容必须靠人工拆分、外部摘要,RAG雏形出现。 2. 中上下文普及时代:4K–128K(2022–2023,工业化可用) 2022 ChatGPT(GPT-3.5):4K;年末升级16K,日常聊天够用,长文档仍吃力 2023.3 GPT-4 首发:8K,专业文档、代码单文件处理门槛打开 2023.7 Claude 2:100K,首个量产十万级窗口,法律、长篇论文场景爆发 2023.11 GPT-4 Turbo:128K,全球主流商用标配门槛,单本小说完整载入 2023年底 国产跟进:通义、GLM、混元开放32K/64K;开源Llama 2、Qwen 7B固定4K–32K 2023.12 Gemini 1.0 Ultra 纸面宣称1M,但仅实验室封闭测试,无法商用落地 时代里程碑:128K成为专业AI标配;RAG成为行业标准方案,弥补窗口不足。 3. 百万上下文实验期:200K–2M纸面(2024–2025,纸面强、实际弱) 2024.2 Gemini 1.5 Pro:原生2M token,全球首个公开百万级模型,但存在严重「中间遗忘Lost in the Middle」,后半段文档召回暴跌,仅适合摘要,不适合精细推理,仅限开发者限量Beta 2024 Claude 3 Opus:200K,稳定可靠,法律行业主力,无严重遗忘问题 2025 上半年:各大厂商放出1M Beta(Claude Sonnet 4、Gemini 2.0),长上下文计费溢价极高、响应慢、显存开销巨大,企业少量试用,普通用户无法接触 2025 国产开源:Qwen、DeepSeek推出64K–128K底座,少量实验版支持256K 时代特征:1M只是技术噱头,标称≠有效;硬件成本、遗忘问题、价格三重门槛,无法大规模落地。 ...

2026-07-09 08:58:34 PM · 2 分钟
AI

一个程序员应该知道的各种锁

1. 悲观锁 vs 乐观锁(心态问题) 悲观锁:觉得肯定有人抢厕所,进去就反锁门(加锁),用完再开。 → Java里的 synchronized 和 ReentrantLock 都是这种。安全,但慢。 乐观锁:觉得没人抢,不锁门,但上厕所时盯着门把手(版本号/时间戳),如果发现被人动过(冲突),就重试。 → Java里的 CAS(比较并交换) 就是这种,比如 AtomicInteger。快,但冲突多时会反复重试。 2. 公平锁 vs 非公平锁(排队问题) 公平锁:先来后到,乖乖排队。 → new ReentrantLock(true)。公平,但效率低(大家都得排队)。 非公平锁:新来的可以插队,如果锁刚好释放,它就直接抢。 → synchronized 和默认的 ReentrantLock 都是非公平。效率高,但可能导致某些线程饿死(一直抢不到)。 3. 可重入锁(同一个人的多次进入) 你进了厕所A,发现里面还有个小隔间B,你能直接进B,不用再掏钥匙。 → synchronized 和 ReentrantLock 都支持。同一个线程可以多次获取同一把锁,防止自己把自己卡死。 4. 读写锁(分情况管理) 厕所分蹲位(写锁)和洗手池(读锁)。 多人同时洗手(读)没问题。 但有人蹲坑(写)时,别人既不能蹲也不能洗手(全阻塞)。 → ReentrantReadWriteLock。适合读多写少的场景。 5. 共享锁 vs 排他锁(权限级别) 排他锁(写锁):厕所门一锁,谁也别进。 共享锁(读锁):可以多人同时看同一份文件(但不能改)。 → 读写锁就是共享/排他的典型实现。 6. 偏向锁 → 轻量级锁 → 重量级锁(锁升级,JVM自动优化) 这是 synchronized 的底层升级过程(为了性能): 偏向锁:厕所只认你一个人,你每次来都不用掏钥匙(无竞争)。 轻量级锁:偶尔有别人来,你们用“自旋”方式(原地转圈等)抢,不挂起线程(省资源)。 重量级锁:竞争激烈,排队挂起(操作系统介入,慢)。 → 这是JVM自动做的,你不用管,但知道它存在即可。 7. 自旋锁(不睡觉,死等) 厕所被占,你不去排队睡觉,而是在门口原地转圈(循环检查),等它释放。 → 适合持有锁时间很短的情况,避免线程挂起/唤醒的开销。Java里的 CAS 就是自旋思想。 8. 分段锁(分块管理) 一个大厕所分成多个小隔间,锁只锁其中一间,不影响其他间。 → ConcurrentHashMap 早期就是用分段锁,提升并发度(现在改用CAS+细粒度锁了)。 一张图总结(按使用场景选): 场景 推荐锁 简单同步,代码少 synchronized 需要可中断、超时、公平等灵活功能 ReentrantLock 读多写少(如缓存) ReentrantReadWriteLock 计数器、自增等简单操作 AtomicXXX(乐观锁) 追求极致性能,竞争不激烈 偏向锁/轻量级锁(JVM自动)

2026-07-09 11:15:34 AM · 1 分钟

Docker 常用配置

Kafka Kafdrop services: kafdrop: image: obsidiandynamics/kafdrop container_name: kafdrop ports: - "9000:9000" environment: KAFKA_BROKERCONNECT: "localhost:9092" SERVER_SERVLET_CONTEXTPATH: "/" restart: unless-stopped KRaft 模式 services: kafka: image: apache/kafka:3.9.0 container_name: kafka hostname: kafka ports: - "9092:9092" environment: KAFKA_NODE_ID: 1 KAFKA_PROCESS_ROLES: broker,controller KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093 # 注意:这里不要换行,不要逗号 KAFKA_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9093 # 外部访问地址 KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092 KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,CONTROLLER:PLAINTEXT KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1 KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1 CLUSTER_ID: MkU3OEVBNTcwNTJENDM2Qk volumes: - ./data:/var/lib/kafka/data restart: unless-stopped

2026-07-09 11:04:37 AM · 1 分钟
Docker

Java GC 是如何驱赶“老年群体”的

在 Java 的世界里,“分代年龄”(Age) 是专门为 JVM 垃圾回收(GC)设计的一个计数器。 简单来说,它记录了一个 Java 对象在垃圾回收中活过了多少轮。就像打游戏刷副本一样,对象每在垃圾回收的“清洗”中幸存下来一次,它的分代年龄就会 +1。 1. 为什么需要“分代年龄”? 这源于 Java 垃圾回收领域著名的 弱分代假说(Weak Generational Hypothesis): 绝大多数的 Java 对象都是“朝生夕死”的。 比如你在一个方法里 new 了一个局部变量,方法结束了,这个对象就没用了。但也有极少数对象(如 Spring 的 Bean、数据库连接池)会一直存活。 为了高效管理内存,JVM 把堆内存分成了两大区域: 新生代(Young Generation): 存放刚出生、寿命短的对象。 老年代(Old Generation): 存放寿命长、常驻内存的对象。 “分代年龄”就是对象从“新生代”晋升到“老年代”的资格证和进度条。 2. 它的工作流程是怎样的? 我们可以把新生代想象成一个“新手村”,老年代想象成“满级主城”: 出生(0岁): 绝大多数新对象在新生代的 Eden(伊甸园)区 出生。 第一次渡劫(1岁): 发生了一次 Minor GC(新生代垃圾回收)。如果这个对象没被回收,它会幸存下来,被移动到 Survivor(幸存者)区,同时它的分代年龄变成 1 岁。 继续熬资历(每活过一轮 +1岁): 以后每发生一次 Minor GC,只要它还在幸存者区折腾且没死掉,它的年龄就会加 1 岁。 晋升老年代: 当它的年龄达到一定阈值(默认是 15 岁,由 JVM 参数 -XX:MaxTenuringThreshold 决定)时,JVM 就会认为:“这小伙子挺能活,应该是个核心常驻对象。” 于是把它晋升(Promote)到老年代。 3. 它存在对象的什么地方? 正如我们在讨论“锁”时提到的,分代年龄存在于每个 Java 对象的 对象头(Object Header)的 Mark Word 里面。 ...

2026-07-09 10:19:20 AM · 1 分钟

为什么每个java对象都有锁的属性?这样不是浪费内存吗

1. 为什么当初要这样设计?(设计初衷) Java 诞生于 20 世纪 90 年代中期,当时多线程编程(并发)正在兴起。Java 的设计者高斯林(James Gosling)希望 Java 成为一门简单易用的多线程语言。 简化并发编程 如果对象没有内置锁,你每次想给一段代码加锁,都必须显式地创建一个 Lock 对象: // 如果没有内置锁,你得这么写: Lock myLock = new ReentrantLock(); void doSomething() { myLock.lock(); try { // 业务逻辑 } finally { myLock.unlock(); } } 而 Java 设计者引入了 synchronized 关键字,直接让对象充当锁,把复杂的同步简化成了: // 极简的同步写法 synchronized(this) { // 业务逻辑 } “万物皆对象,万物皆可为锁”的设计,让开发者不需要管理一堆乱七八糟的锁对象,只要拿到目标对象,就能直接进行同步控制。 2. 这样不浪费内存吗?JVM 是怎么“偷懒”的? 你担心的内存浪费,JVM 工程师早就想到了。他们绝对不会傻到给每个刚 new 出来的对象都分配一个沉重的操作系统级别的锁(Monitor)。 JVM 解决这个问题的核心思路是:按需分配,动态升级。 秘密武器:对象头(Object Header) 在 Java 中,每个对象的内存结构里都有一个叫 Mark Word(标记字) 的区域(在 64 位虚拟机上占 8 个字节 / 64 位)。 ...

2026-07-09 10:11:43 AM · 1 分钟

H mod C = H & (C - 1) ?

在 Java 后端开发面试或源码阅读中,HashMap 永远是避不开的核心。很多人能熟练地背出它的扩容阈值、红黑树化条件,但当深入到源码底层,看到诸如 e.hash & (newCap - 1) 和 e.hash & oldCap 这样的位运算时,往往会陷入沉思。 一、 起源:为什么数组长度必须是 2 的次幂? 在散列表中,为了让数据均匀分布,最直观的想法是对哈希值进行取模(求余数): $$\text{索引位置} = \text{hash} \pmod{\text{Capacity}}$$然而,在 CPU 的底层底层执行中,除法和取模(%)是非常昂贵的算术操作(可能需要几十个时钟周期)。为了追求极致的性能,底层的数论定理为我们提供了一个完美的替代方案: 数学定理:当容量 $C$ 是 $2$ 的次幂($2^n$)时,对于任意整数 $H$,满足: $$H \pmod C \equiv H \ \& \ (C - 1)$$ 位运算(&)在 CPU 中只需要 1 个时钟周期!为了享受到这个性能红利,HashMap 在源码中将容量死死限制为 2 的次幂: // 默认初始容量 16 (2的4次方) static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; 裁剪器的魔术:hash & (newCap - 1) 以容量 16 为例,16 - 1 = 15(二进制:0000 1111)。任何 Hash 值与 15 进行 & 运算,高位都会被无情“抹零”,只有最后 4 位被保留下来: ...

2026-07-08 05:38:40 PM · 2 分钟
代码原理

限流降级研究笔记 更新中

主流算法 固定窗口计数器 每秒一个计数器,+1超过阈值就拒绝 缺点 0.51s 和 1.51s 之间产生2倍流量 滑动窗口 把时间切成更细的小格,滑动统计 缺点 解决临界问题,但是实现复杂 漏桶 请求进桶,匀速流出,桶满则弃 缺点流出速度恒定,扛不了正常的突发 令牌桶 匀速往桶里放令牌,来请求拿令牌,没令牌被拒绝 缺点允许一定突发

2026-07-07 04:05:10 PM · 1 分钟

Java 从Random理解 CAS自旋模式 更新中

你理解这段代码吗? do { oldseed = seed.get(); newseed = (oldseed * ...) & mask; } while (!seed.compareAndSet(oldseed, newseed)); // ← CAS 第〇层:先搞懂"变量"在多线程里的危险 假设有一个变量 seed = 100,两个线程同时想改它: 线程A:读到 seed=100 → 算出新值 200 → 写回 seed=200 线程B:读到 seed=100 → 算出新值 300 → 写回 seed=300 问题来了——两个人都读到了 100,各算各的,最后谁写得晚谁赢。A 的结果被 B 覆盖了,A 白干了,而且程序根本不知道出错了。 这就是经典的 线程安全问题。 第一层:解决办法有两条路 路线一:悲观锁(synchronized) → "我先把门锁上,别人别进来" → 安全但慢(别人要排队) 路线二:乐观锁(CAS) → "门不锁,但我写之前验一下,被人改了我就重来" → 快但可能要重试 你这段代码走的就是 路线二。 第二层:什么是 CAS? CAS = Compare And Set(比较并设置),三个参数: CAS(内存地址, 我期望的旧值, 我想写入的新值) 它做的事用大白话说: “我记得这个值是 42,如果现在还是 42,就帮我改成 77。如果不是 42 了,说明有人动过了,啥也别干,告诉我失败了。” ...

2026-06-06 09:40:20 PM · 2 分钟

常见缓存策略精析 更新中

缓存三兄弟 缓存穿透 是什么:大量请求查询数据库不存在的数据。缓存没有,持续击打数据库 防范方法: 缓存空值 数据库查不到也往缓存放一个空标记(TTL短,2分钟,该TTL过期了才能进行再次数据库查询) 下一次同样的id来了直接被挡住 优点:有效拦截大量穿透请求 缺点: 恶意攻击造成大量不同的不存在的key,缓存堆满无效数据,浪费内存 延迟了数据一致性,新数据必须等缓存过期 布隆过滤器(推荐) 启动时把所有合法id放入布隆过滤器; 布隆过滤器不给进就真的没有; 优点: 没有高内存占用风险 没有数据一致性风险 缺点: 有极小误判率 增删要维护。数据更新时,要同步更新布隆过滤器 接口层限流和校验 API网关或Controller层做拦截 参数校验、限流降级 互斥锁重建 互斥锁主要解决缓存击穿(热点key过期),但是在缓存空对象场景下,如果有大量并发请求一个不存在的key,可以使用锁 每次访问让一个线程去查DB,其他线程等待 大厂的实践 网关层 校验参数格式,IP限流(挡掉脚本攻击) 过滤器层 布隆过滤器 缓存层 缓存击穿 是什么:某个被疯狂访问的热点key,因为TTL一到,失效的一瞬间海量并发请求同时miss,同时到DB重建同一个key 防范方法: 互斥锁 只让第1个miss的线程去查DB 其他线程等一下再读缓存 高一致性:线程傻傻等待 低一致性:先给旧数据,下一次来查就是新的 逻辑过期 key永不物理过期,把过期时间存在value里面 如果发现逻辑上过期了,异步开个线程重建 当前请求先返回旧值 互斥锁的实现 如何搭建出正确的锁? v1:SETNX key 1 抢锁,DEL key 释放 SETNX=“不存在才设置成功”,天然互斥。但问题:抢到锁的线程崩了、DEL 没执行 → 锁永远不释放 → 死锁。 v2:给锁加过期间 崩了也能自动过期。但关键:必须用一条原子命令 SET key value NX EX 30。 陷阱:别写成 SETNX + 再 EXPIRE 两条——如果刚 SETNX 成功、还没 EXPIRE 就崩了,又变回 v1 的死锁。“抢锁"和"设过期"必须一步做完。 ...

2026-05-05 10:46:44 PM · 1 分钟
Redis 缓存策略