三、流和并行

引入流

  • 流是什么
    集合是Java中使用最多的API,但集合操作却远远算不上完美:
    1.很多业务逻辑都涉及类似于数据库的操作,比如对几道菜按照类别进行分组 (比如全素 菜肴),或查找出最贵的菜。你自己用迭代器重新实现过这些操作多少遍?
    2.要是要处理大量元素又该怎么办呢?为了提高性能,你需要并行处理,并利用多核架构
    流是Java API的新成员,它允许你以声明性方式处理数据集合(通过查询语句来表达,而不 是临时编写一个实现)
    1.声明性——更简洁,更易读
    2.可复合——更灵活
    3.可并行——性能更好

那么,流到底是什么呢?简短的定义就是“从支持数据处理操作的源生成的元素序列”

1.元素序列:就像集合一样,流也提供了一个接口,可以访问特定元素类型的一组有序 值。
2.源:流会使用一个提供数据的源,如集合、数组或输入/输出资源。
3.数据处理操作:流的数据处理功能支持类似于数据库的操作,以及函数式编程语言中 的常用操作,如filter、map、reduce、find、match、sort等。流操作可以顺序执 行,也可并行执行。
4.流水线:很多流操作本身会返回一个流,这样多个操作就可以链接起来,形成一个大的流水线。
5.内部迭代:与使用迭代器显式迭代的集合不同,流的迭代操作是在背后进行的。

return dishes.stream()
                .filter(d -> d.getCalories() < 400)
                .sorted(comparing(Dish::getCalories))
                .map(Dish::getName)
                .limit(3)
                .collect(toList());

在本例中,我们先是对menu调用stream方法,由菜单得到一个流。数据源是菜肴列表(菜 单),它给流提供一个元素序列。接下来,对流应用一系列数据处理操作:filter、map、limit 和collect。除了collect之外,所有这些操作都会返回另一个流,这样它们就可以接成一条流 水线,于是就可以看作对源的一个查询。最后,collect操作开始处理流水线,并返回结果.
2-流-示例.png
  • 流与集合
    粗略地说,集合与流之间的差异就在于什么时候进行计算。集合是一个内存中的数据结构, 它包含数据结构中目前所有的值——集合中的每个元素都得先算出来才能添加到集合中。(你可 以往集合里加东西或者删东西,但是不管什么时候,集合中的每个元素都是放在内存里的,元素 都得先算出来才能成为集合的一部分。)
    相比之下,流则是在概念上固定的数据结构(你不能添加或删除元素),其元素则是按需计 算的。这是一种生产者-消费者的关系。从另一个角度来说,流就 像是一个延迟创建的集合:只有在消费者要求的时候才会计算值.
    和迭代器类似,流只能遍历一次。遍历完之后,我们就说这个流已经被消费掉了.

外部迭代和内部迭代

使用Collection接口需要用户去做迭代(比如用for-each),这称为外部迭代。 相反, Streams库使用内部迭代——它帮你把迭代做了,还把得到的流值存在了某个地方,你只要给出 一个函数说要干什么就可以了.

  • 流操作


    2-流-操作.png

1.中间操作
诸如filter或sorted等中间操作会返回另一个流。这让多个操作可以连接起来形成一个查 询。重要的是,除非流水线上触发一个终端操作,否则中间操作不会执行任何处理
2.终端操作
终端操作会从流的流水线生成结果
3.流使用
总而言之,流的使用一般包括三件事:

  • 一个数据源(如集合)来执行一个查询;
  • 一个中间操作链,形成一条流的流水线;
  • 一个终端操作,执行流水线,并能生成结果

使用流

让Stream API管理如何处理数据。这样Stream API就可 以在背后进行多种优化。使用内部迭代的话,Stream API可以决定并行运行你的代码。这 要是用外部迭代的话就办不到了,因为你只能用单一线程挨个迭代。

  • 筛选切片


    2-流-筛选.png

    筛选各异的元素
    截断流
    跳过

  • 映射
    map

flatMap


