今日特价

/

java8新特性

  • 1. Lambda 表达式 - Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法中。
  • 2. 方法引用 - 方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
  • 3. 默认方法 - 默认方法就是一个在接口里面有了一个实现的方法。
  • 4. Stream API - 新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。
  • 5. Date Time API - 加强对日期与时间的处理。
  • 6. Optional 类 - Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。
  • 7. Nashorn, JavaScript 引擎 - Java 8提供了一个新的Nashorn javascript引擎,它允许我们在JVM上运行特定的javascript应用。

1.Lambda表达式

Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。

Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。

使用 Lambda 表达式可以使代码变的更加简洁紧凑。


语法

lambda 表达式的语法格式如下:

  1. (parameters) -> expression
  2. (parameters) ->{ statements; }

以下是lambda表达式的重要特征:

  • 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
  • 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
  • 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
  • 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。

Lambda 表达式实例

Lambda 表达式的简单例子:

  1. // 1. 不需要参数,返回值为 5
  2. () -> 5
  3. // 2. 接收一个参数(数字类型),返回其2倍的值
  4. x -> 2 * x
  5. // 3. 接受2个参数(数字),并返回他们的差值
  6. (x, y) -> x y
  7. // 4. 接收2个int型整数,返回他们的和
  8. (int x, int y) -> x + y
  9. // 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
  10. (String s) -> System.out.print(s)

在 Java8Tester.java 文件输入以下代码:

  1. public class Java8Tester {
  2. public static void main(String args[]){
  3. Java8Tester tester = new Java8Tester();
  4. // 类型声明
  5. MathOperation addition = (int a, int b) -> a + b;
  6. // 不用类型声明
  7. MathOperation subtraction = (a, b) -> a - b;
  8. // 大括号中的返回语句
  9. MathOperation multiplication = (int a, int b) -> { return a * b; };
  10. // 没有大括号及返回语句
  11. MathOperation division = (int a, int b) -> a / b;
  12. System.out.println("10 + 5 = " + tester.operate(10, 5, addition));
  13. System.out.println("10 - 5 = " + tester.operate(10, 5, subtraction));
  14. System.out.println("10 x 5 = " + tester.operate(10, 5, multiplication));
  15. System.out.println("10 / 5 = " + tester.operate(10, 5, division));
  16. // 不用括号
  17. GreetingService greetService1 = message ->
  18. System.out.println("Hello " + message);
  19. // 用括号
  20. GreetingService greetService2 = (message) ->
  21. System.out.println("Hello " + message);
  22. greetService1.sayMessage("Runoob");
  23. greetService2.sayMessage("Google");
  24. }
  25. interface MathOperation {
  26. int operation(int a, int b);
  27. }
  28. interface GreetingService {
  29. void sayMessage(String message);
  30. }
  31. private int operate(int a, int b, MathOperation mathOperation){
  32. return mathOperation.operation(a, b);
  33. }
  34. }

执行以上脚本,输出结果为:

  1. 10 + 5 = 15
  2. 10 - 5 = 5
  3. 10 x 5 = 50
  4. 10 / 5 = 2
  5. Hello Runoob
  6. Hello Google

使用 Lambda 表达式需要注意以下两点:

  • Lambda 表达式主要用来定义行内执行的方法类型接口,例如,一个简单方法接口。在上面例子中,我们使用各种类型的Lambda表达式来定义MathOperation接口的方法。然后我们定义了sayMessage的执行。

  • Lambda 表达式免去了使用匿名方法的麻烦,并且给予Java简单但是强大的函数化的编程能力。


变量作用域

lambda 表达式只能引用标记了 final 的外层局部变量,这就是说不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。

  1. public class Java8Tester {
  2. final static String salutation = "Hello! ";
  3. public static void main(String args[]){
  4. GreetingService greetService1 = message ->
  5. System.out.println(salutation + message);
  6. greetService1.sayMessage("Runoob");
  7. }
  8. interface GreetingService {
  9. void sayMessage(String message);
  10. }
  11. }

输出结果

  1. Hello! Runoob

我们也可以直接在 lambda 表达式中访问外层的局部变量:

  1. public class Java8Tester {
  2. public static void main(String args[]) {
  3. final int num = 1;
  4. Converter<Integer, String> s = (param) -> System.out.println(String.valueOf(param + num));
  5. s.convert(2); // 输出结果为 3
  6. }
  7. public interface Converter<T1, T2> {
  8. void convert(int i);
  9. }
  10. }

