使用stream在内存中处理数据

使用stream在内存中处理数据

stream编程产生背景:

随着互联网行业的兴起,数据来源不再是单一的数据库。可能是缓存,数据库,消息中间件,es等。在对数据进行处理时,需要进行整合数据,原有的编写sql关联查询等方式不再适用复杂数据的处理。在此应用场景下,stream编程应运而生。

Stream介绍

stream是java Se8 除lambda外的又一重要属性,stream基于lambda构建和使用。stream是集合框架的伙伴框架,同样在内存中处理数据,stream’有着高效的处理过程。stream编程不仅使你的代码效率高,并且阅读性更好。

List<String> strings = List.of("one","two","three","four");
var map = strings.stream()
                 .collect(groupingBy(String::length, counting()));
map.forEach((key, value) -> System.out.println(key + " :: " + value));

这段代码分以下步骤完成:
通过 groupingBy(String::length) 对strings进行分组
通过counting()统计每个长度字符的数量
然后创建Map<Integer, Long> 去存放结果

3 :: 2
4 :: 1
5 :: 1

映射-过滤-整合 算法的介绍

这个算法是处理数据的经典算法。下面举一个例子来说明:
假设你有一个 Sale集合,Sale里面包含了 date,product引用,金额amount,简单起见,假设金额为Integer。

public class Sale {
    private String product;
    private LocalDate date;
    private int amount;

    // constructors, getters, setters
    // equals, hashCode, toString
}

假设你要计算三月份的总金额,你会写出如下的算法:

List<Sale> sales = ...; // this is the list of all the sales
int amountSoldInMarch = 0;
for (Sale sale: sales) {
    if (sale.getDate().getMonth() == Month.MARCH) {
        amountSoldInMarch += sale.getAmount();
    }
}
System.out.println("Amount sold in March: " + amountSoldInMarch);

第一个步骤是过滤,过滤那些不是三月份的订单。
第二步是获取过滤后的金额。
第三步是对金额求和。
sql中也可以进行如下的处理:

select sum(amount)
from Sales
where extract(month from date) = 3;

定制化结果而不是算法

通过之前所写的sql,我们可以看到我们只负责三月份金额的求和,具体的性能优化交给数据库服务器。
计算这个金额的代码片段是关于金额求和的每一步的描述。它精确的规定了每一步的执行,而没有留给java虚拟机优化的空间。
Stream Api的两个目标是使代码更具表现力和可读性,并给java运行时提供优化计算的调整空间。

将对象映射为其他的值

映射-过滤-整合算法的第一步是映射,映射是对对象或值进行转换。映射是一个接一个的,如果你10个对象进行转换,那么将得到10个转换后值。
映射改变的是对象的类型,不改变对象的顺序。
映射由function接口建模,接受一个类型参数,返回另一个类型的数据,特殊化的 function接口可以映射为基本类型或其他的。

过滤对象

在另一方面,过滤不会接触到处理的对象,只会选择保留对象或移除该对象。
过滤不改变数据的类型,只改变数据的数量。
过滤由predicate接口建模,predicate接口只接受一个参数,并返回一个boolean类型。

数据整合

整合比看起来的要复杂。它与sql中的聚合类似。如COUNT, SUM, MIN, MAX, AVERAGE。stream Api支持所有的聚合。
整合数据允许你构建复杂的结构数据,如list,set,map,和其他自己定义的类型。

优化映射-过滤-整合 算法

看一个例子:假设你有一个城市列表,现在要统计人口超过10万的城市的总人口数

List<City> cities = ...;

int sum = 0;
for (City city: cities) {
    int population = city.getPopulation();
    if (population > 100_000) {
        sum += population;
    }
}

System.out.println("Sum = " + sum);

你可以在城市列表使用另一种 映射-过滤-整合处理数据。
现在假设没有stream api,在集合上定义map(),filter(),sum()方法,

int sum = cities.map(city -> city.getPopulation())
                .filter(population -> population > 100_000)
                .sum();

进一步的拆解开来:

Collection<Integer> populations         = cities.map(city -> city.getPopulation());
Collection<Integer> filteredPopulations = populations.filter(population -> population > 100_000);
int sum                                 = filteredPopulations.sum();