2-流-flatmap.png
  • 查找匹配

  • 归约
    如何把一个流中的元素组合起来,使用reduce操作来表达更复杂的查
    询,比如“计算菜单中的总卡路里”或“菜单中卡路里最高的菜是哪一个”。此类查询需要将流中所有元素反复结合起来,得到一个值,比如一个Integer。


    2-流-reduce.png
  • 交易员Demo
    (1) 找出2011年发生的所有交易,并按交易额排序(从低到高)。
    (2) 交易员都在哪些不同的城市工作过?
    (3) 查找所有来自于剑桥的交易员,并按姓名排序。
    (4) 返回所有交易员的姓名字符串,按字母顺序排序。
    (5) 有没有交易员是在米兰工作的?
    (6) 打印生活在剑桥的交易员的所有交易额。 (7) 所有交易中,最高的交易额是多少?
    (8) 找到交易额最小的交易。

  • 数值流

  • 构建流
    1.值创建类
    2.数组生成流
    3.文件生成流
    4.由函数生成流,创建无限流

用流收集数据

  • 归约和汇总
    collect是一个归约操作,就像reduce一样可以接 受各种做法作为参数,将流中的元素累积成一个汇总结果。具体的做法是通过定义新的 Collector接口来定义的,因此区分Collection、Collector和collect是很重要的
    下面是一些查询的例子,看看你用collect和收集器能够做什么。
    1.对一个交易列表按货币分组,获得该货币的所有交易额总和(返回一个Map<Currency,Integer>)。
    2.将交易列表分成两组:贵的和不贵的(返回一个Map<Boolean, List<Transaction>>)。
    3.创建多级分组,比如按城市对交易分组,然后进一步按照贵或不贵分组(返回一个 Map<Boolean, List<Transaction>>)

前一个例子清楚地展示了函数式编程相对于指令式编程的一个主要优势:你只需指出希望的 结果——“做什么”,而不用操心执行的步骤——“如何做”。在上一个例子里,传递给collect 方法的参数是Collector接口的一个实现,也就是给Stream中元素做汇总的方法,toList只是说“按顺序给每个元素生成一个列表”;在本例中,groupingBy说的是“生成一个 Map,它的键是(货币)桶,值则是桶中那些元素的列表”。


2-流-收集.png
  • 预定义收集器
    预定义收集器的功能,也就是那些可以从Collectors 类提供的工厂方法(例如groupingBy)创建的收集器。它们主要提供了三大功能:
    1.将流元素归约和汇总为一个值
    2.元素分组
    3.元素分区

  • 归约 汇总

  • 广义的归约汇总
    reducing方法创建的收集器
    起始值
    转换函数
    累计函数

  • 分组


    2-流-分组.png
2-流-分组多级.png
2-流-分组嵌套.png
  • 分区
    分区是分组的特殊情况:由一个谓词(返回一个布尔值的函数)作为分类函数,它称分区函 数。分区函数返回一个布尔值,这意味着得到的分组Map的键类型是Boolean,于是它最多可以 分为两组——true是一组,false是一组

  • 收集器接口
    自定义归约操作


    顺序归约过程.png

并行数据处理与性能

  • 并行流
    在Java 7之前,并行处理数据集合非常麻烦。第一,你得明确地把包含数据的数据结构分成若干子部分。第二,你要给每个子部分分配一个独立的线程。第三,你需要在恰当的时候对它们进行同步来避免不希望出现的竞争条件,等待所有线程完成,最后把这些部分结果合并起来。 Java 7引入了一个叫作分支/合并的框架,让这些操作更稳定、更不易出错

高效使用并行流

把顺序流转成并行流轻而易举,但却不一定是好事。我们在本节中
已经指出,并行流并不总是比顺序流快。此外,并行流有时候会和你的直觉不一致,所
以在考虑选择顺序流还是并行流时,第一个也是最重要的建议就是用适当的基准来检查
其性能
1.留意装箱。自动装箱和拆箱操作会大大降低性能。 Java 8中有原始类型流(IntStream、
LongStream、 DoubleStream)来避免这种操作,但凡有可能都应该用这些流
2.有些操作本身在并行流上的性能就比顺序流差。特别是limit和findFirst等依赖于元
素顺序的操作,它们在并行流上执行的代价非常大
3.还要考虑流的操作流水线的总计算成本
4.对于较小的数据量,选择并行流几乎从来都不是一个好的决定
5.考虑流背后的数据结构是否易于分解
6.考虑终端操作中合并步骤的代价是大是小

  • fork/join


    分支合并过程.png
  • Spliterator

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容