前言

Java8(又称为 Jdk1.8)是 Java 语言开发的一个主要版本。Oracle 公司于 2014 年 3 月 18 日发布 Java8,它支持函数式编程,新的 JavaScript 引擎,新的日期 API,新的 Stream API 等。Java8 API 添加了一个新的抽象称为流 Stream,可以让你以一种声明的方式处理数据。Stream API 可以极大提高 Java 程序员的生产力,让程序员写出高效率、干净、简洁的代码。

Java8 新特性

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

  • 方法引用 − 方法引用提供了非常有用的语法,可以直接引用已有 Java 类或对象(实例)的方法或构造器。与 lambda 联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。

  • 默认方法 − 默认方法就是一个在接口里面有了一个实现的方法。

  • 新工具 − 新的编译工具,如:Nashorn 引擎 jjs、类依赖分析器 jdeps。

  • Stream API − 新添加的 Stream API(java.util.stream)把真正的函数式编程风格引入到 Java 中。

  • Date Time API − 加强对日期与时间的处理。

  • Optional 类 − Optional 类已经成为 Java8 类库的一部分,用来解决空指针异常。

  • Nashorn JavaScript 引擎 − Java8 提供了一个新的 Nashorn javascript 引擎,它允许我们在 JVM 上运行特定的 javascript 应用。

为什么需要 Steam?

Java8 中的 Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作,或者大批量数据操作。

StreamAPI 借助于同样新出现的 Lambda 表达式,极大的提高编程效率和程序可读性。同时它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势,使用 fork/join 并行方式来拆分任务和加速处理过程。

流的操作种类

中间操作

当数据源中的数据上了流水线后,这个过程对数据进行的所有操作都称为“中间操作”。
中间操作仍然会返回一个流对象,因此多个中间操作可以串连起来形成一个流水线。

终端操作

当所有的中间操作完成后,若要将数据从流水线上拿下来,则需要执行终端操作。
终端操作将返回一个执行结果,这就是你想要的数据。

java.util.Stream 使用示例

定义一个简单的学生实体类,用于后面的例子演示:

public class Student {/** 学号 */private long id;/** 姓名 */private String name;/** 年龄 */private int age;/** 性别 */private int grade;/** 专业 */private String major;/** 学校 */private String school;// 省略 getter 和 setter
}// 初始化
List<Student> students = new ArrayList<Student>() {{add(new Student(20160001, "孔明", 20, 1, "土木工程", "武汉大学"));add(new Student(20160002, "伯约", 21, 2, "信息安全", "武汉大学"));add(new Student(20160003, "玄德", 22, 3, "经济管理", "武汉大学"));add(new Student(20160004, "云长", 21, 2, "信息安全", "武汉大学"));add(new Student(20161001, "翼德", 21, 2, "机械与自动化", "华中科技大学"));add(new Student(20161002, "元直", 23, 4, "土木工程", "华中科技大学"));add(new Student(20161003, "奉孝", 23, 4, "计算机科学", "华中科技大学"));add(new Student(20162001, "仲谋", 22, 3, "土木工程", "浙江大学"));add(new Student(20162002, "鲁肃", 23, 4, "计算机科学", "浙江大学"));add(new Student(20163001, "丁奉", 24, 5, "土木工程", "南京大学"));}
};

forEach

Stream 提供了新的方法’forEach’来迭代流中的每个数据。ForEach 接受一个 function 接口类型的变量,用来执行对每一个元素的操作。ForEach 是一个中止操作,它不返回流,所以我们不能再调用其他的流操作。

以下代码片段使用 forEach 输出了 10 个随机数:

// 随机生成 10 个 0,100int 类型随机数
new Random().ints(0, 100).limit(10).forEach(System.out::println);

从集合 students 中筛选出所有武汉大学的学生:

List<Student> whuStudents = students.stream().filter(student -> "武汉大学".equals(student.getSchool())).collect(Collectors.toList());

filter/distinct

filter 方法用于通过设置的条件过滤出元素。Filter 接受一个 predicate 接口类型的变量,并将所有流对象中的元素进行过滤。该操作是一个中间操作,因此它允许我们在返回结果的基础上再进行其他的流操作。

以下代码片段使用 filter 方法过滤出空字符串:

// 获取空字符串的数量
Arrays.asList("abc", "","bc","efg","abcd","", "jkl")// stream() − 为集合创建串行流.stream().filter(string -> string.isEmpty()).count();