lambda 表达式的局部变量可以不用声明为 final,但是必须不可被后面的代码修改(即隐性的具有 final 的语义)

  1. int num = 1;
  2. Converter<Integer, String> s = (param) -> System.out.println(String.valueOf(param + num));
  3. s.convert(2);
  4. num = 5;
  5. //报错信息:Local variable num defined in an enclosing scope must be final or effectively
  6. final

在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量。

  1. String first = "";
  2. Comparator<String> comparator = (first, second) -> Integer.compare(first.length(), second.length()); //编译会出错

2.方法引用

方法引用通过方法的名字来指向一个方法。

方法引用可以使语言的构造更紧凑简洁,减少冗余代码。

方法引用使用一对冒号 :: 。

下面,我们在 Car 类中定义了 4 个方法作为例子来区分 Java 中 4 种不同方法的引用。

  1. @FunctionalInterface
  2. public interface Supplier<T> {
  3. T get();
  4. }
  5. class Car {
  6. //Supplier是jdk1.8的接口,这里和lamda一起使用了
  7. public static Car create(final Supplier<Car> supplier) {
  8. return supplier.get();
  9. }
  10. public static void collide(final Car car) {
  11. System.out.println("Collided " + car.toString());
  12. }
  13. public void follow(final Car another) {
  14. System.out.println("Following the " + another.toString());
  15. }
  16. public void repair() {
  17. System.out.println("Repaired " + this.toString());
  18. }
  19. }
  • 构造器引用:它的语法是Class::new,或者更一般的Class< T >::new实例如下:
  1. final Car car = Car.create( Car::new );
  2. final List< Car > cars = Arrays.asList( car );
  • 静态方法引用:它的语法是Class::static_method,实例如下:
  1. cars.forEach( Car::collide );
  • 特定类的任意对象的方法引用:它的语法是Class::method实例如下:
  1. cars.forEach( Car::repair );
  • 特定对象的方法引用:它的语法是instance::method实例如下:
  1. final Car police = Car.create( Car::new );
  2. cars.forEach( police::follow );

方法引用实例

在 Test.java 文件输入以下代码

  1. public static void main(String args[]){
  2. List names = new ArrayList();
  3. names.add("Google");
  4. names.add("Runoob");
  5. names.add("Taobao");
  6. names.add("Baidu");
  7. names.add("Sina");
  8. names.forEach(System.out::println);
  9. }

实例中我们将 System.out::println 方法作为静态方法来引用。

执行以上脚本,输出结果为:

  1. Google
  2. Runoob
  3. Taobao
  4. Baidu
  5. Sina

3.默认方法

Java 8 新增了接口的默认方法。

简单说,默认方法就是接口可以有实现方法,而且不需要实现类去实现其方法。

我们只需在方法名前面加个 default 关键字即可实现默认方法。

为什么要有这个特性?

首先,之前的接口是个双刃剑,好处是面向抽象而不是面向具体编程,缺陷是,当需要修改接口时候,需要修改全部实现该接口的类,目前的 java 8 之前的集合框架没有 foreach 方法,通常能想到的解决办法是在JDK里给相关的接口添加新的方法及实现。然而,对于已经发布的版本,是没法在给接口添加新方法的同时不影响已有的实现。所以引进的默认方法。他们的目的是为了解决接口的修改与现有的实现不兼容的问题。

语法

默认方法语法格式如下:

  1. public interface Vehicle {
  2. default void print(){
  3. System.out.println("我是一辆车!");
  4. }
  5. }

多个默认方法

一个接口有默认方法,考虑这样的情况,一个类实现了多个接口,且这些接口有相同的默认方法,以下实例说明了这种情况的解决方法:

  1. public interface Vehicle {
  2. default void print(){
  3. System.out.println("我是一辆车!");
  4. }
  5. }
  6. public interface FourWheeler {
  7. default void print(){
  8. System.out.println("我是一辆四轮车!");
  9. }
  10. }

第一个解决方案是创建自己的默认方法,来覆盖重写接口的默认方法:

  1. public class Car implements Vehicle, FourWheeler {
  2. default void print(){
  3. System.out.println("我是一辆四轮汽车!");
  4. }
  5. }

第二种解决方案可以使用 super 来调用指定接口的默认方法:

  1. public class Car implements Vehicle, FourWheeler {
  2. public void print(){
  3. Vehicle.super.print();
  4. }
  5. }

静态默认方法

