文档章节

Java8里面的java.util.Spliterator接口有什么用?

火力全開
 火力全開
发布于 2017/06/02 17:14
字数 1626
阅读 15
收藏 0
点赞 0
评论 0

首先先直接给一个答案:Spliterator(splitable iterator可分割迭代器)接口是Java为了并行遍历数据源中的元素而设计的迭代器,这个可以类比最早Java提供的顺序遍历迭代器Iterator,但一个是顺序遍历,一个是并行遍历

从最早Java提供顺序遍历迭代器Iterator时,那个时候还是单核时代,但现在多核时代下,顺序遍历已经不能满足需求了...如何把多个任务分配到不同核上并行执行,才是能最大发挥多核的能力,所以Spliterator应运而生啦

因为对于数据源而言...集合是描述它最多的情况,所以Java已经默认在集合框架中为所有的数据结构提供了一个默认的Spliterator实现,相应的这个实现其实就是底层Stream如何并行遍历(Stream.isParallel())的实现啦,因此平常用到Spliterator的情况是不多的...因为Java8这次正是一次引用函数式编程的思想,你只需要告诉JDK你要做什么并行任务,关注业务本身,至于如何并行,怎么并行效率最高,就交给JDK自己去思考和优化速度了(想想以前写如何并发的代码被支配的恐惧吧)作为调用者我们只需要去关心一些filtermapcollect等业务操作即可

所以想要看Spliterator的实现,可以直接去看JDK对于集合框架的实现,很多实现类你可以在Spliterators中找到的,也可以直接去你对应集合的stream方法中找到,比如ArrayList点进去的是Collection的默认实现,只需要提供一个Spliterator的实现,然后用StreamSupport就可以构造一个Stream了,相当方便

    default Stream<E> stream() {
        return StreamSupport.stream(spliterator(), false);
    }

    @Override
    default Spliterator<E> spliterator() {
        return Spliterators.spliterator(this, 0);
    }

对于Spliterator接口的设计思想,应该要提到的是Java7的Fork/Join(分支/合并)框架,总得来说就是用递归的方式把并行的任务拆分成更小的子任务,然后把每个子任务的结果合并起来生成整体结果。带着这个理解来看看Spliterator接口提供的方法

boolean tryAdvance(Consumer<? super T> action);
Spliterator<T> trySplit();
long estimateSize();
int characteristics();

第一个方法tryAdvance就是顺序处理每个元素,类似Iterator,如果还有元素要处理,则返回true,否则返回false
第二个方法trySplit,这就是为Spliterator专门设计的方法,区分与普通的Iterator,该方法会把当前元素划分一部分出去创建一个新的Spliterator作为返回,两个Spliterator变会并行执行,如果元素个数小到无法划分则返回null
第三个方法estimateSize,该方法用于估算还剩下多少个元素需要遍历
第四个方法characteristics,其实就是表示该Spliterator有哪些特性,用于可以更好控制和优化Spliterator的使用,具体属性你可以随便百度到,这里就不再赘言

理解了接口方法的意思,现在再来看看自己的实现吧,由于大多数集合都被官方实现了,所以不能搞集合了,只有搞搞类似集合但又不是集合的东西...举以下这个例子,例子反正感觉不是很好,只能说帮助理解哈Spliterator接口了:

问题:求这个字符串"12%3 21sdas s34d dfsdz45 R3 jo34 sjkf8 3$1P 213ikflsd fdg55 kfd"中所有的数字之和例子:比如这种"12%sdf3",和就是12+3=15,这种"12%3 21sdas"和就是12+3+21=36

思路:字符串要用到Stream,只有把整个字符串拆分成一个个Character,而是否是并行,按道理讲只需要改一个标志位即可

先顺序执行代码方式:

/**
 * 字符串中的数字计算器实现
 */
public class NumCounter {

    private int num;
    private int sum;
    // 是否当前是个完整的数字
    private boolean isWholeNum;