distinct 方法用于去除重复元素。

Arrays.asList("a", "c", "ac", "c", "a", "b").stream().distinct().forEach(System.out::println);

anyMatch/allMatch/noneMatch

匹配操作有多种不同的类型,都是用来判断某一种规则是否与流对象相互吻合的。所有的匹配操作都是终结操作,只返回一个 boolean 类型的结果。

anyMatch 方法用于判断集合中是否有任一元素满足条件。

// 集合中是否有任一元素匹配以'a'开头
boolean result = Arrays.asList("abc", "","bc","efg","abcd","", "jkl").stream().anyMatch(s -> s.startsWith("a"));

allMatch 方法用于判断集合中是否所有元素满足条件。

// 集合中是否所有元素匹配以'a'开头
boolean result = Arrays.asList("abc", "","bc","efg","abcd","", "jkl").stream().allMatch(s -> s.startsWith("a"));

noneMatch 方法用于判断集合中是否所有元素不满足条件。

// 集合中是否没有元素匹配以'a'开头
boolean result = Arrays.asList("abc", "","bc","efg","abcd","", "jkl").stream().noneMatch(s -> s.startsWith("a"));

limit/skip

limit 方法用于返回前面 n 个元素。

Arrays.asList("abc", "","bc","efg","abcd","", "jkl").stream().filter(string -> !string.isEmpty()).limit(3).forEach(System.out::println);

skip 方法用于舍弃前 n 个元素。

Arrays.asList("abc", "","bc","efg","abcd","", "jkl").stream().filter(string -> !string.isEmpty()).skip(1).forEach(System.out::println);

sorted

sorted 方法用于对流进行排序。Sorted 是一个中间操作,能够返回一个排过序的流对象的视图。流对象中的元素会默认按照自然顺序进行排序,除非你自己指定一个 Comparator 接口来改变排序规则。

以下代码片段使用 filter 方法过滤掉空字符串,并对其进行自然顺序排序:

List<String> strings = Arrays.asList("abc", "","bc","efg","abcd","", "jkl");
// 一定要记住, sorted 只是创建一个流对象排序的视图, 而不会改变原来集合中元素的顺序。
strings.stream().filter(string -> !string.isEmpty()).sorted().forEach(System.out::println);
// 输出原始集合元素, sorted 只是创建排序视图, 不影响原来集合顺序
strings.stream().forEach(System.out::println);// 按照字符串长度进行排序, 若两个字符串长度相同, 按照字母顺序排列
strings.stream().filter(string -> !string.isEmpty())// 1. 首先根据字符串长度倒序排序; 2. 然后根据字母顺序排列.sorted(Comparator.comparing(String::length).reversed().thenComparing(String::compareTo)).forEach(System.out::println);

以下代码片段根据 Person 姓名倒序排序,然后利用 Collectors 返回列表新列表:

List<Person> persons = new ArrayList();
// 1. 生成 5 个 Person 对象
for (int i = 1; i <= 5; i++) {Person person = new Person(i, "name" + i);persons.add(person);
}// 2. 对 Person 列表进行排序, 排序规则: 根据 Person 姓名倒序排序, 然后利用 Collectors 返回列表新列表;
List<Person> personList = persons.stream().sorted(Comparator.comparing(Person::getName).reversed()).collect(Collectors.toList());

parallel

流操作可以是顺序的,也可以是并行的。顺序操作通过单线程执行,而并行操作则通过多线程执行。可使用并行流进行操作来提高运行效率 parallelStream 是流并行处理程序的代替方法。
parallelStream()本质上基于 Java7 的 Fork-Join 框架实现,Fork-Join 是一个处理并行分解的高性能框架,其默认的线程数为宿主机的内核数。

以下实例我们使用 parallelStream 来输出空字符串的数量:

// 获取空字符串的数量[parallelStream 为 Collection 接口的一个默认方法]
Arrays.asList("abc", "","bc","efg","abcd","", "jkl")// parallelStream() − 为集合创建并行流.parallelStream().filter(string -> string.isEmpty()).count();

parallelStream 中 forEachOrdered 与 forEach 区别:

List<String> strings = Arrays.asList("a", "b", "c");
strings.stream().forEachOrdered(System.out::print);            //abc
strings.stream().forEach(System.out::print);                   //abc
strings.parallelStream().forEachOrdered(System.out::print);    //abc
strings.parallelStream().forEach(System.out::print);           //bca