Java 8 的另一个特性是接口可以声明(并且可以提供实现)静态方法。例如:

  1. public interface Vehicle {
  2. default void print(){
  3. System.out.println("我是一辆车!");
  4. }
  5. // 静态方法
  6. static void blowHorn(){
  7. System.out.println("按喇叭!!!");
  8. }
  9. }

默认方法实例

我们可以通过以下代码来了解关于默认方法的使用

  1. public class Java8Tester {
  2. public static void main(String args[]){
  3. Vehicle vehicle = new Car();
  4. vehicle.print();
  5. }
  6. }
  7. interface Vehicle {
  8. default void print(){
  9. System.out.println("我是一辆车!");
  10. }
  11. static void blowHorn(){
  12. System.out.println("按喇叭!!!");
  13. }
  14. }
  15. interface FourWheeler {
  16. default void print(){
  17. System.out.println("我是一辆四轮车!");
  18. }
  19. }
  20. class Car implements Vehicle, FourWheeler {
  21. public void print(){
  22. Vehicle.super.print();
  23. FourWheeler.super.print();
  24. Vehicle.blowHorn();
  25. System.out.println("我是一辆汽车!");
  26. }
  27. }

执行以上脚本,输出结果为:

  1. 我是一辆车!
  2. 我是一辆四轮车!
  3. 按喇叭!!!
  4. 我是一辆汽车!

4.Stream

Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。

Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。

Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。

这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。

元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。

  1. +--------------------+ +------+ +------+ +---+ +-------+
  2. | stream of elements +-----> |filter+-> |sorted+-> |map+-> |collect|
  3. +--------------------+ +------+ +------+ +---+ +-------+

以上的流程转换为 Java 代码为:

  1. List<Integer> transactionsIds =
  2. widgets.stream()
  3. .filter(b -> b.getColor() == RED)
  4. .sorted((x,y) -> x.getWeight() - y.getWeight())
  5. .mapToInt(Widget::getWeight)
  6. .sum();

什么是 Stream?

Stream(流)是一个来自数据源的元素队列并支持聚合操作

  • 元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。
  • 数据源 流的来源。 可以是集合,数组,I/O channel, 产生器generator 等。
  • 聚合操作 类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。

和以前的Collection操作不同, Stream操作还有两个基础的特征:

  • Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
  • 内部迭代: 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。

生成流

在 Java 8 中, 集合接口有两个方法来生成流:

  • stream() − 为集合创建串行流。

  • parallelStream() − 为集合创建并行流。

  1. List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
  2. List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());

forEach

Stream 提供了新的方法 ‘forEach’ 来迭代流中的每个数据。以下代码片段使用 forEach 输出了10个随机数:

  1. Random random = new Random();
  2. random.ints().limit(10).forEach(System.out::println);

map

map 方法用于映射每个元素到对应的结果,以下代码片段使用 map 输出了元素对应的平方数:

  1. List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
  2. // 获取对应的平方数
  3. List<Integer> squaresList = numbers.stream().map( i -> i*i).distinct().collect(Collectors.toList());

filter

filter 方法用于通过设置的条件过滤出元素。以下代码片段使用 filter 方法过滤出空字符串:

  1. List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
  2. // 获取空字符串的数量
  3. int count = strings.stream().filter(string -> string.isEmpty()).count();

limit

limit 方法用于获取指定数量的流。 以下代码片段使用 limit 方法打印出 10 条数据:

  1. Random random = new Random();
  2. random.ints().limit(10).forEach(System.out::println);

sorted

sorted 方法用于对流进行排序。以下代码片段使用 sorted 方法对输出的 10 个随机数进行排序:

  1. Random random = new Random();
  2. random.ints().limit(10).sorted().forEach(System.out::println);

并行(parallel)程序

parallelStream 是流并行处理程序的代替方法。以下实例我们使用 parallelStream 来输出空字符串的数量:

  1. List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
  2. // 获取空字符串的数量
  3. int count = strings.parallelStream().filter(string -> string.isEmpty()).count();

我们可以很容易的在顺序运行和并行直接切换。


Collectors

Collectors 类实现了很多归约操作,例如将流转换成集合和聚合元素。Collectors 可用于返回列表或字符串:

  1. List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
  2. List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
  3. System.out.println("筛选列表: " + filtered);
  4. String mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(", "));
  5. System.out.println("合并字符串: " + mergedString);

统计

