JDK 1.1

JDK 1.1 于 1997 年 2 月 19 日发布,是 JDK 1.0 之后的首个重要更新。1.0 以 Applet 与 AWT 打开知名度,1.1 则补上了企业开发所需的一批基础能力:数据库、远程调用、反射、序列化、JAR 打包等。命名与后续演变见 Java 版本历史。 版本号说明 维度 JDK 1.1 对外名称 Java 1.1 / JDK 1.1 java.version 1.1.x class major version 45(minor 3,即 45.3) 上一版本 JDK 1.0 下一版本 JDK 1.2(1998-12,集合框架与 Swing) 概览 语言 内部类(nested classes):成员内部类、局部内部类、匿名内部类、静态嵌套类(详见 Java 内部类) 类库与规范 反射(java.lang.reflect):运行时检查类、方法、字段并调用 JDBC(java.sql):标准数据库访问 API,Java 进入服务端开发的入口之一 RMI(java.rmi):远程方法调用 JavaBeans(java.beans):组件化与 IDE 工具链约定 对象序列化(java.io.Serializable 等):对象持久化与网络传输 JAR(Java ARchive):类与资源的打包格式,配套 jar 命令 国际化(java.util.Locale、ResourceBundle 等)与 Unicode 文本流(Reader / Writer) java.text:日期、数字格式化 java.math:BigDecimal、BigInteger AWT 委托事件模型(delegation event model):用监听器(如 ActionListener)替代 1.0 的 handleEvent() 继承式写法;GUI 代码结构变化较大,属于不兼容的 API 调整 JVM 与性能 引入 JIT 编译器(因平台而异),热点 bytecode 可编译为本地代码,性能较 1.0 纯解释执行明显提升 类卸载(class unloading)等运行时能力增强 仍为 Sun 早期 JVM;HotSpot 要到 1.3 才成为默认实现 工具 jar:打包与解包 JAR serialver:查看序列化类的 serialVersionUID jdb 等调试工具随 JDK 增强 1.1 尚未具备(后续版本才加入) 能力 大致引入版本 Swing、集合框架(HashMap 等) 1.2 java.lang.ref(软/弱/虚引用) 1.2 HotSpot 默认 VM 1.3(1.4 起唯一,见 HotSpot) assert、NIO、标准正则 1.4 泛型、enum、for-each、JUC 5 内部类 1.0 只有顶层类;1.1 起可在类或方法内部定义嵌套类,为回调、封装 helper、匿名监听器等场景提供语法支持: ...

Created 20206-06-20 · Updated 15156-07-15 · 3 min · 487 words · -

JDK 1.3

JDK 1.3(对外称 Java 2 Platform, Standard Edition, v1.3,代号 Kestrel)于 2000 年 5 月 8 日发布,是 JDK 1.2 之后又一次以运行时为核心的升级——最重要的变化不在语言语法,而在 JVM 本身:HotSpot 从此成为默认 VM。命名与后续演变见 Java 版本历史。 版本号说明 维度 JDK 1.3 对外名称 Java 2 SE 1.3 / JDK 1.3 代号 Kestrel java.version 1.3.x class major version 47 上一版本 JDK 1.2 下一版本 JDK 1.4 概览 JVM:HotSpot 成为默认 VM 这是 1.3 最重要的变化。HotSpot 早在 1999 年 4 月就以独立产品 Java HotSpot Performance Engine 发布,可配合 JDK 1.2 使用,但当时不是默认项;JDK 1.3 起 HotSpot 成为默认 VM,旧的 Classic VM 仍可通过参数选用,直到 JDK 1.4 才被彻底移除。详见 HotSpot。 ...

Created 14146-07-14 · Updated 15156-07-15 · 3 min · 437 words · -

JDK 1.2