特别注意:1、千万不要任意地并行 Stream pipeline,如果源头是来自 stream.iterate,或者中间使用了中间操作的 limit,那么并行 pipeline 也不可能提升性能。因此,在 Stream 上通过并行获取的性能,最好是通过 ArrayList、HashMap、HashSet 和 CouncurrentHashMap 实例,数组,int 范围和 long 范围等。这些数据结构的共性是,都可以被精确、轻松地分成任意大小的子范围,使并行线程中的分工变得更加轻松。2、Stream pipeline 的终止操作本质上也影响了并发执行的效率。并行的最佳操作是做减法,用一个 Stream 的 reduce 方法,将所有从 pipeline 产生的元素都合并在一起,或者预先打包想 min、max、count 和 sum 这类方法。骤死式操作如 anyMatch、allMatch 和 nonMatch 也都可以并行。由 Stream 的 collect 方法执行的操作,都是可变的减法,不是并行的最好选择,因此并行集合的成本非常高。3、一般来说,程序中所有的并行 Stream pipeline 都是在一个通用的 fork-join 池中运行的。只要有一个 pipeline 运行异常,都是损害到系统中其它不相关部分的性能。因此,如果对 Stream 进行不恰当的并行操作,可能导致程序运行失败,或者造成性能灾难。

map

map 方法用于映射每个元素到对应的结果。map 是一个对于流对象的中间操作,通过给定的方法,它能够把流对象中的每一个元素对应到另外一个对象上。
以下代码片段使用 map 将集合元素转为大写 (每个元素映射到大写)-> 降序排序 ->迭代输出:

Arrays.asList("abc", "","bc","efg","abcd","", "jkl")// 通过 stream()方法即可获取流对象.stream()// 通过 filter()过滤元素.filter(string -> !string.isEmpty())// 通过 map()方法用于映射每个元素到对应的结果.map(String::toUpperCase)// 通过 sorted()方法用于对流进行排序.sorted(Comparator.reverseOrder())// 通过 forEach()方法迭代流中的每个数据.forEach(System.out::println);

筛选出所有专业为计算机科学的学生姓名:

List<String> names = students.stream().filter(student -> "计算机科学".equals(student.getMajor())).map(Student::getName).collect(Collectors.toList());

计算所有专业为计算机科学学生的年龄之和:

int totalAge = students.stream().filter(student -> "计算机科学".equals(student.getMajor())).mapToInt(Student::getAge).sum();

peek

peek 操作接收的是一个 Consumer<T> 函数。顾名思义 peek 操作会按照 Consumer<T> 函数提供的逻辑去消费流中的每一个元素,同时有可能改变元素内部的一些属性。

按照 Java 团队的说法,peek() 方法存在的主要目的是用调试,通过 peek() 方法可以看到流中的数据经过每个处理点时的状态。

Stream.of("one", "two", "three","four").filter(e -> e.length() > 3).peek(e -> System.out.println("Filtered value: " + e)).map(String::toUpperCase).peek(e -> System.out.println("Mapped value: " + e)).collect(Collectors.toList());

除去用于调试,peek() 在需要修改元素内部状态的场景也非常有用,比如我们想将所有 Student 的名字修改为大写,当然也可以使用 map() 和 flatMap() 实现,但是相比来说 peek() 更加方便,因为我们并不想替代流中的数据。

students.stream().peek(student -> student.setName(student.getName().toUpperCase())).forEach(System.out::println);

那么 peek() 和 map() 有什么区别呢?peek 接收一个 Consumer,而 map 接收一个 Function。Consumer 是没有返回值的,它只是对 Stream 中的元素进行某些操作,但是操作之后的数据并不返回到 Stream 中,所以 Stream 中的元素还是原来的元素。而 Function 是有返回值的,这意味着对于 Stream 的元素的所有操作都会作为新的结果返回到 Stream 中。

findFirst/findAny

findAny 能够从流中随便选一个元素出来,它返回一个 Optional 类型的元素。

Optional<String> optional = Arrays.asList("abc", "","bc","efg","abcd","", "jkl").stream().findAny();

findFirst 能够从流中选第一个元素出来,它返回一个 Optional 类型的元素。

Optional<String> optional = Arrays.asList("abc", "","bc","efg","abcd","", "jkl").stream().findFirst();

collect