另外,一些产生统计结果的收集器也非常有用。它们主要用于int、double、long等基本类型上,它们可以用来产生类似如下的统计结果。

  1. List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
  2. IntSummaryStatistics stats = numbers.stream().mapToInt((x) -> x).summaryStatistics();
  3. System.out.println("列表中最大的数 : " + stats.getMax());
  4. System.out.println("列表中最小的数 : " + stats.getMin());
  5. System.out.println("所有数之和 : " + stats.getSum());
  6. System.out.println("平均数 : " + stats.getAverage());

Stream 常见方法

操作类型 接口方法
中间操作 concat() distinct() filter() flatMap() limit() map() peek() skip() sorted() parallel() sequential() unordered()
结束操作 allMatch() anyMatch() collect() count() findAny() findFirst() forEach() forEachOrdered() max() min() noneMatch() reduce() toArray()

5.新的日期时间API

Java 8通过发布新的Date-Time API (JSR 310)来进一步加强对日期与时间的处理。

在旧版的 Java 中,日期时间 API 存在诸多问题,其中有:

  • 非线程安全 − java.util.Date 是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一。

  • 设计很差 − Java的日期/时间类的定义并不一致,在java.util和java.sql的包中都有日期类,此外用于格式化和解析的类在java.text包中定义。java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期,将其纳入java.sql包并不合理。另外这两个类都有相同的名字,这本身就是一个非常糟糕的设计。

  • 时区处理麻烦 − 日期类并不提供国际化,没有时区支持,因此Java引入了java.util.Calendar和java.util.TimeZone类,但他们同样存在上述所有的问题。

Java 8 在 java.time 包下提供了很多新的 API。以下为两个比较重要的 API:

  • Local(本地) − 简化了日期时间的处理,没有时区的问题。

  • Zoned(时区) − 通过制定的时区处理日期时间。

新的java.time包涵盖了所有处理日期,时间,日期/时间,时区,时刻(instants),过程(during)与时钟(clock)的操作。


本地化日期时间 API

LocalDate/LocalTime 和 LocalDateTime 类可以在处理时区不是必须的情况。代码如下:

  1. import java.time.LocalDate;
  2. import java.time.LocalTime;
  3. import java.time.LocalDateTime;
  4. import java.time.Month;
  5. public class Java8Tester {
  6. public static void main(String args[]){
  7. Java8Tester java8tester = new Java8Tester();
  8. java8tester.testLocalDateTime();
  9. }
  10. public void testLocalDateTime(){
  11. // 获取当前的日期时间
  12. LocalDateTime currentTime = LocalDateTime.now();
  13. System.out.println("当前时间: " + currentTime);
  14. LocalDate date1 = currentTime.toLocalDate();
  15. System.out.println("date1: " + date1);
  16. Month month = currentTime.getMonth();
  17. int day = currentTime.getDayOfMonth();
  18. int seconds = currentTime.getSecond();
  19. System.out.println("月: " + month +", 日: " + day +", 秒: " + seconds);
  20. LocalDateTime date2 = currentTime.withDayOfMonth(10).withYear(2012);
  21. System.out.println("date2: " + date2);
  22. // 12 december 2014
  23. LocalDate date3 = LocalDate.of(2014, Month.DECEMBER, 12);
  24. System.out.println("date3: " + date3);
  25. // 22 小时 15 分钟
  26. LocalTime date4 = LocalTime.of(22, 15);
  27. System.out.println("date4: " + date4);
  28. // 解析字符串
  29. LocalTime date5 = LocalTime.parse("20:15:30");
  30. System.out.println("date5: " + date5);
  31. }
  32. }

执行以上脚本,输出结果为:

  1. 当前时间: 2019-03-22T10:43:06.842
  2. date1: 2019-03-22
  3. 月: MARCH, 日: 22, 秒: 6
  4. date2: 2012-03-10T10:43:06.842
  5. date3: 2014-12-12
  6. date4: 22:15
  7. date5: 20:15:30

使用时区的日期时间API

如果我们需要考虑到时区,就可以使用时区的日期时间API:

  1. import java.time.ZonedDateTime;
  2. import java.time.ZoneId;
  3. public class Java8Tester {
  4. public static void main(String args[]){
  5. Java8Tester java8tester = new Java8Tester();
  6. java8tester.testZonedDateTime();
  7. }
  8. public void testZonedDateTime(){
  9. // 获取当前时间日期
  10. ZonedDateTime date1 = ZonedDateTime.parse("2015-12-03T10:15:30+05:30[Asia/Shanghai]");
  11. System.out.println("date1: " + date1);
  12. ZoneId id = ZoneId.of("Europe/Paris");
  13. System.out.println("ZoneId: " + id);
  14. ZoneId currentZone = ZoneId.systemDefault();
  15. System.out.println("当期时区: " + currentZone);
  16. }
  17. }

