Java SE 8:尺度库加强
副标题#e#
Lambda表达式是Java SE 8的焦点成果,大部门的改造都环绕lambda表达式展开。(Jigsaw项目已经被推迟到Java SE 9。)关于lambda表达式的内容,已经在上一篇文章中举办了说明。这篇文章主要先容Java SE 8中包括的其他Java尺度库的加强。
并行排序
跟着多核CPU的风行,Java平台的尺度库实现也尽大概操作底层硬件平台的本领来提高机能。Java SE 7中引入了Fork/Join框架作为一个轻量级的并行任务执行引擎。Java SE 8把Fork/Join框架用到了尺度库的一些要领的实现中。较量典范的是java.utils.Arrays类中新增的parallelSort要领。与已有的sort要领差异的是,parallelSort要领利用Fork/Join框架来实现。在多核CPU平台上的机能更好。下面的代码对包括1亿个整数的数组别离利用parallelSort和sort举办排序。
Random random = new Random(); int count = 100000000; int[] array = new int[count]; Arrays.parallelSetAll(array, (index) -> random.nextInt()); int[] copy = new int[count]; System.arraycopy(array, 0, copy, 0, array.length); Arrays.parallelSort(array); Arrays.sort(copy);
在本人的4核CPU的平台上,parallelSort和sort要领的耗时别离是7112毫秒和16777毫秒。所以parallelSort要领的机能要好不少。不外parallelSort要领只在数据量较大时有较量明明的机能晋升。当数据量较小时,Fork/Join框架自己所带来的特别开销足以抵消它带来的机能晋升。
荟萃批量数据操纵
在Java应用的开拓中,对荟萃的操纵是较量常见的。不外在Java SE 8之前的Java尺度库中,对荟萃所能举办的操纵较量有限,根基上都环绕荟萃遍向来展开。相对付其他编程语言来说,Java尺度库在这一块是较量弱的。Java SE 8中lambda表达式的引入以及尺度库的加强改造了这种状况。详细来说表此刻两个方面上的改造:第一个方面是对荟萃的操纵方法上。得益于默认要领的引入,Java荟萃框架中的接口可以举办更新,添加了更多有用的操纵方法,即凡是所说的“filter/map/reduce”等操纵。第二个方面是对荟萃的操纵逻辑的暗示方法上。新添加的操纵方法利用了java.util.function包中的新的函数式接口,可以很利便地利用lambda表达式来暗示对荟萃的处理惩罚逻辑。这两个方面团结起来,获得的是越发直观和简捷的代码。
新的荟萃批量处理惩罚操纵的焦点是新增的java.util.stream包,个中最重要的是java.util.stream.Stream接口。Stream接口的观念雷同于Java I/O库中的流,暗示的是一个支持顺序和并行操纵的元素的序列。在该序列上可以举办差异的转换操纵。序列中包括的元素也可以被消费以发生所需的功效。Stream接口所暗示的只是操纵层面上的抽象,与底层的数据存储并没有干系。凡是的利用方法是从荟萃中建设出Stream接口的工具,再举办各类差异的转换操纵,最后消费操纵执行的功效。
Stream接口中包括的操纵分成两类:第一类是对序列中元素举办转换的中间操纵,如filter和map等。这类中间操纵是延迟举办的,可以级联起来。第二类是消费序列中元素的终止操纵,如forEach和count等。当对一个Stream接口的工具执行了终止操纵之后,该工具无法被再次处理惩罚。这点切合一般意义上对付“流”的领略。下面的代码给出了Stream接口中的filter、map和reduce操纵的根基利用方法。Stream接口中的要领大量利用了函数式接口,可以用lambda表达式很利便地举办操纵。
IntStream.range(1,10).filter(i -> i % 2 == 0).findFirst().ifPresent(System.out::println); //保存偶数并输出第一个元素IntStream.range(1,10).map(i -> i * 2).forEach(System.out::println); //所有元素乘以2并输出
int value = IntStream.range(1, 10).reduce(0, Integer::sum); //求和
Stream接口的reduce操纵还支持一种越发巨大的用法,如下面的代码所示:
List<String> fruits = Arrays.asList(new String[] {"apple", "orange", "pear"}); int totalLength = fruits.stream().reduce(0, (sum, str) -> sum + str.length(), Integer::sum); //字符串长度的总和
这种方法的reduce要领需要3个参数,别离是初始值、累积函数和组合函数。初始值是reduce操纵的起始值;累积函数把部门功效和新的元素累积成新的部门功效组合函数则把两个部门功效组合成新的部门功效,最后发生最终功效。这种形式的reduce操纵凡是可以简化成一个map操纵和别的一个简朴的reduce操纵,如下面的代码所示。两种方法的结果是一样的,不外下面的方法越发容易领略一些。
int totalLength = fruits.stream().mapToInt(String::length).reduce(0, Integer::sum);
别的一种非凡的reduce操纵是collect操纵。它与reduce的差异之处在于,collect操纵的进程中所举办的是对一个功效工具举办修改操纵。这样可以制止不须要的工具建设,提高机能。下面代码中的功效是一个StringBuilder类的工具。
StringBuilder upperCase = fruits.stream().collect(StringBuilder::new, (builder, str) -> builder.append(str.substring(0, 1).toUpperCase()), StringBuilder::append); //字符串首字母大写并毗连
#p#分页标题#e#
Stream接口中的操纵可以是顺序执行或并行执行的。这是在Stream接口的工具建设时所确定的。好比Collection接口提供了stream和parallelStream要领来建设两种差异执行方法的Stream接口的工具。这两种差异的方法是可以切换的,通过Stream接口的sequential和parallel要领就可以完成。
日期和时间
Java尺度库中的日期和时间处理惩罚API一直为开拓人员所诟病。大大都开拓人员会选择Joda Time这样的第三方库来举办替代。JSR 310作为Java SE 8的一部门,从头界说了新的日期和时间API,警惕了已有第三方库中的最佳实践。I界说在java.time包中的新的日期和时间API基于尺度的ISO 8601日历系统。
在新的日期和时间API中,焦点的类是LocalDateTime、OffsetDateTime和ZonedDateTime。LocalDateTime类暗示的是ISO 8601日历系统中不带时区的日期和时间信息。OffsetDateTime类在根基的日期和时间基本上增加了与UTC的偏移量。ZonedDateTime类则加上了时区的相关信息。下面的代码给出了日期和时间API的根基用法,包罗对日期和时间的修改、输出息争析。
查察本栏目
LocalDateTime.now().plusDays(3).minusHours(1).format(DateTimeFormatter .ISO_LOCAL_DATE_TIME); //输出日期和时间 ZonedDateTime.now().withZoneSameInstant(ZoneId.of("GMT+08:00")).format (DateTimeFormatter.ISO_ZONED_DATE_TIME); //输出日期、时间和时区 DateTimeFormatter.ofPattern("yyyy MM dd").parse("200101 25"). query(TemporalQuery.localDate()); //日期的理会
除了上述3个类之外,尚有几个值得一提的帮助类。
#p#副标题#e#
Instant:暗示时间线上的一个点。当措施中需要记录时间戳时,应该利用该类。Instant类暗示的时间雷同“2013-07-22T23:18:35.743Z“。
Duration:暗示准确的基于时间的隔断。好比Duration.of(30, ChronoUnit.SECONDS)可以获取30秒的隔断。需要留意的是,Duration类的工具只能从准确的时距离断建设出来,如秒、小时和天等。在这里,一天暗示准确的24小时。而月份和年是不能利用的,因为它们不能暗示准确的隔断。
Period:与Duration类相对应的Period类暗示的是基于日期的隔断,只能利用年/月/日作为单元。Period类在计较时会思量夏令时等因素,适合于计较展示给最终用户的内容。
Clock:暗示包括时区信息的时钟,可以获取当前日期和时间。如LocalDateTime.now(Clock.system(ZoneId.of("GMT+08:00")))暗示的是当前的北京时间。Clock类的一个重要浸染是简化测试。在测试时可以指定差异时区的时钟来举办模仿。
其他更新
除了上面提到的几个较量大的更新之前,尚有一些小的窜改。
Base64编码
Base64编码在Java应用开拓中常常会用到,好比在HTTP根基认证中。在Java SE 8之前,需要利用第三方库来举办Base64编码与解码。Java SE 8增加了java.util.Base64类举办编码息争码。下面的代码给出了简朴的示例。
Base64.Encoder encoder = Base64.getEncoder(); String encoded = encoder.encodeToString("username:password".getBytes()); Base64.Decoder decoder = Base64.getDecoder(); String decoded = new String(decoder.decode(encoded));
并发处理惩罚
Java SE 8进一步加强了并发处理惩罚的相关API。在java.util.concurrent.atomic中新增了LongAccumulator、LongAdder、DoubleAccumulator和DoubleAdder等几个类。这几个类用来在多线程的环境下更新某个Long或Double范例的变量。下面的代码给出了LongAccumulator类的利用示例。
public class ConcurrentSample { public static void main(String[] args) throws Exception { ConcurrentSample sample = new ConcurrentSample(); LongAccumulator accumulator = new LongAccumulator(Long::max, Long.MIN_VALUE); for (int i = 0; i < 100; i++) { sample.newThread("Test thread - " + i, accumulator); } System.out.println(accumulator.longValue()); } private void newThread(final String name, final LongAccumulator accumulator) throws Exception { Thread thread = new Thread(() -> { Random random = new Random(); int value = random.nextInt(5000); System.out.println(String.format("%s -> %s", Thread.currentThread ().getName(), value)); accumulator.accumulate(value); }, name); thread.start(); thread.join(); } }
#p#分页标题#e#
当LongAccumulator类的工具上的accumulate要领被挪用时,参数中的值会通过Long类的max要领举办较量,所获得的功效作为LongAccumulator类的工具的当前值。颠末多次累积操纵之后,最终的功效是所有挪用操纵中提供的最大值。
ConcurrentHashMap类获得了较量大的更新,添加了许多实用的要领,如compute要领用来举办值的计较,merge要领用来举办键值的归并,search要领用来举办查找等。这使得ConcurrentHashMap类可以很利便的建设缓存系统。