    public NumCounter(int num, int sum, boolean isWholeNum) {
        this.num = num;
        this.sum = sum;
        this.isWholeNum = isWholeNum;
    }

    public NumCounter accumulate(Character c){
        if (Character.isDigit(c)){
            return isWholeNum ? new NumCounter(Integer.parseInt("" + c), sum + num, false) : new NumCounter(Integer.parseInt("" + num + c), sum, false);
        }else {
            return new NumCounter(0, sum + num, true);
        }
    }

    public NumCounter combine(NumCounter numCounter){
        return new NumCounter(numCounter.num, this.getSum() + numCounter.getSum(), numCounter.isWholeNum);
    }

    public int getSum() {
        return sum + num;
    }
}
/**
 * 测试类
 */
public class NumCounterTest {

    public static void main(String[] args) {
        String arr = "12%3 21sdas s34d dfsdz45   R3 jo34 sjkf8 3$1P 213ikflsd fdg55 kfd";
        Stream<Character> stream = IntStream.range(0, arr.length()).mapToObj(arr::charAt);
        System.out.println("ordered total: " + countNum(stream));
    }

    private static int countNum(Stream<Character> stream){
        NumCounter numCounter = stream.reduce(new NumCounter(0, 0, false), NumCounter::accumulate, NumCounter::combine);
        return numCounter.getSum();
    }
}

执行结果如下:

如果按照普通方式直接把stream改为并行流...执行结果明显有点...不对

/**
 * 测试类
 */
public class NumCounterTest {

    public static void main(String[] args) {
        String arr = "12%3 21sdas s34d dfsdz45   R3 jo34 sjkf8 3$1P 213ikflsd fdg55 kfd";
        Stream<Character> stream = IntStream.range(0, arr.length()).mapToObj(arr::charAt);
        // 调用parallel()变成并行流
        System.out.println("ordered total: " + countNum(stream.parallel()));
    }

    private static int countNum(Stream<Character> stream){
        NumCounter numCounter = stream.reduce(new NumCounter(0, 0, false), NumCounter::accumulate, NumCounter::combine);
        return numCounter.getSum();
    }
}

此时错误的并行执行结果如下:

为什么会执行错误,是因为默认的Spliterator在并行时并不知道整个字符串从哪里开始切割,由于切割错误,导致把本来完整的数字比如123,可能就切成了12和3,这样加起来的数字肯定不对
若是理解了上诉顺序执行的NumCounter的逻辑,再来看看Spliterator的实现

/**
 * 字符串中的数字分割迭代计算器实现
 */
public class NumCounterSpliterator implements Spliterator<Character> {

    private String str;
    private int currentChar = 0;

    public NumCounterSpliterator(String str) {
        this.str = str;
    }

    @Override
    public boolean tryAdvance(Consumer<? super Character> action) {
        action.accept(str.charAt(currentChar++));
        return currentChar < str.length();
    }

    @Override
    public Spliterator<Character> trySplit() {

        int currentSize = str.length() - currentChar;
        if (currentSize < 10) return null;

        for (int pos = currentSize/2 + currentSize; pos < str.length(); pos++){
            if (pos+1 < str.length()){
                // 当前Character是数字,且下一个Character不是数字,才需要划分一个新的Spliterator
                if (Character.isDigit(str.charAt(pos)) && !Character.isDigit(str.charAt(pos+1))){
                    Spliterator<Character> spliterator = new NumCounterSpliterator(str.substring(currentChar, pos));
                    currentChar = pos;
                    return spliterator;
                }
            }else {
                if (Character.isDigit(str.charAt(pos))){
                    Spliterator<Character> spliterator = new NumCounterSpliterator(str.substring(currentChar, pos));
                    currentChar = pos;
                    return spliterator;
                }
            }
        }
        return null;
    }

    @Override
    public long estimateSize() {
        return str.length() - currentChar;
    }