JDK 1.2(对外称 Java 2 Platform, Standard Edition, v1.2,代号 Playground)于 1998 年 12 月 8 日发布,是 JDK 1.1 之后的一次「平台级」大版本——类库规模扩大近三倍(约 1520 个类、59 个包),并首次启用 Java 2 品牌。命名与后续演变见 Java 版本历史。 版本号说明 维度 JDK 1.2 对外名称 Java 2 SE 1.2 / JDK 1.2 代号 Playground java.version 1.2.x class major version 46 上一版本 JDK 1.1 下一版本 JDK 1.3 「Java 2」中的 2 指平台代际(1.2 这次大改),并非版本号跳到了 2.0;这一代还拆出了 J2SE / J2EE / J2ME 三个平台方向。完整命名脉络见 Java 版本历史。 概览 语言 strictfp 关键字:约束浮点运算严格遵循 IEEE 754,保证跨平台一致性;这个「严格模式 vs. 默认模式」的区分后来被认为没必要,JDK 17 的 JEP 306 让所有浮点运算恢复为始终严格,strictfp 从此变成没有实际效果的保留字(详见下文「与现代 Java 的关系」) 类库 集合框架(java.util.Collection / List / Set / Map 等):统一的容器体系,ArrayList、HashMap、HashSet 等新增实现;1.0/1.1 的 Vector、Hashtable 被改造为实现 List / Map 接口以便互操作(详见 Collection List Set Map 区别) Swing(javax.swing)并入核心,与 AWT、Java 2D、Accessibility、Drag and Drop 一起构成 JFC(Java Foundation Classes) java.lang.ref:软引用(SoftReference)、弱引用(WeakReference)、虚引用(PhantomReference),配合 ReferenceQueue 提供比 finalize() 更可控的对象回收前/回收后通知机制 Java 2D API(java.awt.geom 等):矢量图形、坐标变换、复杂渲染 拖放 API(java.awt.dnd):跨组件、跨应用的拖放交互 Accessibility API(javax.accessibility):屏幕阅读器等辅助技术支持 Java IDL(org.omg.CORBA):内置 CORBA ORB,是后续 RMI-IIOP(JDK 1.3)的基础 安全模型重写:java.security.Policy / ProtectionDomain 引入细粒度、可配置的权限模型,取代 1.1「Applet 沙箱 vs. 本地代码完全信任」的二元划分 Java Plug-in:让浏览器脱离自带(常常过时)的 JVM,改用独立安装、可控版本的 JRE 运行 Applet JVM 与性能 内置改进版 JIT 编译器——但仍是 Classic VM 自带的实现,不是 HotSpot(HotSpot 此时尚未发布,详见下文「与 HotSpot 的关系」) 默认虚拟机依旧是 Classic VM;HotSpot 要到 JDK 1.3 才成为默认项 1.2 尚未具备(后续版本才加入) 能力 大致引入版本 HotSpot 成为默认 VM 1.3 RMI 可选兼容 CORBA IIOP 1.3 JNDI 并入核心 1.3 assert、NIO、标准正则 1.4 泛型、enum、for-each、JUC 5 集合框架 1.0/1.1 只有 Vector、Hashtable、Stack 等各自为政的容器类,缺少统一抽象。1.2 引入的集合框架用一组接口(Collection、List、Set、Map)加多种实现(ArrayList、LinkedList、HashSet、TreeSet、HashMap、TreeMap)统一了这块: ...

Created 14146-07-14 · Updated 15156-07-15 · 3 min · 536 words · -

JDK 7

JDK 7(Java SE 7)于 2011 年 7 月发布,是 Java 6 之后的一次重要版本更新。相对 JDK 6,主要变化集中在语言语法、NIO 文件 API、并发框架、JVM/GC 和诊断工具几方面。 概览 语言与编译器 钻石操作符(Diamond Operator)<> try-with-resources 自动资源管理 多异常捕获(multi-catch) switch 支持 String 二进制整数字面量(0b 前缀) 数字字面量中的下划线 类库 NIO.2(java.nio.file):Path、Files、目录遍历、WatchService 异步 I/O:AsynchronousSocketChannel 等 并发增强:ForkJoinPool、Phaser、LinkedTransferQueue、ThreadLocalRandom JDBC 4.1 invokedynamic 与 Method Handles(JSR 292,为 JVM 动态语言与后续 Lambda 铺路) JVM 与 GC G1 垃圾收集器(6u14 起实验性提供,7u4 起转为正式支持) 字符串常量池从 PermGen 移至 Java Heap 64 位 HotSpot 默认启用 Compressed Oops 工具与诊断 jcmd 与 Diagnostic Command 框架(见 Attach API) VisualVM 随 JDK 分发 钻石操作符 泛型实例化时,若右侧类型参数可从左侧声明推断,可省略重复的类型参数,写 <>: ...

Created 25256-03-25 · Updated 14146-07-14 · 3 min · 521 words · -

JDK 9

JDK 9(Java SE 9)于 2017 年 9 月 21 日发布。相对 JDK 8,最重要的变化是 Java 平台模块系统(JPMS),以及 java.version 去掉前导 1.(JEP 223)。版本号背景见 Java 版本历史。 版本号说明 维度 JDK 8 JDK 9 java.version 1.8.0_xxx 9、9.0.1 等 里程碑 对外 Java 8、对内仍 1.x 对外与系统属性一致 详见 Java 版本历史。 概览 平台与模块化 Java 平台模块系统(JPMS,Project Jigsaw) jlink:定制运行时镜像 jmod:模块打包格式 基于模块的 Runtime 镜像 语言与 API JShell(REPL) 接口 private 方法 集合工厂方法(List.of、Set.of、Map.of 等) Stream API 增强(takeWhile、dropWhile、iterate) Optional 增强 Process API 改造 VarHandle、Stack-Walking API 多版本 JAR(Multi-Release JARs) HTTP/2 Client(孵化) JVM、GC 与工具 G1 成为默认 GC 移除 JDK 8 标记过时的 GC 组合(JEP 214);CMS 被标记废弃(JEP 291) 统一 JVM 日志(Unified JVM Logging) 从 JDK 中移除 Java EE / CORBA 等模块(改由外部维护) Compact Strings(内部字符串存储优化) 模块系统(JPMS) JDK 9 引入 module-info.java 描述模块依赖与导出包,目标是强封装、可裁剪的运行时: ...

