Stream 用来处理集合数据的,通过 stream 操作可以实现 SQL 的拥有的大部分查询功能

Java8 API 官方文档

下面借助例子,演示 stream 操作

Java userList 列表

private List<User> userList = Arrays.asList(
new User(101, "小明", 10, "男", "青海省", "西宁市"),
new User(102, "小青", 12, "女", "宁夏回族自治区", "银川市"),
new User(103, "小海", 8, "男", "西藏自治区", "拉萨市"),
new User(108, "阿刁", 18, "女", "西藏自治区", "拉萨市"),
new User(104, "小阳", 9, "女", "新疆维吾尔自治区", "乌鲁木齐市"),
new User(105, "小强", 14, "男", "陕西省", "西安市"),
new User(106, "小帅", 15, "男", "河北省", "石家庄市"),
new User(107, "小云", 15, "女", "河北省", "石家庄市")
);

MySQL user 表数据

DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(11) PRIMARY KEY,
`name` varchar(20),
`age` int(2),
`gender` varchar(10),
`province` varchar(100),
`city` varchar(100)
) ; INSERT INTO `user` VALUES (101, '小明', 10, '男', '青海省', '西宁市');
INSERT INTO `user` VALUES (102, '小青', 12, '女', '宁夏回族自治区', '银川市');
INSERT INTO `user` VALUES (103, '小海', 8, '男', '西藏自治区', '拉萨市');
INSERT INTO `user` VALUES (104, '小阳', 9, '女', '新疆维吾尔自治区', '乌鲁木齐市');
INSERT INTO `user` VALUES (105, '小强', 14, '男', '陕西省', '西安市');
INSERT INTO `user` VALUES (106, '小帅', 15, '男', '河北省', '石家庄市');
INSERT INTO `user` VALUES (107, '小云', 15, '女', '河北省', '石家庄市');

查询字段 select - map

// select id from user
userList.stream()
.map(e -> e.getId())
.forEach(System.out::println);

至于如何实现 select id, name from user 查询多字段在下面 collector 收集器会详细讲解

条件 where - filter

// select * from user where age<10
userList.stream()
.filter(e-> e.getAge() < 10)
.forEach(System.out::println); // select * from user where age<10 and gender='男'
userList.stream()
.filter(e->e.getAge() < 10)
.filter(e->e.getGender()=="男")
.forEach(System.out::println);

最值、总和、数量、均值(max, min, sum, count, average)

// select max(age), min(age), sum(age), count(age), avg(age) from user
// max
Optional<Integer> maxAge = userList.stream()
.map(e -> e.getAge())
.max(Comparator.comparingInt(x -> x));
// 等同于
// Optional<Integer> maxAge = userList.stream()
// .map(e -> e.getAge())
// .max((x, y) -> x-y); // min
Optional<Integer> minAge = userList.stream()
.map(e -> e.getAge())
.min(Comparator.comparingInt(x -> x));
// sum
Optional<Integer> sumAge = userList.stream()
.map(e -> e.getAge())
.reduce((e, u) -> e + u);
// count
long count = userList.stream()
.map(e -> e.getAge())
.count();
// 平均值=总和/数量

排序 order by - sorted

// select * from user order by age
userList.stream()
.sorted(Comparator.comparingInt(User::getAge))
.forEach(System.out::println);

分页 limit - skip、limit

// select * from user limit 5
userList.stream()
.limit(5)
.forEach(System.out::println); // select * from user limit 5, 5
userList.stream()
.skip(5)
.limit(5)
.forEach(System.out::println); // select * from user order by age limit 1
userList.stream()
.sorted(Comparator.comparingInt(User::getAge))
.limit(1)
.forEach(System.out::println);
// 或者
Optional<User> minAgeUser = userList.stream()
.sorted(Comparator.comparingInt(User::getAge))
.findFirst();

是否存在 exists - anymatch

// select exists(select * from user where name='小海')
// 有没有名字叫“小海”的用户
boolean exists0 = userList.stream()
.anyMatch(e -> e.getName().equals("小海")); // select not exists(select * from user where name='小海')
// 是不是没有名字叫“小海”的用户
boolean exists1 = userList.stream()
.noneMatch(e -> e.getName().equals("小海")); // 是不是所有用户年龄都小于10岁
boolean exists2 = userList.stream()
.allMatch(e -> e.getAge() < 10);