    @Override
    public int characteristics() {
        return ORDERED + SIZED + SUBSIZED + NONNULL + IMMUTABLE;
    }
}
/**
 * 测试类
 */
public class NumCounterTest {

    public static void main(String[] args) {
        String arr = "12%3 21sdas s34d dfsdz45   R3 jo34 sjkf8 3$1P 213ikflsd fdg55 kfd";
        Stream<Character> stream = IntStream.range(0, arr.length()).mapToObj(arr::charAt);
        System.out.println("ordered total: " + countNum(stream));

        Spliterator<Character> spliterator = new NumCounterSpliterator(arr);
        // 传入true表示是并行流
        Stream<Character> parallelStream = StreamSupport.stream(spliterator, true);
        System.out.println("parallel total: " + countNum(parallelStream));
    }

    private static int countNum(Stream<Character> stream){
        NumCounter numCounter = stream.reduce(new NumCounter(0, 0, false), NumCounter::accumulate, NumCounter::combine);
        return numCounter.getSum();
    }
}

这下可以看到执行结果是正确的了

本文转载自:https://segmentfault.com/q/1010000007087438

火力全開
粉丝 19
博文 199
码字总数 17966
作品 0
卢湾
高级程序员
Java8新特性系列(Interface)

在Java8版本以前,Interface接口中所有的方法都是和,那么在Java8中,Interface有什么新特性呢? 静态成员 在Java8以前,我们要定义一些常量,一般会写一个类,类中都是的一些变量,如下: ...

码上论剑 ⋅ 02/03 ⋅ 0

Collection及其师傅Iterable

上一节我们缕清了Collection家族的关系,接下来我们就来看看这个家族的创始者及其师傅。 1. 师傅Iterable Iterable是一个接口类,先看看接口里面的方法: 其中第二个和第三个方法,都是java8...

村上扼罢 ⋅ 2016/05/26 ⋅ 0

02、Java的lambda表达式和JavaScript的箭头函数

[toc] 前言 在JDK8和ES6的语言发展中,在Java的lambda表达式和JavaScript的箭头函数这两者有着千丝万缕的联系;本次试图通过这篇文章弄懂上面的两个“语法糖”。 简介 Lambda 表达式来源于 ...

weir_will ⋅ 06/14 ⋅ 0

名词王国里的新政-解读Java8之lambda表达式

前几天在reddit上看到Java8 M8 Developer Preview版本已经发布了,不免想要尝鲜一把。Developer Preview版本已经所有Feature都完成了,Java8的特性可以在这里看到http://openjdk.java.net/p...

黄亿华 ⋅ 2013/09/15 ⋅ 11

Java8接口的默认方法

Java8接口的默认方法 什么是默认方法,为什么要有默认方法 简单说,就是接口可以有实现方法,而且不需要实现类去实现其方法。只需在方法名前面加个default关键字即可。 为什么要有这个特性?...

秋风醉了 ⋅ 2015/09/18 ⋅ 0

JAVA 8 新特性 (值得学习)

JAVA 8 已经出现好长时间了,大的互联网公司很多都已经使用了,甚至很多知名互联网公司踩过很多坑,也有一些大牛分享出了他们的实战经验。去很多知名的互联网公司经常会被面试官问,你了解j...

豆芽菜橙 ⋅ 2017/08/20 ⋅ 0

Java8中的WeakHashMap

什么是WeakHashMap? WeakHashMap是基于java弱引用实现的HashMap,感觉一句话就讲的差不多了嘿嘿。 先看看它的定义: 如上,在定义方面和HashMap并没有什么不同。而且在结构上,基本和HashM...

anLA7856 ⋅ 2017/01/13 ⋅ 0

基于注解的Mybatis mapper 接口注意事项

原文:http://my.oschina.net/doctor2014/blog/411580 基于注解的Mybatis mapper 接口功能没有mapper xml配置文件丰富,而且动态sql语句的灵活性不能和xml配置相比。 这里只说一下基于注解的...