Created 5056-01-05 · Updated 14146-07-14 · 2 min · 421 words · -

JDK 8

JDK 8 相对于 JDK 7 引入的主要特性: Lambda 表达式 函数式接口(Functional Interface) Stream API 方法引用(Method Reference) 默认方法(Default Method) Optional 新的日期时间 API(java.time) Nashorn JavaScript 引擎 Base64 并发增强(CompletableFuture、StampedLock、LongAdder) 重复注解(Repeating Annotations) 类型注解(Type Annotations) PermGen 移除,改用 Metaspace HashMap 内部实现优化:链表转红黑树 GC 组合废弃(JEP 173) Lambda 表达式 Lambda 来自数学中的 λ 演算(Lambda Calculus),是函数式编程的理论基础。函数式编程的核心思想是:函数是"一等公民",可以像普通值一样被传递、赋值、返回。JDK 8 引入 Lambda,本质上是给 Java 这门面向对象语言加入了函数式编程的能力。 Lambda 表达式允许将函数(即 Lambda 表达式本身)作为参数传递,简化匿名内部类的写法。 语法结构 (参数列表) -> 方法体 (参数列表):传入的参数,无参数时写空括号 () ->:箭头操作符,读作 “goes to”,分隔参数列表和方法体 方法体:要执行的逻辑 Runnable 是一个接口(java.lang.Runnable),只有一个无参方法 void run(),因此参数列表为空 ()。 // JDK 7:匿名内部类写法 Runnable r = new Runnable() { @Override public void run() { System.out.println("Hello"); } }; // JDK 8:Lambda 写法,箭头右边等价于 run() 的方法体 Runnable r = () -> System.out.println("Hello"); 将函数作为参数传递 JDK 8 之前,Java 无法直接传递"一段逻辑",只能把逻辑包装成匿名对象再传递。Lambda 表达式让你可以直接把逻辑作为参数传入方法,这就是"将函数作为参数传递"的含义。 ...

Created 25256-04-25 · Updated 14146-07-14 · 3 min · 618 words · -

Xorshift 伪随机数算法

Xorshift 是什么 Xorshift 是 George Marsaglia 在 2003 年发表的一类伪随机数生成算法(PRNG,Pseudo-Random Number Generator),核心思路是反复对一个整数状态做"自己和自己移位后的版本"做异或(XOR),只用异或和移位两种运算就能生成看起来随机的数字序列。 它属于线性反馈移位寄存器(LFSR,Linear Feedback Shift Register)的一种变体,相比传统的线性同余法(LCG,Linear Congruential Generator,也就是很多语言标准库 rand() 背后的算法)不需要乘法和取模,只用位运算,速度快很多;相比更复杂的 Mersenne Twister(java.util.Random 用的不是这个,但 Python、PHP 等语言的默认随机数用的是它),实现更简单、状态更小。 代价是随机质量不如 Mersenne Twister,不能用于密码学场景(不是 CSPRNG,Cryptographically Secure PRNG),但对哈希表、游戏、模拟、需要"看起来随机就行"的场景完全够用,且速度优势明显。 基本算法 最经典的 32 位 xorshift 只需要一个非零的整数状态 x 和三次移位异或: uint32_t xorshift32(uint32_t x) { x ^= x << 13; x ^= x >> 17; x ^= x << 5; return x; } 每次调用把上一次的输出作为下一次的输入(状态),就能生成一串伪随机序列。三个移位量 (13, 17, 5) 不是随便选的——Marsaglia 在论文里给出了一批能保证生成的序列覆盖 2^32 - 1 个非零状态(即最大周期)的移位量组合,(13, 17, 5) 是其中常用的一组。如果移位量选得不好,会导致序列过早循环或者分布不均匀。 64 位状态的版本同理,只是移位量不同: uint64_t xorshift64(uint64_t x) { x ^= x << 13; x ^= x >> 7; x ^= x << 17; return x; } 为什么异或和移位比乘除法快 xorshift 只用异或和移位,LCG 需要乘法,还常常需要取模——这两类操作在 CPU 上的实际开销差距很大,不只是"位运算听起来更底层"这种直觉。 ...

