前言
聊了很长时间逻辑性非常强的Spring框架,这篇文章来些轻松点的内容:聊聊JDK基础类库中那些你可能不知道的工具。
正所谓玩好JDK,面试不用愁。那么JDK好掌握吗,答案是非常难。
大家平时理解的JDK可能就只是指的它的基础类库部分,但其实此处可扫盲一下。JDK主要包含有如下三个部分:
- Java运行时环境(JRE),说白了就是JVM
- Java的基础类库(它数量可观,功能强大,覆盖面广。对开发者来说是最为中重要的部分)
- Java的开发工具(比如javac、jmap、jconsole、jstack、jvisualvm等等)
本文将着眼于第二部分:基础类库,并且讲解的也只还是它的冰山一角,因为本文主要还是着眼于我认为的(本人水平有限)还能稍微比较常用的一些工具。
工具们介绍
下面介绍的工具,大都来自于java.util包。其实之前也有好几篇相关的工具类介绍文章。
为何专门篇幅介绍工具?因为我认为:工欲善其事必先利其器。你掌握的工具越多,你才能做好更多的事。
【小家java】聊聊Java中的java.util.Arrays类和java.util.Collections工具类
【小家Java】Java第二API之apache的commons-lang3工具包史上最完整的讲解(书写优雅代码必备工具)
【小家java】Java中Apache Commons-lang3提供的DateUtils等时间、日期工具类
EnumMap和EnumSet
这两个哥们都是JDK1.5提供的。
EnumMap
EnumMap它也属于Map体系的东西,该类是专门针对枚举类设计的一个Map集合类。集合中的所有键必须是同一个枚举类的实例,它的key为枚举元素,value自定义。
其实有小伙伴包括我也这样疑问过,我们都可以自己使用Map来实现,为何要使用EnumMap呢?
答案是:它的性能高。因为它的内部是**用数组的数据结构**来维护的!
使用Demo:
public enum Color { RED, BLACK, WHITE, GREEN } public class Main { public static void main(String[] args) { // 它没有空构造,必须制定枚举class类型。当然构造函数也可以接收一个Mapnew EnumMap<>(EnumMap/Map); Map<Color, String> map = new EnumMap<>(Color.class); map.put(Color.RED, "红色"); map.put(Color.BLACK, "黑色"); map.put(Color.WHITE, "白色"); map.put(Color.WHITE, "白色"); // 故意重复放一个 // 遍历:发现EnumMap它的存储是有序的 map.forEach((k, v) -> System.out.print(k + ":" + v + " ")); System.out.println(); // 可以看到,它并不会发生并发修改异常ConcurrentModificationException map.forEach((k, v) -> { if (k == Color.WHITE) { map.put(Color.GREEN, "绿色"); } System.out.print(k + ":" + v + " "); }); // key不允许为null java.lang.NullPointerException map.put(null,"未知"); } }
运行结果如下:
RED:红色 BLACK:黑色 WHITE:白色 RED:红色 BLACK:黑色 WHITE:白色 GREEN:绿色 红色 Exception in thread "main" java.lang.NullPointerException at java.util.EnumMap.typeCheck(EnumMap.java:743) at java.util.EnumMap.put(EnumMap.java:267) at java.util.EnumMap.put(EnumMap.java:79) at com.fsx.maintest.Main.main(Main.java:40)
从上示例对EnumMap总结出以下几点:
- EnumMap是有序的,这个顺序是按照你枚举类的定义顺序走的
- EnumMap可以一边遍历一边修改,不会抛ConcurrentModificationException异常
- EnumMap的key不允许为null
- EnumMap是线程不安全的(若需要安全可以使用Collections#synchronizedMap来一下子)
- EnumMap效率高,所有操作都是常量时间。(因为底层是数组)
从源码处稍微了解一下为何它的效率高?
// @since 1.5 继承自AbstractMap,所以它也能够当作Map来使用 public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V> implements java.io.Serializable, Cloneable { // 所有key都必须这个类型 private final Class<K> keyType; // 简单的说它把所有的key(枚举)都缓存起来 此处用的数组 private transient K[] keyUniverse; // 所有的值 也是数组 它的length和keyUniverse是相同的 private transient Object[] vals; // 它的get方法,直接根据ordinal()去数组找了 这就是它快最为核心的原因 public V get(Object key) { return (isValidKey(key) ? unmaskNull(vals[((Enum<?>)key).ordinal()]) : null); } }
它的get方法,直接使用的ordinal()
去数组里拿值,那可不效率高吗。
最后介绍:EnumMap
还有一种用法是被继承:
public class ColorMap extends EnumMap<Color, String> { public ColorMap() { super(Color.class); } } public class Main { public static void main(String[] args) { Map<Color, String> map = new ColorMap(); map.put(Color.BLACK, "黑色"); System.out.println(map.get(Color.BLACK)); //黑色 } }
EnumSet
EnumSet
是一个专为枚举设计的集合类,EnumSet
中的所有元素都必须是指定枚举类型的枚举值,该枚举类型在创建EnumSet
时显式或隐式地指定。
同样的我们使用它的原因是:效率高(EnumSet
比HashSet
更快)。
使用Demo:
public class Main { public static void main(String[] args) { Set<Color> set = EnumSet.allOf(Color.class); System.out.println(set); //[RED, BLACK, WHITE, GREEN] set = EnumSet.noneOf(Color.class); System.out.println(set); //[] // 添加元素 set.add(Color.RED); set.add(Color.BLACK); System.out.println(set); //[RED, BLACK] // 自己人工指定 set = EnumSet.of(Color.WHITE, Color.BLACK, Color.WHITE); System.out.println(set); //[BLACK, WHITE] 请注意这个顺序并不是上面add的顺序 毕竟自己去重了 // 根据区间来创建 set = EnumSet.range(Color.BLACK, Color.GREEN); //set = EnumSet.range(Color.GREEN, Color.BLACK); // 这样抛出异常了, java.lang.IllegalArgumentException: GREEN > BLACK System.out.println(set); //[BLACK, WHITE, GREEN] 可以看到含头含尾的 // 相当于取差集的意思~ System.out.println(EnumSet.complementOf((EnumSet) set)); //[RED] // copyOf方法克隆一个(请注意是克隆,并不是返回的一个视图) System.out.println(EnumSet.copyOf((EnumSet) set)); //[BLACK, WHITE, GREEN] } }
输出结果:
[RED, BLACK, WHITE, GREEN] [] [RED, BLACK] [BLACK, WHITE] [BLACK, WHITE, GREEN] [RED] [BLACK, WHITE, GREEN]
EnumSet是个抽象类,所以构建它只能通过static方法。它通过内建的实现类RegularEnumSet来保证高效率,下面可以简单看看它为何这么优秀的原因。
RegularEnumSet #add()方法源码如下:
// @since 1.5 public abstract class EnumSet<E extends Enum<E>> extends AbstractSet<E> implements Cloneable, java.io.Serializable { ... } //@since 1.5 访问权限非public 属于内建类 class RegularEnumSet<E extends Enum<E>> extends EnumSet<E> { public boolean add(E e) { typeCheck(e); long oldElements = elements; elements |= (1L << ((Enum<?>)e).ordinal()); return elements != oldElements; } }
这个实现非常非常非常的高大上:add方法只是对长整型数据element做了一个操作而已,也就是说EnumSet实际上将枚举值ordinal()保存在一个长整型long数据上,每个枚举值占用一bit。
我们知道Long型是64bit,那枚举值数量超过64咋办呢? 其实这个时候JDK就会用EnumSet的另一个实现JumboEnumSet
实际情况是:试问一下,谁的枚举值能定义64个这么多???拉出去斩了???
另还注意这里指的是64bit,也就是说是8byte。(计算机中一个字节(byte)占8位(bit))
总而言之,EnumSet的实现是非常高级,效率也是非常的高的。
StringTokenizer和StringJoiner
StringTokenizer
Java中的StringTokenizer类用于将字符串分解为标记,效果类似split,但split方法@since 1.4,而StringTokenizer 1.0版本就有了。
使用Demo:
public class Main { public static void main(String[] args) { // 它只能靠构造函数把分隔符传进去 // 若不指定分隔符,默认使用" \t\n\r\f"去分割 StringTokenizer st1 = new StringTokenizer("Hello Geeks How are you", " "); while (st1.hasMoreTokens()) System.out.println(st1.nextToken()); StringTokenizer st2 = new StringTokenizer("JAVA : Code : String", " :"); while (st2.hasMoreTokens()) System.out.println(st2.nextToken()); StringTokenizer st3 = new StringTokenizer("JAVA : Code : String", " :", true); while (st3.hasMoreTokens()) System.out.println(st3.nextToken()); } }
StringJoiner
:拯救字符串拼接
它是JDK8新增的一个工具类,位于java.util
包下。那么为何JDK8要新增这样一个类呢?原因是之前的StringBuilder
太死板了,不支持分隔。
比如我们经常会有这样的需求:把一个字符串数组用,分隔开,但是最后一个不要逗号?
public class Main { public static void main(String[] args) { String[] strs = {"wo", "ai", "ni"}; // 使用原始的StringBuider方式~ StringBuilder sb = new StringBuilder(); Arrays.stream(strs).forEach(s -> { sb.append(s); sb.append(","); }); System.out.println(sb.substring(0, sb.lastIndexOf(","))); //wo,ai,ni // String内置的工具类方式 它底层还是使用的StringBuilder System.out.println(StringUtils.arrayToDelimitedString(strs, ",")); //wo,ai,ni //使用StringJoiner StringJoiner sj = new StringJoiner(","); Arrays.stream(strs).forEach(x -> sj.add(x)); System.out.println(sj); //wo,ai,ni } }
StringJoiner它主要就是掌握构造函数+add这个方法,其余方法不常用就不介绍了~
虽然它最终实现还是依赖StringBuilder,但是它的实现还是很高效的,特别是有一些小技巧比如对前缀、后缀的处理~