收集操作 collect

收集操作就是遍历 stream 中的元素,并进行累加处理,即归约 reduction

归约的定义

A reduction operation (also called a fold) takes a sequence of input elements and combines them into a single summary result by repeated application of a combining operation, such as finding the sum or maximum of a set of numbers, or accumulating elements into a list.

前面提到的 max() min() count() reduce() 都属于 reduction operation

collect() 又和前面这几种归约操作有所区别,它是 Mutable reduction 动态归约

动态归约的定义

A mutable reduction operation accumulates input elements into a mutable result container, such as a Collection or StringBuilder, as it processes the elements in the stream

区别:动态归约将结果放进 Collection StringBuilder 这样的动态容器中,所以称为动态归约。

Stream 接口提供了两个 collect() 方法

<R> R collect(Supplier<R> supplier,
BiConsumer<R, ? super T> accumulator,
BiConsumer<R, R> combiner); <R, A> R collect(Collector<? super T, A, R> collector);

我们只需理解了第一个方法,第二个方法就手到擒来了

理解第一个 collect 方法,强烈建议阅读文档 动态归约的定义,下面只简单的介绍一下它

三个参数:

  • 供给者 supplier:负责提供动态容器,例如 Collectors、StringBuilder
  • 累加器 accumulator:负责将流中的元素做累加处理
  • 合并者 combiner :负责将两个容器的元素合并在一起

在串行流中,combiner 根本没有执行,所以随便写点啥满足参数对象就行。

如果说串行流是单线程,那么并行流就是多线程了

举个例子:


ArrayList<String> strings = new ArrayList<>();
for (T element : stream) {
strings.add(element.toString());
}
// 等同于
ArrayList<String> strings = stream.collect(() -> new ArrayList<>(),
(c, e) -> c.add(e.toString()),
(c1, c2) -> c1.addAll(c2));

与其传递三个参数这么麻烦,还不如直接传递一个对象呢!

这就是第二个 collect() 方法的由来,使用收集器 Collector 来替代三个参数

实际上,我们一般不需要自己创建 Collector 对象,Java8 提供了一个 Collectors 类,专门提供收集器 Collector 对象。毕竟我们平时能够使用到的收集操作也就那几种:转为集合对象、分组、统计。

下面以例子演示


在初看 stream 操作的时候,我被什么创建、中间操作、终止操作、不会改变原对象给弄晕了,我根本不关心这些,我的第一想法是怎么将操作后的数据导出来,重新变成集合对象。

toCollection

不使用收集器的情况下:

List<User> subUserList1 = userList.stream()
.filter(e -> e.getAge() < 10)
.filter(e -> e.getGender() == "男")
.collect(() -> new ArrayList<>(),
(c, e) -> c.add(e),
(c1, c2) -> c1.addAll(c2));

在 collect() 方法第二个参数累加器 accumulator (c, e) -> c.add(e) 这里,对流中元素进行了遍历,所以可以把流中元素添加到任意的集合容器中,List、Set、Map 等等

使用 Collectors 工具类提供的收集器:

// toList()
List<User> list = userList.stream()
.filter(e -> e.getAge() < 10)
.filter(e -> e.getGender() == "男")
.collect(Collectors.toList()); // toSet()
Set<User> set = userList.stream()
.filter(e -> e.getAge() < 10)
.filter(e -> e.getGender() == "男")
.collect(Collectors.toSet()); // toCollection(),想要返回什么容器,就 new 一个
ArrayList<User> collection = userList.stream()
.filter(e -> e.getAge() < 10)
.filter(e -> e.getGender() == "男")
.collect(Collectors.toCollection(
() -> new ArrayList<>()
));

这里插播一条新闻:如何将流转为数组?

Stream 提供了方法 toArray()

Object[] toArray();
<A> A[] toArray(IntFunction<A[]> generator);

小试牛刀:

Object[] nameArray = userList.stream()
.map(e -> e.getName())
.toArray();
Arrays.stream(nameArray)
.forEach(System.out::println);
// 转为 User 对象数组
User[] users = userList.stream()
.filter(e -> e.getGender() == "女")
.toArray(User[]::new);
Arrays.stream(users)
.forEach(System.out::println);

toStringBuilder

不使用收集器的情况下:

StringBuilder joinName = userList.stream()
.map(e -> e.getName())
.collect(StringBuilder::new,
(s, e) -> s = s.length() > 0 ? s.append("-" + e) : s.append(e),
(s1, s2) -> s1.append(s2)
);

谁能告诉我在Java中怎么单独使用三元运算符?s = s.length() > 0 ? s.append("-" + e) : s.append(e) 我想把 s = 省略掉,但 Java 中不行

使用 Collectors 类提供的收集器:

String joinName1 = userList.stream()
.map(e -> e.getName())
.collect(Collectors.joining()); String joinName2 = userList.stream()
.map(e -> e.getName())
.collect(Collectors.joining("-")); String joinName3 = userList.stream()
.map(e -> e.getName())
.collect(Collectors.joining("-", "[", "]"));

至于 Collectors.joining() 参数分别代表什么含义,看一下它们的参数名称,就明白了

public static Collector<CharSequence, ?, String> joining(CharSequence delimiter,	// 分隔符
CharSequence prefix, // 前缀
CharSequence suffix) // 后缀

toMap

在 Collectors 中一共有3个 toMap(),它们用来处理不同的问题

两个参数的 toMap

 Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper) {
return toMap(keyMapper, valueMapper, throwingMerger(), HashMap::new);
}

参数 keyMapper 用来获取key;valueMapper 用来获取 value

它的内部调用了四个参数的 toMap() 方法

例子

Map<Integer, User> map1 = userList.stream()
.collect(Collectors.toMap(e -> e.getId(), Function.identity()));
System.out.println(map1);
// Function.identity() 等价于 e -> e // select id, name, gender from user
Map<Integer, Map<String, Object>> map2 = userList.stream()
.collect(Collectors.toMap(e -> e.getId(), e -> {
Map<String, Object> map = new HashMap<>();
map.put("gender", e.getGender());
map.put("name", e.getName());
map.put("id", e.getId());
return map;
}));
System.out.println(map2);

你:如果 key 冲突了咋办?

Java8:你想咋办就咋办

三个参数的 toMap

Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction) {
return toMap(keyMapper, valueMapper, mergeFunction, HashMap::new);
}

第三个参数 mergeFunction 就是用来处理 key 键冲突的

内部也是调用了四个参数的 toMap() 方法

例子

// 如果 key 冲突,那么将冲突的 value 值拼接在一起
Map<String, String> map3 = userList.parallelStream()
.collect(Collectors.toMap(
e -> e.getGender(),
e -> e.getName(),
(o1, o2) -> o1 + ", " + o2
)
);
System.out.println(map3);

你:我想自己 new 一个 Map 对象

四个参数的 toMap

Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction,
Supplier<M> mapSupplier)

参数 mapSupplier 用来提供返回容器

例子

LinkedHashMap<String, String> map4 = userList.parallelStream()
.collect(Collectors.toMap(e -> e.getGender(), e -> e.getName(), (o1, o2) -> o1 + ", " + o2, LinkedHashMap::new));
System.out.println(map4);

reducing

单参数和两参数的 reducing()

Collector<T, ?, Optional<T>> reducing(BinaryOperator<T> op)
Collector<T, ?, U> reducing(U identity, Function<? super T,? extends U> mapper, BinaryOperator<U> op)

以例子具体解释这两个方法

Optional<String> names1 = userList.stream()
.map(User::getName)
.collect(Collectors.reducing((e1, e2) -> e1 + "," + e2));
System.out.println(names1.get()); // 等同于
String names2 = userList.stream()
.collect(Collectors.reducing(
"", (e) -> e.getName(), (e1, e2) -> e1 + "," + e2)
);
System.out.println(names2);

输出结果:

小明,小青,小海,阿刁,小阳,小强,小帅,小云
,小明,小青,小海,阿刁,小阳,小强,小帅,小云

参数 identity 表示返回结果的初始值

三参数的 reducing()

reducing(U identity, Function<? super T,? extends U> mapper, BinaryOperator<U> op)

identity 是初始值,mapper 会对元素先进行一次处理,然后 op 对元素进行归约操作

注意: 返回类型要和参数 identity 的一致。

你也许会纳闷,为什么有的返回一个 Optional<String> 类型数据,而有的就返回了 String

因为含有参数 identity 的 reduing 方法中返回值有初始值,也就是 identity,所以不会出现空的情况