分析一下这段代码:
第一步是映射步骤。您看到,如果必须处理1,000个城市,那么这个映射步骤将生成1,000个整数,并将它们放入一个集合中。
第二步是过滤步骤。它遍历所有元素,并根据给定的标准删除其中一些元素。这是另外1000个要测试的元素和另一个要创建的集合,可能更小。
因为此代码返回一个集合,所以它映射所有城市,然后过滤得到的整数集合。这与您最初编写的for循环的工作方式非常不同。存储这个中间的整数集合可能会导致很大的开销,特别是在有很多城市要处理的情况下。for循环没有这种开销:它直接对结果中的整数求和,而不将它们存储在中间结构中。
这种开销很糟糕,在某些情况下甚至会更糟。假设您需要知道是否有人口超过10万的城市。也许集合的第一个城市就是这样一个城市。在这种情况下,你几乎不需要付出任何努力就能得到结果。首先,构建来自你的城市的所有人口的集合,然后过滤它,并检查结果是否为空,这将是荒谬的。
出于明显的性能原因,创建在Collection接口上返回Collection的map()方法并不是正确的方法。最终,您将创建不必要的中间结构,并在内存和CPU上造成很高的开销。
这就是为什么没有将map()和filter()方法添加到Collection接口的原因。相反,它们是在Stream接口上创建的。
正确的处理方式如下:

Stream<City> streamOfCities         = cities.stream();
Stream<Integer> populations         = streamOfCities.map(city -> city.getPopulation());
Stream<Integer> filteredPopulations = populations.filter(population -> population > 100_000);
int sum = filteredPopulations.sum(); // in fact this code does not compile; we'll fix it later

这引导出了stream的一个重要的属性

A stream is an object that does not store any data.

stream 不存储任何数据。

使用中间操作创建管道

中间操作是返回另一个流的操作。调用这样的操作将在现有的操作管道上增加一个操作,而不处理任何数据。它由一个返回流的方法建模。

用最终操作计算结果

终端操作是不返回流的操作。调用这样的操作将触发对流源元素的消费。然后,这些元素由中间操作流水线处理,每次处理一个元素。
终端操作由一个方法建模,该方法返回除流之外的任何内容,包括void。
您不能在一个流上调用多个中间方法或终端方法。如果你这样做,你会得到一个IllegalStateException和以下消息:“流已经被操作或关闭”。

使用特殊的数字流来避免包装

stream api给了四个接口:
第一个是Stream,你可以使用它定义任何类型的管道。
接着有三个特殊化的数字流: IntStream, LongStream and DoubleStream.
这三个数字流使用基本数字类型而不是包装类型来处理数据。
它们有stream流中的大多数方法,只有少许不同。因为它们是处理数字的,所有它们有一些整合方法,在stream中是没有的。如:
sum():计算总和
min (), max():计算流的最小或最大数目
average():计算数值的平均值
summaryStatistics():这个调用产生一个特殊的对象,该对象携带多个统计信息,所有这些统计信息都是在对数据的一次传递中计算出来的。这些统计数据是该流处理的元素数量、最小值、最大值、总和和平均值。

跟随着良好的练习

如您所见,只允许调用流上的一个方法,即使该方法是中间方法。因此,将流存储在字段或局部变量中是无用的,有时甚至是危险的。编写将流作为参数的方法也可能是危险的,因为您不能确定接收到的流是否已经被操作过。流应该在现场创建和使用。
流是一个连接到源的对象。它从这个源中提取它处理的元素。流本身不应该修改这个源。这样做将导致未指定的结果。在某些情况下,这个源是不可变的或只读的,所以您不能这样做,但在某些情况下您可以这样做。

我们对Markdown编辑器进行了一些功能拓展与语法支持,除了标准的Markdown编辑器功能,我们增加了如下几点新功能,帮助你用它写博客:

  1. 全新的界面设计 ,将会带来全新的写作体验;
  2. 在创作中心设置你喜爱的代码高亮样式,Markdown 将代码片显示选择的高亮样式 进行展示;
  3. 增加了 图片拖拽 功能,你可以将本地的图片直接拖拽到编辑区域直接展示;
  4. 全新的 KaTeX数学公式 语法;
  5. 增加了支持甘特图的mermaid语法1 功能;
  6. 增加了 多屏幕编辑 Markdown文章功能;
  7. 增加了 焦点写作模式、预览模式、简洁写作模式、左右区域同步滚轮设置 等功能,功能按钮位于编辑区域与预览区域中间;
  8. 增加了 检查列表 功能。

功能快捷键

