HashMap

本文源码基于 JDK 1.8.0_101 。

在 JDK 1.8 之前,HashMap 采用 槽数组 + 单链表 来实现;

在 JDK 1.8 开始采用 槽数组 + 单链表 + 红黑树 来实现。

1. 为什么引入红黑树?

解决哈希冲突时、链表过长导致访问效率低下的问题。

为什么是红黑树不是其他树:
二叉排序树在极端情况下会退化成线性结构。
平衡二叉树(AVL树)是严格平衡树,在增加或删除节点时,旋转次数比红黑树要多。红黑树的统计性能高于 AVL 树。

红黑树特性,RBT树上的每个节点,都要遵循下面的规则:
① 每个节点都是红色或者黑色;
② 根节点必须始终是黑色;
③ 没有两个相邻的红色节点;
④ 对每个结点,从该结点到其子孙节点的所有路径上包含相同数目的黑结点。

继续阅读

Java8 Striped64 和 LongAdder

数据 striping

根据维基百科的这段说明

In computer data storage, data striping is the technique of segmenting logically sequential data, such as a file, so that consecutive segments are stored on different physical storage devices.

Striping is useful when a processing device requests data more quickly than a single storage device can provide it. By spreading segments across multiple devices which can be accessed concurrently, total data throughput is increased. It is also a useful method for balancing I/O load across an array of disks. Striping is used across disk drives in redundant array of independent disks (RAID) storage, network interface controllers, different computers in clustered file systems and grid-oriented storage, and RAM in some systems.

数据 striping 就是把逻辑上连续的数据分为多个段,使这一序列的段存储在不同的物理设备上。通过把段分散到多个设备上可以增加访问并发性,从而提升总体的吞吐量。

Striped64

JDK 8 的 java.util.concurrent.atomic 下有一个包本地的类 Striped64 ,它持有常见表示和机制用于类支持动态 striping 到 64bit 值上。

设计思路

这个类维护一个延迟初始的、原子地更新值的表,加上额外的 “base” 字段。表的大小是 2 的幂。索引使用每线程的哈希码来masked。这个的几乎所有声明都是包私有的,通过子类直接访问。

表的条目是 Cell 类,一个填充过(通过 sun.misc.Contended )的 AtomicLong 的变体,用于减少缓存竞争。填充对于多数 Atomics 是过度杀伤的,因为它们一般不规则地分布在内存里,因此彼此间不会有太多冲突。但存在于数组的原子对象将倾向于彼此相邻地放置,因此将通常共享缓存行(对性能有巨大的副作用),在没有这个防备下。

部分地,因为Cell相对比较大,我们避免创建它们直到需要时。当没有竞争时,所有的更新都作用到 base 字段。根据第一次竞争(更新 base 的 CAS 失败),表被初始化为大小 2。表的大小根据更多的竞争加倍,直到大于或等于CPU数量的最小的 2 的幂。表的槽在它们需要之前保持空。

一个单独的自旋锁(“cellsBusy”)用于初始化和resize表,还有用新的Cell填充槽。不需要阻塞锁,当锁不可得,线程尝试其他槽(或 base)。在这些重试中,会增加竞争和减少本地性,这仍然好于其他选择。

继续阅读

Java 8 IO/NIO 增强

更便捷的文本行处理

BufferedReader 提供了一个lines()方法用于返回文本行的流Stream<String>Files类也提供了一个同名的方法lines(Path, Charset)返回文本行的流,在返回的流上结合Lambda可以进行一序列便捷的处理。

System.out.println("\nBufferedReader.lines");
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream("text.txt")));
bufferedReader
          .lines()
          .filter(line -> line.length() > 2)
          .filter(line -> line.matches("\\d+"))
          .forEach(line -> System.out.println(line));

System.out.println("\nFiles.lines");
Stream<String> stream = Files.lines(Paths.get("text.txt"), Charset.forName("utf8"));
stream.filter(line -> line.length() > 5)
          .forEach(line -> System.out.println(line));

在Java SE 8 b116的实现里,Stream是继承自AutoCloseable接口,所以可以结合try-with-resourse机制来确保资源的关闭。

目录遍历

Files新增了3个方法用于遍历目录:Files.list(Path), Files.walk(Path, int, FileVisitOption...), Files.walk(Path, FileVisitOption...)

Files.list方法只遍历当前目录下的文件和目录,Files.walk方法遍历指定目录下的所有文件和目录,除非指定了最大深度。
第一个walk方法的int类型参数用于指定遍历的最大深度。

System.out.println("\nFiles.list");
Files.list(Paths.get("."))
          .filter(path -> !path.toString().endsWith(".iml"))
          .forEach(path -> System.out.println(path));

System.out.println("\nFiles.walk");
Files.walk(Paths.get("."), 3, FileVisitOption.values())
          .filter(path -> path.toString().startsWith(".\\src"))
          .forEach(path -> System.out.println(path));

System.out.println("\nFiles.walk 2");
Files.walk(Paths.get("."), FileVisitOption.values())
     .filter(path -> path.toString().endsWith(".java"))
     .forEach(path -> System.out.println(path));

文件查找

查找的功能其实跟文件的遍历差不多,多了一个匹配器。

System.out.println("\nFiles.find:");
BiPredicate<Path, BasicFileAttributes> matcher = (path, attr) -> path.endsWith(Paths.get("Collec"));
Files
          .find(Paths.get("."), 3,  matcher, FileVisitOption.values())
          .forEach(path -> System.out.println(path.toString()));

UncheckedIOException

由于Iterator/Stream的方法签名不允许抛出IOException,当进行IO操作出现异常时,需要通过不受检查的异常类来传递这个异常信息。UncheckedIOException就是做这个的。

总的来说,io包变化不大,主要是结合Lambda进行一些改进,很多IO方面的改进已经在Java 7里提供了。


欢迎关注我的微信公众号: coderbee笔记,可以更及时回复你的讨论。

Java 8 之 java.time 包

包概述

java.time 包是在JDK8新引入的,提供了用于日期、时间、实例和周期的主要API。

java.time包定义的类表示了日期-时间概念的规则,包括instants, durations, dates, times, time-zones and periods。这些都是基于ISO日历系统,它又是遵循 Gregorian规则的。

所有类都是不可变的、线程安全的。

继续阅读