下面Collectors 提供的一些常用归约收集器

// minBy、maxBy
Optional<User> minAgeUser = userList.stream()
.collect(Collectors.minBy((o1, o2) -> o1.getAge() - o2.getAge())); // counting
Long count = userList.stream()
.collect(Collectors.counting()); // summingInt、summingLong、summingDouble、
Integer sumAge = userList.stream()
.collect(Collectors.summingInt(User::getAge)); // averagingInt、averagingLong、averagingDouble
// 平均值内部是总值/数量,所以返回值是浮点数 dobule
Double avgAge = userList.stream()
.collect(Collectors.averagingInt(User::getAge));

你也许觉得每次都要执行一遍 minBy、maxBy、counting、summingXxx、averagingXxx 这些太麻烦了,有没有一次执行就获取所有这些方法结果?

有的。这就是 summarizingXxx

Collector<T, ?, IntSummaryStatistics> summarizingInt(ToIntFunction<? super T> mapper)
Collector<T, ?, LongSummaryStatistics> summarizingLong(ToLongFunction<? super T> mapper)
Collector<T, ?, DoubleSummaryStatistics> summarizingDouble(ToDoubleFunction<? super T> mapper)

这里不演示了,实际上你看一下 XxxSummaryStatistics 这些类就明白了,比如

public class IntSummaryStatistics implements IntConsumer {
private long count;
private long sum;
private int min = Integer.MAX_VALUE;
private int max = Integer.MIN_VALUE;
...
}

group by

最最激动人心的时候到了,我们要使用分组了!!!

Map<String, List<User>> map = userList.stream()
.collect(Collectors.groupingBy(User::getGender));

SQL 中的 group by 结果集中只能包含分组字段和聚合函数计算结果,这段代码比它更加全面

我们使用如下语句输出结果

map.keySet().stream()
.forEach((e) -> {
System.out.println(e + "=" + map.get(e));
});

显示结果:

女=[User{id=102, name='小青', age=12, gender='女', province='宁夏回族自治区', city='银川市'}, User{id=108, name='阿刁', age=18, gender='女', province='西藏自治区', city='拉萨市'}, User{id=104, name='小阳', age=9, gender='女', province='新疆维吾尔自治区', city='乌鲁木齐市'}, User{id=107, name='小云', age=15, gender='女', province='河北省', city='石家庄市'}]
男=[User{id=101, name='小明', age=10, gender='男', province='青海省', city='西宁市'}, User{id=103, name='小海', age=8, gender='男', province='西藏自治区', city='拉萨市'}, User{id=105, name='小强', age=14, gender='男', province='陕西省', city='西安市'}, User{id=106, name='小帅', age=15, gender='男', province='河北省', city='石家庄市'}]

它真的分组了!!这是真正的分组

那怎么对分组中的元素进行操作呢,像 SQL 那样??

完全不用担心,Collectors 提供了三个 groupBy 方法返回分组收集器

Collector<T, ?, Map<K, List<T>>> groupingBy(Function<? super T,? extends K> classifier)

Collector<T, ?, Map<K, D>> groupingBy(Function<? super T,? extends K> classifier,
Collector<? super T,A,D> downstream) Collector<T, ?, M> groupingBy(Function<? super T,? extends K> classifier,
Supplier<M> mapFactory,
Collector<? super T,A,D> downstream)

让我们放飞想象的翅膀,思考一下这几个参数分别有什么用。

downstream ?有 down 就表示有 up。那么谁是 upstream,很明显是 userList.stream,那么 downstream 就是分组集合的流喽。猜测 downstream 收集器是对分组中的元素进行归约操作的,就像是分组 SQL 语句字段中的聚合操作一样。

// select gender, count(*) from user group by gender
Map<String, Long> map2 = userList.stream()
.collect(Collectors.groupingBy(User::getGender, Collectors.counting()));
System.out.println(map2);

输出结果确实不出所料!这就是证明参数 downstream 确实是分组集合元素的收集器。

Supplier<M> mapFactory 这函数式接口方法不会有参数传入,所以不会操作集合元素;它只是返回一个变量。同志们,注意观察三个方法返回值,前二者都指定了 Map 作为归约操作的返回类型,而第三个要我们自己定义,使用 mapFactory 提供返回的数据容器