Created 13136-07-13 · Updated 14146-07-14 · 2 min · 321 words · -

LCG 线性同余生成器

LCG 是什么 线性同余生成器(LCG,Linear Congruential Generator)是历史最悠久、实现最简单的一类伪随机数算法(PRNG,Pseudo-Random Number Generator)。1949 年由 D. H. Lehmer 提出雏形,此后几十年里几乎所有编程语言标准库的 rand() 都是它的某个变体。 它的核心是一条递推公式:给定当前状态 seed,用一次乘法、一次加法、一次取模算出下一个状态,同时把这个状态作为输出。相比 Mersenne Twister 随机数算法 和 Xorshift 伪随机数算法,LCG 实现最简单、状态最小,但随机质量也最差,且不是密码学安全的随机数生成器(不是 CSPRNG,Cryptographically Secure PRNG),不能用在密钥生成、令牌生成等安全场景。 递推公式 X(n+1) = (a * X(n) + c) mod m 四个参数决定了一个具体的 LCG: X(0):种子(seed),初始状态 a:乘数(multiplier) c:增量(increment) m:模数(modulus) c = 0 的特例叫乘同余生成器(multiplicative congruential generator,也叫 Lehmer RNG);c != 0 时才是完整的(mixed)LCG。取模运算保证了状态永远落在 [0, m) 区间内,序列迟早会回到某个之前出现过的状态并开始循环——这个循环长度就是"周期",LCG 的周期上限是 m。 参数怎么选:Hull–Dobell 定理 不是随便选四个数字就能得到一个"好"的 LCG。如果参数选得不好,序列可能远没跑到 m 就开始循环,或者呈现明显的规律。Hull–Dobell 定理给出了 c != 0 时序列能达到最大周期 m(即遍历 [0, m) 里所有整数才循环)的充要条件: ...

Created 14146-07-14 · Updated 14146-07-14 · 2 min · 397 words · -

IdentityHashMap:基于引用相等的 Map 实现

java.util.IdentityHashMap 自 JDK 1.4 起存在,表面上实现了 Map 接口,但官方文档明确说它不是通用 Map 实现——判等方式和内部存储结构都和 HashMap 不同,只应该用在明确需要"引用相等"语义的少数场景。 与 HashMap 的核心区别:引用相等而非逻辑相等 HashMap 判断两个 key 是否相同,用的是 equals();IdentityHashMap 用的是 ==,也就是判断是否是同一个对象引用。计算哈希值时也不调用 key 自身的 hashCode(),而是统一用 System.identityHashCode(key)。 Map<String, Integer> hashMap = new HashMap<>(); Map<String, Integer> identityMap = new IdentityHashMap<>(); String a = new String("key"); String b = new String("key"); hashMap.put(a, 1); hashMap.put(b, 2); System.out.println(hashMap.size()); // 1,a.equals(b) 为 true,视为同一个 key identityMap.put(a, 1); identityMap.put(b, 2); System.out.println(identityMap.size()); // 2,a != b,视为两个不同的 key 这个差异不仅体现在 key 上,entrySet()、keySet()、values() 这些视图集合里元素的比较,同样是引用相等语义,而不是 HashMap 视图那种基于 equals() 的语义。 ...

Created 13136-07-13 · Updated 13136-07-13 · 2 min · 391 words · -

Java Random 随机数