collect 方法是一个终端操作,它接收的参数是将流中的元素累积到汇总结果的各种方式(称为收集器)。

Collectors 工具类提供了许多静态工具方法来为大多数常用的用户用例创建收集器,比如将元素装进一个集合中、将元素分组、根据不同标准对元素进行汇总等。

Collectors.joining()

Collectors.joining()方法以遭遇元素的顺序拼接元素。我们可以传递可选的拼接字符串、前缀和后缀。

List<String> strings = Arrays.asList("abc", "","bc","efg","abcd","", "jkl");
// 筛选列表
List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());// 合并字符串
String mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(","));

Collectors.groupingBy

Collectors.groupingBy 方法根据项目的一个属性的值对流中的项目作问组,并将属性值作为结果 Map 的键。

  1. List 里面的对象元素,以某个属性来分组。

// 按学校对学生进行分组:
Map<String, List<Student>> groups = students.stream().collect(Collectors.groupingBy(Student::getSchool));// 多级分组, 在按学校分组的基础之上再按照专业进行分组
Map<String, Map<String, List<Student>>> groups2 = students.stream().collect(Collectors.groupingBy(Student::getSchool,  // 一级分组,按学校Collectors.groupingBy(Student::getMajor)));  // 二级分组,按专业
  1. 统计 List 集合重复元素出现次数。

List<String> items = Arrays.asList("apple", "apple", "banana", "apple", "orange", "banana", "papaya");// 方式一
Map<String, Long> result = items.stream()// Function.identity() 返回一个输出跟输入一样的 Lambda 表达式对象, 等价于形如 t -> t 形式的 Lambda 表达式..collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));// 方式二
Map<String, Long> result2 = items.stream()// Collectors.counting() 计算流中数量.collect(Collectors.groupingBy(String::toString, Collectors.counting()));//  Output :
//  {papaya=1, orange=1, banana=2, apple=3}

统计每个组的个数:

Map<String, Long> groups = students.stream().collect(Collectors.groupingBy(Student::getSchool, Collectors.counting()));
  1. 累加求和

// 统计相同姓名, 总年龄大小
Map<String, Integer> sumMap = persons.stream()// Collectors.summingInt() 返回流中整数属性求和.collect(Collectors.groupingBy(Person::getName, Collectors.summingInt(Person::getAge)));
  1. 转换

// 按照姓名对学生分布组,并只保留员工的年龄
Map<String, List<String>> nameMap = persons.stream().collect(Collectors.groupingBy(Person::getName,Collectors.mapping(Employee::getName,   // 下游收集器Collectors.toList()))); // 更下游的收集器

Collectors.toMap

Collectors.toMap 方法将 List 转 Map。

// 根据 Person 年龄生成 Map
Map<Integer, Person> personMap = persons.stream().collect(Collectors.toMap(Person::getAge, person -> person));// account -> account 是一个返回本身的 lambda 表达式, 其实还可以使用 Function 接口中的一个默认方法代替, 使整个方法更简洁优雅.
Map<Integer, Person> personMap = persons.stream().collect(Collectors.toMap(Person::getAge, Function.identity()));

当 key 重复时,会抛出异常:java.lang.IllegalStateException: Duplicate key **

// 针对重复 key 的, 覆盖之前的 value
Map<Integer, Person> personMap = persons.stream().collect(Collectors.toMap(Person::getAge, Function.identity(), (person, person2) -> person2));

指定具体收集的 map:

Map<Integer, Person> personMap = persons.stream().collect(Collectors.toMap(Person::getAge, Function.identity(), (person, person2) -> person2, LinkedHashMap::new));

当 value 为 null 时,会抛出异常:java.lang.NullPointerException[Collectors.toMap 底层是基于 Map.merge 方法来实现的,而 merge 中 value 是不能为 null 的,如果为 null,就会抛出空指针异常。]

Map<Integer, String> personMap = persons.stream().collect(Collectors.toMap(Person::getAge, Person::getName, (person, person2) -> person2));
// 1. 解决方式 1: 用 for 循环的方式亦或是 forEach 的方式
Map<Integer, String> personMap = new HashMap<>();
for (Person person : persons) {personMap.put(person.getAge(), person.getName());
}// 2. 解决方式 2: 使用 stream 的 collect 的重载方法
Map<Integer, String> personMap = persons.stream().collect(HashMap::new, (m, v) -> m.put(v.getAge(), v.getName()), HashMap::putAll);
Collectors.collectingAndThen

Collectors.collectingAndThen 方法主要用于转换函数返回的类型。

List 里面的对象元素,以某个属性去除重复元素。

List<Person> unique = persons.stream().collect(Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparingInt(Person::getAge))), ArrayList::new));

Collectors.partitioningBy

Collectors.partitioningBy 方法主要用于根据对流中每个项目应用谓词的结果来对项目进行分区。

“年龄小于 18”进行分组后可以看到,不到 18 岁的未成年人是一组,成年人是另外一组。

Map<Boolean, List<Person>> groupBy = persons.stream().collect(Collectors.partitioningBy(o -> o.getAge() >= 18));

Collectors 收集器静态方法:

Collectors 收集器静态方法

Collectors 收集器静态方法

数值流的使用

在 Stream 里元素都是对象,那么,当我们操作一个数字流的时候就不得不考虑一个问题,拆箱和装箱。虽然自动拆箱不需要我们处理,但依旧有隐含的成本在里面。Java8 引入了 3 个原始类型特化流接口来解决这个问题:IntStream、DoubleStream、LongStream,分别将流中的元素特化为 int、long、double,从而避免了暗含的装箱成本。

将对象流映射为数值流

// 将对象流映射为数值流
IntStream intStream = persons.stream().mapToInt(Person::getAge);

默认值 OptinalInt

由于数值流经常会有默认值,比如默认为 0。数值特化流的终端操作会返回一个 OptinalXXX 对象而不是数值。

// 每种数值流都提供了数值计算函数, 如 max、min、sum 等
OptionalInt optionalInt = persons.stream().mapToInt(Person::getAge).max();int max = optionalInt.orElse(1);

生成一个数值范围流

// 创建一个包含两端的数值流, 比如 1 到 10, 包含 10:
IntStream intStream = IntStream.rangeClosed(1, 10);
// 创建一个不包含结尾的数值流, 比如 1 到 9:
IntStream range = IntStream.range(1, 9);

将数值流转回对象流

// 将数值流转回对象流
Stream<Integer> boxed = intStream.boxed();

流的扁平化

案例:对给定单词列表 [“Hello”,”World”],你想返回列表[“H”,”e”,”l”,”o”,”W”,”r”,”d”]

方法一:错误方式

String[] words = new String[]{"Hello", "World"};
List<String[]> a = Arrays.stream(words).map(word -> word.split("")).distinct().collect(Collectors.toList());
a.forEach(System.out::print);// Output
// [Ljava.lang.String;@12edcd21[Ljava.lang.String;@34c45dca

返回一个包含两个 String[]的 list,传递给 map 方法的 lambda 为每个单词生成了一个 String[]。因此,map 返回的流实际上是 Stream<String[]>类型的。

方法二:正确方式

String[] words = new String[]{"Hello", "World"};
List<String> a = Arrays.stream(words).map(word -> word.split("")).flatMap(Arrays::stream).distinct().collect(Collectors.toList());
a.forEach(System.out::print);// Output
// HeloWrd

使用 flatMap 方法的效果是,各个数组并不是分别映射一个流,而是映射成流的内容,所有使用 map(Array::stream)时生成的单个流被合并起来,即扁平化为一个流。


参考博文

[1]. Java 8 中的 Streams API 详解
[2]. java8 快速实现 List 转 map 、分组、过滤等操作

source:https://morning-pro.github.io/archives/8cef11db.html

喜欢,在看

强大的 Stream 函数式编程相关推荐

  1. java8函数式编程 视频_快速掌握Java8 Stream函数式编程技巧

    函数式编程优势"函数第一位",即函数可以出现在任何地方. 可以把函数作为参数传递给另一个函数,还可以将函数作为返回值. 让代码的逻辑更清晰更优雅. 减少了可变量(Immutable ...

  2. Java 代码写的又臭又长,还不会用 Java Stream 函数式编程?

    点击上方"猿芯",选择"设为星标" 后台回复"1024",有份惊喜送给面试的你 原文 https://www.cnblogs.com/Car ...

  3. java函数式编程入口_Java中的函数式编程

    前言 JDK8引入的Lambda表达式和Stream为Java平台提供了函数式编程的支持,极大地提高了开发效率.本文结合网络资源和自身使用经验,介绍下Java中的函数式编程 Java中的函数式编程 出 ...

  4. Java8函数式编程——Stream流

    函数式编程-Stream流 实例练习:https://javadaily.cn/post/2022020817/7f222b3057fb/ 1. 概述 1.1 为什么学? 能够看懂公司里的代码 大数量 ...

  5. java8/Stream流式计算从入门到精通/函数式编程实战

    摘要:Stream流式计算,本文讲解了Stream流式计算的概念,具体的使用步骤以及源码实现,最后讲解了使用Stream过程中需要注意的事项.Stream在公司项目中被频繁使用,在性能优化上具有广泛的 ...

  6. 【CSDN软件工程师能力认证学习精选】 Java8新特性学习-函数式编程(Stream/Function/Optional/Consumer)

    CSDN软件工程师能力认证是由CSDN制定并推出的一个能力认证标准,宗旨是让一流的技术人才凭真才实学进大厂拿高薪,同时为企业节约大量招聘与培养成本,使命是提升高校大学生的技术能力,为行业提供人才储备, ...

  7. 浅谈函数式编程与 Java Stream

    CSDN 社区的小伙伴们大家好啊,许久不见- 在这一篇文章中,我将介绍函数式编程的基本概念,如何使用函数式编程的思想编写代码以及 Java Stream 的基本使用方法. 本文不会涉及到任何晦涩难懂的 ...

  8. java stream 取某个字段_java8的函数式编程和stream使用心得

    1:函数式编程 在很多其他的编程语言里面,都可以实现函数式的编程,也就是函数可以作为变量去灵活使用,但是java一直都不可以,之前很多都使用一些匿名内部类这种丑的亚批的代码.java8之后算是可以使用 ...

  9. 【Java10】lambda表达式(函数式编程),Stream流,File类,字节/字符流,乱码,缓冲/转换/序列化/打印流,Properties

    文章目录 1.lambda表达式标准语法:()->{} 2.lambda表达式简略语法:可推导即可省略 3.lambda表达式原理:lambda效率比匿名内部类高 4.两个函数式接口:Consu ...

  10. java函数式编程归约reduce概念原理 stream reduce方法详解 reduce三个参数的reduce方法如何使用

    java函数式编程归约reduce概念原理 stream reduce方法详解 reduce三个参数的reduce方法如何使用

最新文章

  1. c语言表达式3178的值为,【C语言】C语言运算符
  2. 只花5-10分钟评审,还不提供拒稿理由,IJCAI就“枪毙”42%论文,网友:一脸懵逼...
  3. 智能推荐算法在直播场景中的应用
  4. c语言数组将素数放在前部,m个人的成绩存放在score数组中,请编写函数fun(),它的功能是将高于平均分的人数作为函数值返回,将_开卷宝...
  5. SonarQube6.2源码解析(四)
  6. 【转】C# 动态对象(dynamic)的用法
  7. bootstap-栅格系统
  8. js自动滚动条在底部
  9. 支持向量机(SVM)复习总结
  10. PHP生成海报 文字描边,php实现图片添加描边字和马赛克的方法
  11. 物流货代公司管理系统
  12. setInterval()与clearInterval()的用法
  13. 估计理论(4):例5.8说明如何用完备的充分统计量找到MVU估计
  14. 计算机原理教程 pdf,《计算机组成原理》教程 概述.pdf
  15. 【JAVA】数据结构——堆的排序及相关面试题
  16. 仿苹果桌面Android,iLauncher(仿苹果桌面)安卓版
  17. java mocked_java如何使用Mockito?
  18. h标签本身自带间距 去除方法
  19. IPv6与ICMPv6
  20. 海光国产CPU芯片和服务器,海光CPU芯片 一文看懂国产CPU!“造不如买”时代终

热门文章

  1. iOS 之模拟网易新闻主页的滚动效果
  2. 计算机主板反复启动,主板无限重启怎么回事
  3. HeadFirst 设计模式 4工厂模式(披萨店演变)
  4. 华为智慧屏鸿蒙评测,华为智慧屏S Pro体验:告诉你鸿蒙OS有多优秀?
  5. 木子-前端-ajax传值与接收最简单的方式
  6. BeagleBone Black 从零到一 (2 MLO、U-Boot)
  7. 互联网时代下的市场营销
  8. JavaWeb个人信息修改及修改密码
  9. 十分钟超简单完成百度地图3.0离线功能
  10. 氢键H-H的博客目录