两参数的 groupingBy 方法其实是调用了三参数的 groupingBy 方法(而单参数 groupingBy 调用了两参数的 groupingBy)

    Collector<T, ?, Map<K, D>> groupingBy(Function<? super T, ? extends K> classifier,
Collector<? super T, A, D> downstream) {
return groupingBy(classifier, HashMap::new, downstream);
}

groupingBy 经常使用 Collectors.mapping() 处理分组集合

Map<String, List<Map<String, Object>>> map4 = userList.stream()
.collect(Collectors.groupingBy(
User::getGender,
Collectors.mapping(e -> {
Map<String, Object> m = new HashMap<>();
m.put("name", e.getName());
m.put("id", e.getId());
return m;
}, Collectors.toList())
));
System.out.println(map4);

输出结果:

{女=[{name=小青, id=102}, {name=阿刁, id=108}, {name=小阳, id=104}, {name=小云, id=107}], 男=[{name=小明, id=101}, {name=小海, id=103}, {name=小强, id=105}, {name=小帅, id=106}]}

partitionBy

实际上也是分组,只不过 partitionBy 是按照布尔值(真假)来分组

Collector<T, ?, Map<Boolean, List<T>>> partitioningBy(Predicate<? super T> predicate) {
return partitioningBy(predicate, toList());
} Collector<T, ?, Map<Boolean, D>> partitioningBy(Predicate<? super T> predicate,
Collector<? super T, A, D> downstream)

例子:大于10岁为一组,小于等于10的为一组

Map<Boolean, List<User>> map1 = userList.stream()
.collect(Collectors.partitioningBy(e -> e.getAge() > 10));

例子:统计大于10岁的有多少人,小于等于10岁的有多少人

Map<Boolean, Long> map2 = userList.stream()
.collect(Collectors.partitioningBy(e -> e.getAge() > 10, Collectors.counting()));

第二个参数 downstream 用来处理分组集合

结语

Java8 提供的 stream 几乎是穷尽了所有集合元素能有的操作,起码是穷尽了我脑海里对集合元素操作的所有想象

这篇文章也列举了 stream 绝大部分的功能,尽量写得通俗易懂,但读者理解起来可能还是有模糊的地方,这时建议大家参考 Java8 API 官方文档 ,多做几个 Demo 加深理解

不要过度使用

stream 是为了方便集合操作,简化代码而推出的,提升代码执行效率并不是它的目的。

虽然,并行流会对代码的执行效率有较大的提升(尤其是数据量非常大的时候),但也依赖于计算机的CPU配置。

Stream 能实现的功能,for 循环都能实现,只是 Stream 代码一般比较简洁,可读性强。但在某些情况下,使用 for 循环要比 Stream 要简洁代码逻辑清晰

举个例子:

	private List<Order> orderList = Arrays.asList(
new Order(103),
new Order(106),
new Order(107),
new Order(104),
new Order(102),
new Order(103),
new Order(102),
new Order(101),
new Order(104),
new Order(102),
new Order(105)
);
// 现根据 userId 设置 Order 对象的 name 属性 // 使用 stream
List<Order> newOrderList = orderList.stream()
.map(o -> userList.stream()
.filter(u -> u.getId() == o.getUserId())
.findFirst()
.map(u -> {
o.setUserName(u.getName());
return o;
})
.orElse(o))
.collect(Collectors.toList());
newOrderList.stream().forEach(System.out::println); // 使用 for 循环
for (Order o : orderList) {
for (User u : userList) {
if (o.getUserId() == u.getId()) {
o.setUserName(u.getName());
break;
}
}
}
orderList.stream().forEach(System.out::println);

在这个例子中,使用 for 循环要比 使用 stream 干净利落的多,代码逻辑清晰简明,可读性也比 stream 好。