Java 里获取随机数的几种方式 在 Java 中,获取随机数广义上有三种途径: 用 System.currentTimeMillis() 拿到当前时间的毫秒数,当作一个"看起来随机"的数字。 用 Math.random(),返回一个 [0, 1) 之间均匀分布的 double 值。 用 java.util.Random 类,功能更完整的专业随机数工具类。 Math.random() 内部其实也是委托给一个 java.util.Random 实例(每个类第一次调用时惰性初始化一个内部单例),所以第 2、3 种本质上是同一套实现。 java.util.Random 的用法 Random 有两种构造方法: Random():用当前时间 System.currentTimeMillis() 作为种子(seed)。 Random(long seed):用指定的 seed 初始化。 产生 Random 对象后,调用 nextInt()、nextLong()、nextFloat()、nextDouble()、nextBoolean()、nextGaussian()(返回符合高斯/正态分布的 double)等方法获得不同类型的随机数。 如果两个 Random 对象用相同的种子、并且以相同的顺序调用相同的方法,它们返回的值会完全相同——因为随机数序列完全由种子和内部算法决定: import java.util.Random; public class TestRandom { public static void main(String[] args) { Random random1 = new Random(100); System.out.println(random1.nextInt()); System.out.println(random1.nextFloat()); System.out.println(random1.nextBoolean()); Random random2 = new Random(100); System.out.println(random2.nextInt()); // 和 random1 完全一样 System.out.println(random2.nextFloat()); System.out.println(random2.nextBoolean()); } } 指定范围内的随机数,可以用取模运算符 %(配合 Math.abs 避免负数): ...

Created 25256-05-25 · Updated 13136-07-13 · 2 min · 244 words · -

Java hashCode 对象哈希值的计算方法

equals 和 hashCode 的约定 Java 规定了 hashCode() 和 equals() 之间的约定: 两个对象用 equals() 判断相等,则它们的 hashCode() 必须相等。 反过来不成立:hashCode() 相等的两个对象,equals() 未必相等(也就是哈希冲突)。 自定义 hashCode() 时必须遵守第 1 条,否则这个对象放进 HashMap、HashSet 之类基于哈希的容器后会出现"明明 equals 相等却查不到"的 bug。第 2 条则说明哈希冲突是被允许的,容器内部(开链法或开放地址法)本来就要处理冲突,具体见 开链法与开放地址法。 Object 默认的 hashCode 是怎么算的 没有重写 hashCode() 的类,用的是 Object 的默认实现,也叫 identity hash code(身份哈希码)。一个常见的误解是"默认哈希值就是对象的内存地址"——这在 HotSpot JVM 里并不准确: 对象在 GC(尤其是分代 GC 的 minor GC)过程中会被移动到不同的内存区域,如果哈希值直接绑定内存地址,对象移动后哈希值就变了,这会破坏"同一个对象的 hashCode 在生命周期内不能变"的规定。 所以 HotSpot 实际上是单独生成、缓存一个哈希值(存在对象头里),和地址无关。生成算法由 JVM 参数 -XX:hashCode=N 控制,N 有 0~6 几种取值,比如 0 是全局的 Park-Miller 随机数生成器,1 是基于地址计算,4 是直接用地址,其余是自增计数器等方案;默认值是 5,用的是 Marsaglia’s xorshift 算法,基于每个线程私有的状态做一次异或位移运算生成伪随机数,和地址、和对象内容都无关。 也就是说,默认的 hashCode() 本质上是一个和对象绑定的伪随机数,只是为了效率碰巧可能和地址相关的实现(-XX:hashCode=1/4)也存在,但不是默认策略。 ...

Created 21216-09-21 · Updated 13136-07-13 · 2 min · 249 words · -

开链法与开放地址法, Separate Chaining and Open Addressing, hashmap

开链法与开放地址法,Separate Chaining and Open Addressing 哈希表在插入元素时,如果两个不同的 key 计算出的哈希值落到了数组的同一个位置,就发生了哈希冲突(hash collision)。解决哈希冲突主要有两种思路:开链法(Separate Chaining)和开放地址法(Open Addressing)。Java 的 HashMap 用的是开链法;Python 的 dict 用的是开放地址法。Java 里也有用开放地址法的例子,比如 IdentityHashMap 和 ThreadLocal.ThreadLocalMap,详见后文「在 Java 中的应用」一节。 开链法,Separate Chaining 开链法是一种二维结构:第一维是一个数组,数组的每个槽位(bucket)挂着一个链表(第二维)。插入元素时,先用 hash 函数算出该 key 应该落在数组的哪个槽位,然后直接把这个 key(连同 value)作为一个节点追加到该槽位的链表上。 当多个不同的 key 计算出相同的槽位时,它们并不会互相覆盖或者被挪到别处,而是全部挂在同一个槽位的链表里,按顺序排列。查找时,先定位到槽位,然后遍历该槽位的链表,逐个比较 key 是否相等,直到找到目标或者遍历到链表末尾。 优点 实现简单,冲突处理直观,槽位之间互不影响。 删除操作非常简单,只需要从链表中把对应节点摘除即可,不会影响其它元素的查找路径。 负载因子(元素个数 / 数组长度)可以大于 1,只是链表会变长,查找退化为线性扫描。 负载因子,Load Factor 负载因子的英文是 load factor,公式为 load factor = 哈希表中总元素个数 / 数组长度(槽位数)。这里的"元素个数"指的是整个哈希表中的元素总数,而不是某一个槽位对应的元素个数;某个槽位挂了多少个元素(链表长度)反映的是 hash 分布是否均匀,是另一个维度的问题。例如数组长度为 16,哈希表中一共存了 12 个元素,此时 load factor 就是 0.75。 开链法下即使 load factor 大于 1(元素个数超过数组长度)也不会出错,只是链表会变长;而开放地址法由于只有一个数组,load factor 不能超过 1(数组会满)。 ...

Created 6066-05-06 · Updated 13136-07-13 · 3 min · 579 words · -

HashMap

历史背景 HashMap 最早在 JDK 1.2(1998 年发布的 Java 2)引入,是 java.util 集合框架(Collections Framework)随 1.2 一起推出的一部分,与 ArrayList、HashSet、Collection/Map 接口等同批加入; 在此之前,JDK 1.0/1.1 只有 Hashtable——线程安全但不允许 null key/value。HashMap 是作为 Hashtable 的非同步、允许 null 的替代品引入的,因此在没有并发访问需求的场景下性能更好;本文后续讨论的实现细节(JDK 1.7 数组 + 链表、JDK 1.8 引入红黑树)都是在这个基础上的持续演进。二者更详细的差异对比见 HashMap 与 Hashtable 的区别。 JDK 1.7 默认容量为 16,负载因子为 0.75。Map 在使用过程中不断的往里面存放数据,当数量达到了 16 * 0.75 = 12 就需要将当前 16 的容量进行扩容,而扩容这个过程涉及到 rehash、复制数据等操作,所以非常消耗性能。 因此通常建议能提前预估 HashMap 的大小最好,尽量的减少扩容带来的性能损耗。 put 方法 public V put(K key, V value) { if (table == EMPTY_TABLE) { inflateTable(threshold); } if (key == null) return putForNullKey(value); int hash = hash(key); int i = indexFor(hash, table.length); for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash, key, value, i); return null; } put 方法执行流程: ...

Created 14146-01-14 · Updated 13136-07-13 · 15 min · 2999 words · -

java basic

java basic JDK 和 JRE 的区别 JRE:Java Runtime Environment 的简称,java 运行时环境,为 java 的运行提供了所需环境。 Java 虚拟机 jfr JDK:Java Development Kit 的简称,java 开发工具包,提供了 java 的开发环境和运行环境。 包含了 JRE 编译器: javac 其他的工具: JavaDoc,Java调试器(jdb), jcmd, jstat, jmap JDK 其实包含了 JRE,同时还包含了编译 java 源码的编译器 javac,还包含了很多 java 程序调试和分析的工具。简单来说:如果你需要运行 java 程序,只需安装 JRE 就可以了,如果你需要编写 java 程序,需要安装 JDK。 http://www.wiloon.com/jdk-jre == vs equals == 对于基本类型和引用类型 == 的作用效果是不同的 基本类型:比较的是值是否相同; 引用类型:比较的是引用是否相同; String x = "string"; String y = "string"; String z = new String("string"); System.out.println(x==y); // true System.out.println(x==z); // false System.out.println(x.equals(y)); // true System.out.println(x.equals(z)); // true 代码解读:因为 x 和 y 指向的是同一个引用,所以 == 也是 true,而 new String()方法则重写开辟了内存空间,所以 == 结果为 false,而 equals 比较的一直是值,所以结果都为 true。 ...

Created 20206-09-20 · Updated 9096-07-09 · 4 min · 715 words · -

2026 W27

女儿上幼儿园的情绪课 这周女儿在排练小班毕业表演,舞蹈动作有点难,加上分离焦虑,早上去幼儿园成了每天要过的一关。周一那天她抗拒得厉害,做了近一个小时心理建设才送进去,迟到了一小时。 我试着用她熟悉的东西帮她面对情绪。她在玩《星之卡比》,一开始连第一关的兔子都怕,现在已经把卡比的装备升到四级了。我就问她:现在还怕第一关的兔子吗?她说不怕了。我说幼儿园跳舞也一样,你手里的"冰淇淋"(舞蹈道具)现在是几级装备?她说是一级。我说今天带着它去跳舞,明天就升到二级,跳舞就没那么难了。她点点头,跟妈妈说了再见,蹦蹦跳跳去幼儿园"升级装备"了。 后面几天肉眼可见地在进步。周三到门口离开妈妈还会有点不开心,安抚一会儿就自己跑进去了。周四早上我在路上问她冰淇淋几级了,她说二级;我说今天再升一级,她纠正我:不对,上午能升一级,下午还能再升一级。大概她的神经网络已经把跳舞和打怪升级关联起来了。 比起替她扛下情绪,更想让她自己有面对情绪的办法。这套比喻能不能一直管用不好说,但这周是有效的。 周六全天带娃,出门时她的"小怪兽钱包"里有些现金。到星海广场,她自己花十块买了一袋海鸥食,又花三十坐了小火车,接着想玩旋转木马,翻钱包发现只剩十三块六了。她开始琢磨:游乐场都是收费三十吗?我说我们去找找十三块能玩哪个。绕了一圈,路过一个转飞船的场地,工作人员大姐答应十三块让她上去坐一圈。让她花自己的压岁钱,她就开始比较各种东西的价格了。 求职 周二收到了一家公司的面试邀请,周五去参加,过程还算顺利,有些问题答得还好,有些答得一般。目前结果未知。 咖啡:蒸气奶测试 某天中午送老婆去公司后,我又带电脑在她楼下的星巴克找了个座位。那天出门晚,担心喝咖啡影响睡眠,又不好久占座位,翻了半天菜单,看到一项叫"蒸气奶"。跟 AI 确认了一下,蒸气奶大致等于拿铁去掉咖啡。之前喝拿铁不会触发乳糖不耐,蒸气奶应该也安全。选项里有牛奶也有燕麦奶,我选了普通牛奶——顺便测试一下:蒸气乳化的过程据说能分解一部分乳糖,气泡也可能减缓乳糖进入肠道的速度。结果跟之前喝拿铁一样,喝完没有触发乳糖不耐的症状。

Created 5056-07-05 · Updated 8086-07-08 · 1 min · 12 words · -

Hashtable 哈希表原理与哈希算法对比

Hash 表也称散列表,也有直接译作哈希表,是一种特殊的数据结构,同数组、链表以及二叉排序树等相比较有很明显的区别:它能够快速定位到想要查找的记录,而不是与表中存在的记录的关键字进行比较来进行查找。这源于 Hash 表设计的特殊性,它采用函数映射的思想将记录的存储位置与记录的关键字关联起来,从而能够很快速地进行查找。 java.util.Hashtable 是 Java 中最早的哈希表实现,自 JDK 1.0(1996 年)就已存在。在现代 Java 中它依然保留,但仅用于向后兼容旧代码:非线程安全场景推荐使用 HashMap,需要线程安全时推荐使用 ConcurrentHashMap,两者性能和易用性都更好,Hashtable 已不建议在新代码中使用。 Hash 表的设计思想 对于一般的线性表,比如链表,如果要存储联系人信息: 张三 13980593357 李四 15828662334 王五 13409821234 张帅 13890583472 可能会设计一个结构体包含姓名、手机号码这些信息,然后把 4 个联系人的信息存到一张链表中。当要查找“李四 15828662334”这条记录是否在这张链表中,或者想要得到李四的手机号码时,可能会从链表的头结点开始遍历,依次将每个结点中的姓名同“李四”进行比较,直到查找成功或者失败为止,这种做法的时间复杂度为 O(n)。即使采用二叉排序树进行存储,也最多为 O(logn)。假设能够通过“李四”这个信息直接获取到该记录在表中的存储位置,就能省掉中间关键字比较的这个环节,复杂度直接降到 O(1)。Hash 表就能够达到这样的效果。 Hash 表采用一个映射函数 f : key -> address 将关键字映射到该记录在表中的存储位置,从而在想要查找该记录时,可以直接根据关键字和映射关系计算出该记录在表中的存储位置。通常情况下,这种映射关系称作 Hash 函数,而通过 Hash 函数和关键字计算出来的存储位置(注意这里的存储位置只是表中的存储位置,并不是实际的物理地址)称作 Hash 地址。比如上述例子中,假如联系人信息采用 Hash 表存储,则当想要找到“李四”的信息时,直接根据“李四”和 Hash 函数计算出 Hash 地址即可。 hashCode 与 equals 的关系 有人会说,可以直接根据 hashCode 值判断两个对象是否相等吗?肯定是不可以的,因为不同的对象可能会生成相同的 hashCode 值。虽然不能根据 hashCode 值判断两个对象是否相等,但是可以直接根据 hashCode 值判断两个对象不等——如果两个对象的 hashCode 值不等,则必定是两个不同的对象。如果要判断两个对象是否真正相等,必须通过 equals 方法。 ...

Created 15156-06-15 · Updated 4046-07-04 · 1 min · 184 words · -

HashMap 遍历: entrySet vs keySet

两种遍历方式 HashMap 常见的遍历方式有两种:entrySet() 和 keySet()。两者最终都能拿到全部键值对,但性能有差别。 entrySet(推荐) Map<String, String> map = new HashMap<>(); Iterator<Map.Entry<String, String>> iter = map.entrySet().iterator(); while (iter.hasNext()) { Map.Entry<String, String> entry = iter.next(); String key = entry.getKey(); String val = entry.getValue(); } entry 中已经同时持有 key 和 value,取值时不需要再回查一次 map。 keySet Map<String, String> map = new HashMap<>(); Iterator<String> iter = map.keySet().iterator(); while (iter.hasNext()) { String key = iter.next(); String val = map.get(key); // extra lookup for every key } keySet() 只拿到 key 的集合,遍历时还要对每个 key 再调用一次 map.get(key) 才能取到 value。 ...

Created 2026-04-02 · Updated 4046-07-04 · 1 min · 210 words · -

HashMap Initialization 初始化写法

不同的初始化写法在可读性、序列化兼容性和性能上有一些差异,本文记录双括号初始化写法及其注意事项。本文原本是 HashMap 的一部分,因主题独立而拆分为单独文章。 双括号初始化 HashMap 是一种常用的数据结构,一般用来做数据字典或者 Hash 查找的容器。普通写法一般会这么初始化: HashMap<String, String> map = new HashMap<String, String>(); map.put("Name", "June"); map.put("QQ", "2572073701"); 看完这段代码,很多人都会觉得这么写太啰嗦了,对此,还有一种更紧凑的写法: HashMap<String, String> map = new HashMap<String, String>() { { put("Name", "June"); put("QQ", "2572073701"); } }; 看起来优雅了不少,一步到位。这里的双括号到底是什么意思呢?看看下面的代码就知道了: public class Test { /* private static HashMap<String, String> map = new HashMap<String, String>() { { put("Name", "June"); put("QQ", "2572073701"); } }; */ public Test() { System.out.println("Constructor called"); // constructor called } static { System.out.println("Static block called"); // static block called } { System.out.println("Instance initializer called"); // instance initializer called } public static void main(String[] args) { new Test(); System.out.println("======================="); new Test(); } } 输出: ...

Created 3036-07-03 · Updated 3036-07-03 · 2 min · 405 words · -

ConcurrentHashMap

ConcurrentHashMap ConcurrentHashMap 是一个线程安全的Hash Table,它的主要功能是提供了一组和HashTable功能相同但是线程安全的方法。ConcurrentHashMap可以做到读取数据不加锁,并 且其内部的结构可以让其在进行写操作的时候能够将锁的粒度保持地尽量地小,不用对整个ConcurrentHashMap加锁。 ConcurrentHashMap 由 JDK 5(2004 年)随 java.util.concurrent 包一起引入(JSR 166,Doug Lea 主导),是最早一批加入的并发容器之一,用于替代性能受限的 Hashtable 和外部同步的 HashMap。 与 HashMap 的区别 ConcurrentHashMap 和 HashMap 都基于数组 + 链表(JDK 1.8 后含红黑树)实现,核心区别在于线程安全性和并发设计: 维度 HashMap ConcurrentHashMap 线程安全 否,多线程并发写会导致死循环(1.7 扩容)或数据丢失 是,专为并发场景设计 加锁粒度 无锁(需要外部自行同步,如 Collections.synchronizedMap) 1.7:Segment 分段锁;1.8:CAS + 对单个桶 synchronized,粒度远小于对整表加锁 null 支持 key、value 都允许 null key、value 都不允许 null(避免 get() 返回 null 时无法区分键不存在和值为 null 的二义性) 读操作 无锁,但非并发安全 无锁(val/next 用 volatile 修饰保证可见性),且并发安全 典型场景 单线程或已有外部同步的场景 多线程共享同一个 Map 的场景 简单说:HashMap 用来替代非线程安全场景下的 Hashtable,ConcurrentHashMap 用来替代线程安全场景下的 Hashtable——二者是 Hashtable 拆分出的两条演进路线,而不是互相替代的关系。历史背景与 Hashtable 的详细对比见 HashMap 与 Hashtable 的区别。 ...

Created 12126-08-12 · Updated 3036-07-03 · 8 min · 1620 words · -

HashMap 与 Hashtable 的区别

HashMap 与 Hashtable 的区别 Hashtable 的应用非常广泛,HashMap 是新框架中用来代替 Hashtable 的类,也就是说建议使用 HashMap,不要使用 Hashtable。可能你觉得 Hashtable 很好用,为什么不用呢?这里简单分析它们的区别。 Hashtable 的方法是同步的,HashMap 未经同步,所以在多线程场合要手动同步 HashMap,这个区别就像 Vector 和 ArrayList 一样。 Hashtable 不允许 null 值(key 和 value 都不可以),HashMap 允许 null 值(key 和 value 都可以)。 HashMap 去掉了 Hashtable 的 contains(Object value) 方法,但是加上了 containsValue() 和 containsKey() 方法。 Hashtable 使用 Enumeration,HashMap 使用 Iterator。以上只是表面的不同,它们的实现也有很大的不同。 Hashtable 中 hash 数组默认大小是 11,增加的方式是 old * 2 + 1。HashMap 中 hash 数组的默认大小是 16,而且一定是 2 的指数。 哈希值的使用不同,Hashtable 直接使用对象的 hashCode: int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; HashMap 重新计算 hash 值,并用位与运算代替求模: ...

Created 21216-09-21 · Updated 3036-07-03 · 2 min · 337 words · -