Beaver_ ⋅ 2015/05/06 ⋅ 2

Java8在接口的变化

对接口的改变: 1.增加了default方法和static方法,这两种方法完全可以有方法体。 2.default方法属于实例,static方法属于类(接口)。 3.接口里面的静态方法不会被基础。静态变量会被继承。 ...

金馆长1 ⋅ 2015/09/17 ⋅ 0

java8 lambda表达式与集合类批处理操作

一、基本概念 λ表达式可以被当做是一个Object。λ表达式的类型,叫做“目标类型(target type)”。λ表达式的目标类型是“函数接口(functional interface)”,这是Java8新引入的概念。它...

cloud-coder ⋅ 2014/03/31 ⋅ 1

没有更多内容

加载失败,请刷新页面

加载更多

下一页

zblog2.3版本的asp系统是否可以超越卢松松博客的流量[图]

最近访问zblog官网,发现zlbog-asp2.3版本已经进入测试阶段了,虽然正式版还没有发布,想必也不久了。那么作为aps纵横江湖十多年的今天,blog2.2版本应该已经成熟了,为什么还要发布这个2.3...

原创小博客 ⋅ 54分钟前 ⋅ 0

聊聊spring cloud的HystrixCircuitBreakerConfiguration

序 本文主要研究一下spring cloud的HystrixCircuitBreakerConfiguration HystrixCircuitBreakerConfiguration spring-cloud-netflix-core-2.0.0.RELEASE-sources.jar!/org/springframework/......

go4it ⋅ 今天 ⋅ 0

二分查找

二分查找,也称折半查找、二分搜索,是一种在有序数组中查找某一特定元素的搜索算法。搜素过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜素过程结束;如果某一特定元素大于...

人觉非常君 ⋅ 今天 ⋅ 0

VS中使用X64汇编

需要注意的是,在X86项目中,可以使用__asm{}来嵌入汇编代码,但是在X64项目中,再也不能使用__asm{}来编写嵌入式汇编程序了,必须使用专门的.asm汇编文件来编写相应的汇编代码,然后在其它地...

simpower ⋅ 今天 ⋅ 0

ThreadPoolExecutor

ThreadPoolExecutor public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, ......

4rnold ⋅ 昨天 ⋅ 0

Java正无穷大、负无穷大以及NaN

问题来源:用Java代码写了一个计算公式,包含除法和对数和取反,在页面上出现了-infinity,不知道这是什么问题,网上找答案才明白意思是负的无穷大。 思考:为什么会出现这种情况呢?这是哪里...

young_chen ⋅ 昨天 ⋅ 0

前台对中文编码,后台解码

前台:encodeURI(sbzt) 后台:String param = URLDecoder.decode(sbzt,"UTF-8");

west_coast ⋅ 昨天 ⋅ 0

实验楼—MySQL基础课程-挑战3实验报告

按照文档要求创建数据库 sudo sercice mysql startwget http://labfile.oss.aliyuncs.com/courses/9/createdb2.sqlvim /home/shiyanlou/createdb2.sql#查看下数据库代码 代码创建了grade......

zhangjin7 ⋅ 昨天 ⋅ 0

一起读书《深入浅出nodejs》-node模块机制

node 模块机制 前言 说到node,就不免得提到JavaScript。JavaScript自诞生以来,经历了工具类库、组件库、前端框架、前端应用的变迁。通过无数开发人员的努力,JavaScript不断被类聚和抽象,...

小草先森 ⋅ 昨天 ⋅ 0

Java桌球小游戏

其实算不上一个游戏,就是两张图片,不停的重画,改变ball图片的位置。一个左右直线碰撞的,一个有角度碰撞的。 左右直线碰撞 package com.bjsxt.test;import javax.swing.*;import j...

森林之下 ⋅ 昨天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部