撤销:Ctrl/Command + Z
重做:Ctrl/Command + Y
加粗:Ctrl/Command + B
斜体:Ctrl/Command + I
标题:Ctrl/Command + Shift + H
无序列表:Ctrl/Command + Shift + U
有序列表:Ctrl/Command + Shift + O
检查列表:Ctrl/Command + Shift + C
插入代码:Ctrl/Command + Shift + K
插入链接:Ctrl/Command + Shift + L
插入图片:Ctrl/Command + Shift + G
查找:Ctrl/Command + F
替换:Ctrl/Command + G

合理的创建标题,有助于目录的生成

直接输入1次#,并按下space后,将生成1级标题。
输入2次#,并按下space后,将生成2级标题。
以此类推,我们支持6级标题。有助于使用TOC语法后生成一个完美的目录。

如何改变文本的样式

强调文本 强调文本

加粗文本 加粗文本

标记文本

删除文本

引用文本

H2O is是液体。

210 运算结果是 1024.

插入链接与图片

链接: link.

图片: Alt

带尺寸的图片: Alt

居中的图片: Alt

居中并且带尺寸的图片: Alt

当然,我们为了让用户更加便捷,我们增加了图片拖拽功能。

如何插入一段漂亮的代码片

博客设置页面,选择一款你喜欢的代码片高亮样式,下面展示同样高亮的 代码片.

// An highlighted block
var foo = 'bar';

生成一个适合你的列表

  • 项目
    • 项目
      • 项目
  1. 项目1
  2. 项目2
  3. 项目3
  • 计划任务
  • 完成任务

创建一个表格

一个简单的表格是这么创建的:

项目Value
电脑$1600
手机$12
导管$1

设定内容居中、居左、居右

使用:---------:居中
使用:----------居左
使用----------:居右

第一列第二列第三列
第一列文本居中第二列文本居右第三列文本居左

SmartyPants

SmartyPants将ASCII标点字符转换为“智能”印刷标点HTML实体。例如:

TYPEASCIIHTML
Single backticks'Isn't this fun?'‘Isn’t this fun?’
Quotes"Isn't this fun?"“Isn’t this fun?”
Dashes-- is en-dash, --- is em-dash– is en-dash, — is em-dash

创建一个自定义列表

Markdown
Text-to- HTML conversion tool
Authors
John
Luke

如何创建一个注脚

一个具有注脚的文本。2

注释也是必不可少的

Markdown将文本转换为 HTML

KaTeX数学公式

您可以使用渲染LaTeX数学表达式 KaTeX:

Gamma公式展示 Γ ( n ) = ( n − 1 ) ! ∀ n ∈ N \Gamma(n) = (n-1)!\quad\forall n\in\mathbb N Γ(n)=(n1)!nN 是通过欧拉积分

Γ ( z ) = ∫ 0 ∞ t z − 1 e − t d t   . \Gamma(z) = \int_0^\infty t^{z-1}e^{-t}dt\,. Γ(z)=0tz1etdt.

你可以找到更多关于的信息 LaTeX 数学表达式here.

新的甘特图功能,丰富你的文章

Mon 06 Mon 13 Mon 20 已完成 进行中 计划一 计划二 现有任务 Adding GANTT diagram functionality to mermaid
  • 关于 甘特图 语法,参考 这儿,

UML 图表

可以使用UML图表进行渲染。 Mermaid. 例如下面产生的一个序列图:

张三 李四 王五 你好!李四, 最近怎么样? 你最近怎么样,王五? 我很好,谢谢! 我很好,谢谢! 李四想了很长时间, 文字太长了 不适合放在一行. 打量着王五... 很好... 王五, 你怎么样? 张三 李四 王五

这将产生一个流程图。:

链接
长方形
圆角长方形
菱形
  • 关于 Mermaid 语法,参考 这儿,

FLowchart流程图

我们依旧会支持flowchart的流程图:

Created with Raphaël 2.3.0 开始 我的操作 确认? 结束 yes no
  • 关于 Flowchart流程图 语法,参考 这儿.

导出与导入

导出

如果你想尝试使用此编辑器, 你可以在此篇文章任意编辑。当你完成了一篇文章的写作, 在上方工具栏找到 文章导出 ,生成一个.md文件或者.html文件进行本地保存。

导入

如果你想加载一篇你写过的.md文件,在上方工具栏可以选择导入功能进行对应扩展名的文件导入,
继续你的创作。


  1. mermaid语法说明 ↩︎

  2. 注脚的解释 ↩︎

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值