执行以上脚本,输出结果为:

  1. date1: 2015-12-03T10:15:30+08:00[Asia/Shanghai]
  2. ZoneId: Europe/Paris
  3. 当期时区: Asia/Shanghai

6.Optional类

Optional 类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。

Optional 是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。

Optional 类的引入很好的解决空指针异常。

类声明

以下是一个 java.util.Optional 类的声明:

  1. public final class Optional<T> extends Object

类方法

序号 方法 描述
1 static Optional empty() 返回空的 Optional 实例。
2 boolean equals(Object obj) 判断其他对象是否等于 Optional。
3 Optional filter(Predicate<? super predicate) 如果值存在,并且这个值匹配给定的 predicate,返回一个Optional用以描述这个值,否则返回一个空的Optional。
4 Optional flatMap(Function<? super T,Optional> mapper) 如果值存在,返回基于Optional包含的映射方法的值,否则返回一个空的Optional
5 T get() 如果在这个Optional中包含这个值,返回值,否则抛出异常:NoSuchElementException
6 int hashCode() 返回存在值的哈希码,如果值不存在 返回 0。
7 void ifPresent(Consumer<? super T> consumer) 如果值存在则使用该值调用 consumer , 否则不做任何事情。
8 boolean isPresent() 如果值存在则方法会返回true,否则返回 false。
9 Optional map(Function<? super T,? extends U> mapper) 如果有值,则对其执行调用映射函数得到返回值。如果返回值不为 null,则创建包含映射返回值的Optional作为map方法返回值,否则返回空Optional。
10 static Optional of(T value) 返回一个指定非null值的Optional。
11 static Optional ofNullable(T value) 如果为非空,返回 Optional 描述的指定值,否则返回空的 Optional。
12 T orElse(T other) 如果存在该值,返回值, 否则返回 other。
13 T orElseGet(Supplier<? extends T> other) 如果存在该值,返回值, 否则触发 other,并返回 other 调用的结果。
14 T orElseThrow(Supplier<? extends X> exceptionSupplier) 如果存在该值,返回包含的值,否则抛出由 Supplier 继承的异常
15 String toString() 返回一个Optional的非空字符串,用来调试

注意: 这些方法是从 java.lang.Object 类继承来的。


Optional 实例

我们可以通过以下实例来更好的了解 Optional 类的使用:

  1. import java.util.Optional;
  2. public class Java8Tester {
  3. public static void main(String args[]){
  4. Java8Tester java8Tester = new Java8Tester();
  5. Integer value1 = null;
  6. Integer value2 = new Integer(10);
  7. // Optional.ofNullable - 允许传递为 null 参数
  8. Optional<Integer> a = Optional.ofNullable(value1);
  9. // Optional.of - 如果传递的参数是 null,抛出异常 NullPointerException
  10. Optional<Integer> b = Optional.of(value2);
  11. System.out.println(java8Tester.sum(a,b));
  12. }
  13. public Integer sum(Optional<Integer> a, Optional<Integer> b){
  14. // Optional.isPresent - 判断值是否存在
  15. System.out.println("第一个参数值存在: " + a.isPresent());
  16. System.out.println("第二个参数值存在: " + b.isPresent());
  17. // Optional.orElse - 如果值存在,返回它,否则返回默认值
  18. Integer value1 = a.orElse(new Integer(0));
  19. //Optional.get - 获取值,值需要存在
  20. Integer value2 = b.get();
  21. return value1 + value2;
  22. }
  23. }

执行以上脚本,输出结果为:

  1. 第一个参数值存在: false
  2. 第二个参数值存在: true
  3. 10

7.Nashorn,JavaScript引擎

Nashorn 一个 javascript 引擎。

从JDK 1.8开始,Nashorn取代Rhino(JDK 1.6, JDK1.7)成为Java的嵌入式JavaScript引擎。Nashorn完全支持ECMAScript 5.1规范以及一些扩展。它使用基于JSR 292的新语言特性,其中包含在JDK 7中引入的 invokedynamic,将JavaScript编译成Java字节码。

与先前的Rhino实现相比,这带来了2到10倍的性能提升。

Reproduced please indicate the author and the source, and error a link to this page.
text link: //118.24.153.47/3.html

Comments

  • 你好

    111 On 2019-09-27 09:28:08.0

  • vessel测试

    1 On 2019-09-27 09:28:21.0

Say something...