Java8 新特性(二)- Stream的更多相关文章

  1. 乐字节-Java8新特性之Stream流(上)

    上一篇文章,小乐给大家介绍了<Java8新特性之方法引用>,下面接下来小乐将会给大家介绍Java8新特性之Stream,称之为流,本篇文章为上半部分. 1.什么是流? Java Se中对于 ...

  2. Java8 新特性之Stream API

    1. Stream 概述 Stream 是Java8中处理集合的关键抽象概念,可以对集合执行非常复杂的查找,过滤和映射数据等操作; 使用 Stream API 对集合数据进行操作,就类似于使用 SQL ...

  3. 【Java8新特性】Stream API有哪些中间操作?看完你也可以吊打面试官!!

    写在前面 在上一篇<[Java8新特性]面试官问我:Java8中创建Stream流有哪几种方式?>中,一名读者去面试被面试官暴虐!归根结底,那哥儿们还是对Java8的新特性不是很了解呀!那 ...

  4. 【Java8新特性】- Stream流

    Java8新特性 - Stream流的应用 生命不息,写作不止 继续踏上学习之路,学之分享笔记 总有一天我也能像各位大佬一样 一个有梦有戏的人 @怒放吧德德 分享学习心得,欢迎指正,大家一起学习成长! ...

  5. Java8新特性之Stream

    原文链接:http://ifeve.com/stream/ Java8初体验(二)Stream语法详解 感谢同事[天锦]的投稿.投稿请联系 tengfei@ifeve.com上篇文章Java8初体验( ...

  6. Java8 新特性 Lambda & Stream API

    目录 Lambda & Stream API 1 Lambda表达式 1.1 为什么要使用lambda表达式 1.2 Lambda表达式语法 1.3 函数式接口 1.3.1 什么是函数式接口? ...

  7. Java8新特性--流(Stream)

    1.简介      Java 8是Java自Java 5(发布于2004年)之后的最重要的版本.这个版本包含语言.编译器.库.工具和JVM等方面的十多个新特性.在本文中我们一起来学习引入的一个新特性- ...

  8. java8 新特性入门 stream/lambda

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

  9. 【Java8新特性】Stream(分类+案例)

    一.Stream概述 什么是Stream? Stream是Java8引入的全新概念,它用来处理集合中的数据,可以让你以一种声明的方式处理数据. Stream 使用一种类似用 SQL 语句从数据库查询数 ...

随机推荐

  1. git查看当前分支所属

    1.git branch -vv 2.git config --lis

  2. Spring Security 中的 Bcrypt

    最近在写用户管理相关的微服务,其中比较重要的问题是如何保存用户的密码,加盐哈希是一种常见的做法.知乎上有个问题大家可以先读一下: 加盐密码保存的最通用方法是? 对于每个用户的密码,都应该使用独一无二的 ...

  3. win10 uwp win2d CanvasVirtualControl 与 CanvasAnimatedControl

    本文来告诉大家 CanvasVirtualControl ,在什么时候使用这个控件. 在之前的入门教程win10 uwp win2d 入门 看这一篇就够了我直接用的是CanvasControl,实际上 ...

  4. P1082 数列分段

    题目描述 对于给定的一个长度为 \(N\) 的正整数数列 \(A_i\) ,现要将其分成连续的若干段,并且每段和不超过 \(M\) (可以等于 \(M\) ),问最少能将其分成多少段使得满足要求. 输 ...

  5. P1058 车厢重组

    题目描述 在一个旧式的火车站旁边有一座桥,其桥面可以绕河中心的桥墩水平旋转.一个车站的职工发现桥的长度最多能容纳两节车厢,如果将桥旋转 \(180\) 度,则可以把相邻两节车厢的位置交换,用这种方法可 ...

  6. 1119 机器人走方格 V2 (组合数学)

    M * N的方格,一个机器人从左上走到右下,只能向右或向下走.有多少种不同的走法?由于方法数量可能很大,只需要输出Mod 10^9 + 7的结果.   Input 第1行,2个数M,N,中间用空格隔开 ...

  7. ZR1158

    ZR1158 http://www.zhengruioi.com/contest/446/problem/1158 给定限制的问题大多数都是容斥或者二分,或者二分之后容斥 首先,这个问题的第一步我们还 ...

  8. RabbitMQ-Exchange交换器

    交换器分类 RabbitMQ的Exchange(交换器)分为四类: direct(默认) headers fanout topic 其中headers交换器允许你匹配AMQP消息的header而非路由 ...

  9. H3C在设备上使用TFTP服务

  10. Linux环境下安装mysql(远程连接),zookeeper,java,tomcat.

    环境阿里云centos7.5 64位 + FinalShell + Navicat Permium 12 用到的压缩包(版本看后缀) 注意:安装均在/usr/local目录下,下面代码中#号不要复制上 ...