实现自定义流生成器

Java 9并发编程指南 目录

流是数据序列,通过顺序或并行的方式对其进行一系列操作(通常用lambda表达式表示),用来筛选、转换、排序、减少或构造新的数据结构。流在Java 8中引入,是此版本中最重要的特性之一。

流基于Stream接口和java.util.stream包中的相关类和接口。它们还在许多类中引入新方法,通过不同的数据结构生成流。可以从实现Collection接口的每个数据结构创建Stream接口:File、Director、Array和其它许多源。

Java还包括从自定义源中创建流的不同机制,最重要的如下所示:

  • Supplier接口:此接口定义get()方法,当需要处理其它对象时,通过Stream调用。可以使用Stream类的generate()静态方法从Supplier接口创建Stream。考虑到这个源可能是无限的,所以必须使用limit()或类似的方法来限制Stream中的元素数量。
  • Stream.Builder接口:此接口提供accept()和add()方法来添加元素到Stream和build()方法,返回之前增加元素创建的Stream接口
  • Spliterator接口:此接口定义了遍历和拆分源元素所需的方法,可以使用StreamSupport类的stream()方法生成Stream接口来处理Spliterator元素。

本节将学习如何实现自定义Spliterator接口,以及如何创建Stream接口来处理数据。我们将使用元素矩阵,正常的Steam接口应该一次处理一个元素,但是我们将使用Spliterator类一次处理一行。

准备工作

本范例通过Eclipse开发工具实现。如果使用诸如NetBeans的开发工具,打开并创建一个新的Java项目。

实现过程

通过如下步骤实现范例:

  1. 创建名为Item的类,继承矩阵每个元素的信息。包括三个私有属性:名为name的String属性和名为row、column的两个整型属性。创建获取和设置这些属性值的方法,此类代码很简单,不在这里列出。

  2. 创建名为MySpliterator的类,指定其实现Item类参数化的Spliterator接口。此类包括四个属性:名为items的Item对象矩阵和三个名为start、end和current的整型属性,分别存储待通过这个Spliterator接口处理的第一个和最后一个元素,以及正在被处理的当前元素。实现类构造函数,初始化这些属性:

    public class MySpliterator  implements Spliterator<Item> {
    	private Item[][] items;
    	private int start, end, current;
    	public MySpliterator(Item[][] items, int start, int end) {
    		this.items=items;
    		this.start=start;
    		this.end=end;
    		this.current=start;
    	}
    
  3. 实现characteristics(),此方法返回描述Spliterator行为的int值。这个值的含义将在后面的“工作原理”中讲解:

    	@Override
    	public int characteristics() {
    		return ORDERED | SIZED | SUBSIZED;
    	}
    
  4. 实现estimatedSize()。此方法将返回通过这个Spliterator接口处理的元素数量,通过计算end和current属性值的差实现:

    	@Override
    	public long estimateSize() {
    		return end - current;
    	}
    
  5. 现在实现tryAdvance()方法,调用此方法试图处理Spliterator()的一个元素。tryAdvance()方法的输入参数是实现Consumer接口的对象。这个接口将通过Stream API调用,所以只需要考虑其实现即可。如本节引言所述,我们有一个Item对象矩阵,且每次处理一行,接收到的Consumer函数将处理Item对象。因此,如果Spliterator接口始终有元素处理,我们将使用Consumer函数的accept()方法处理当前行的所有item元素:

    	@Override
    	public boolean tryAdvance(Consumer<? super Item> consumer) {
    		System.out.printf("MySpliterator.tryAdvance.start: %d, %d, %d\n", start,end,current);
    		if (current < end) {
    			for (int i=0; i<items[current].length; i++) {
    				consumer.accept(items[current][i]);
    			}
    			current++;
    			System.out.printf("MySpliterator.tryAdvance.end:true\n");
    			return true;
    		}
    		System.out.printf("MySpliterator.tryAdvance.end:false\n");
    		return false;
    	}
    
  6. 接下来实现forEachRemaining(),此方法将接收Consumer接口的实现,并将此函数应用于Spliterator的剩余元素。本范例中,将对所有剩余的元素调用tryAdvance()方法:

    	@Override
    	public void forEachRemaining(Consumer<? super Item> consumer) {
    		System.out.printf("MySpliterator.forEachRemaining.start\n");
    		boolean ret;
    		do {
    			ret=tryAdvance(consumer);
    		} while (ret);
    		System.out.printf("MySpliterator.forEachRemaining.end\n");
    	}
    
  7. 最后实现trySplit()方法,并行流将调用此方法把Spliterator拆分为两个子集。它将返回新的Spliterator对象,包含其它线程待处理的元素,当前线程将此处理剩余的元素。如果spliterator对象无法拆分,需要返回null值。本范例中,将计算需要处理的在中间的元素。前半部分由当前线程处理,后半部分通过其它线程处理:

    	@Override
    	public Spliterator<Item> trySplit() {
    		System.out.printf("MySpliterator.trySplit.start\n");
    		if (end-start<=2) {
    			System.out.printf("MySpliterator.trySplit.end\n");
    			return null;
    		}
    		int mid=start+((end-start)/2);
    		int newStart=mid;
    		int newEnd=end;
    		end=mid;
    		System.out.printf("MySpliterator.trySplit.end: %d, %d, %d, %d, %d, %d\n",start, mid, end, newStart, newEnd, current);
    		return new MySpliterator(items, newStart, newEnd);
    	}
    }
    
  8. 现在添加main()方法,实现本范例主类。首先,声明和初始化10行乘10列的Item对象:

    public class Main {
    	public static void main(String[] args) {
    		Item[][] items;
    		items= new Item[10][10];
    		for (int i=0; i<10; i++) {
    			for (int j=0; j<10; j++) {
    				items[i][j]=new Item();
    				items[i][j].setRow(i);
    				items[i][j].setColumn(j);
    				items[i][j].setName("Item "+i+" "+j);
    			}
    		}
    
  9. 然后,创建MySpliterator对象处理矩阵所有的元素:

    		MySpliterator mySpliterator=new MySpliterator(items, 0, items.length);
    
  10. 最后,使用StreamSupport类的stream()方法从Spliterator中创建流。将true值作为第二个参数传递,指明流是并行操作的。然后,使用Stream类的forEach()方法输出每个元素的信息到控制台:

    		StreamSupport.stream(mySpliterator, true).forEach( item -> {
    			System.out.printf("%s: %s\n",Thread.currentThread()
    			.getName(),item.getName());
    		});
    	}
    }
    

工作原理

范例的主元素是Spliterator,这个接口定义了用来处理和划分要使用的元素源的方法,例如Stream对象的源。很少需要直接使用Spliterator对象,只有当想要不同的行为时—也就是说,如果想要实现自定义的数据结构并从中创建Stream——使用Spliterator对象。

Spliterator有一组定义其行为的特征,如下所示:

  • CONCURRENT:能够同时安全地修改的数据源
  • DISTINCT:数据源的所有元素都是不同的
  • IMMUTABLE:数据源中能够添加、删除或者替换元素
  • NONNULL:数据源中没有null元素
  • ORDERED:在数据源元素中有相遇排序
  • SIZED:通过estimateSize()方法返回的值是Spliterator的确切大小。
  • SORTED:对Spliterator的元素进行排序
  • SUBSIZED:调用trySplit()方法之后,能够得到Spliterator两个部分的确切大小

本范例中,我们定义了Spliterator的DISTINCT、IMMUTABLE、NONNULL、ORDERED、SIZED和SUBSIZED特性。

然后,我们实现了Spliterator接口定义的所有没有默认实现的方法:

  • characteristics():此方法返回Spliterator对象的特征。具体来说,它返回一个整数值,可以使用Spliterator对象的各个特征之间的位运算符或运算符(|)进行计算。需要注意返回值应该与Spliterator对象的实际特征一致。
  • estimatedSize():如果在当前时刻调用forEachRemaining()方法,此方法将返回元素的数量。本范例中,我们返回已知的确切值,但是方法定义涉及到估计大小。
  • tryAdvance():此方法将指定为参数的函数应用到待处理的下一个元素,如果有的话,并返回true。如果没有元素待处理,则返回false。本范例中,此方法接收处理一个Item对象的Consumer,但同一时间需要处理一行Item对象,所以转换行的所有元素,并且调用Consumer的accept()方法。
  • trySplit():此方法用来把当前Spliterator分成两个不同部分,以便每一部分能够被不同的线程处理。理想情况下,应该将数据源分成两部分,每部分包含相同数量的元素。但本范例中,我们计算开始和结束位置中间的元素,生成了两个元素块。开始到中间部分的元素由当前的Spliterator处理,中间到结束部分的元素由新的Spliterator对象处理。如果无法拆分数据源,此方法返回空值。本范例中,Spliterator只有两个元素,所以不会拆分。

Spliterator接口的其它方法都有一个默认实现,但我们重写了forEachRemaining()方法。此方法将接收到的函数作为参数(Consumer接口实现),应用到尚未处理的Spliterator元素。我们实现了自定义的信息输出到控制台,并且使用tryAdvance()方法处理每个独立项目。

下图显示本范例在控制台输出的部分执行信息:

pics/08_07.jpg

首先,调用trySplit()方法拆分数据源,然后调用forEachRemaining()方法来处理trySplit()方法生成的每个Spliterator的所有元素。

扩展学习

我们可以从不同的数据源获得Spliterator接口的实现。BaseStream类提供了spliterator()方法,此方法从流的元素返回spliterator。其它数据源,如ConcurrentLinkedDeque、ConcurrentLinkedQueue或者Collection,也提供了spliterator()方法获得接口实现,用来处理这些数据结构的元素。

更多关注

  • 第六章“并行和响应式流”中的“创建不同来源的流”小节
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
-----------------更新说明----------------- 1.1.2: 支持所有的按键,特殊键也可以直接设置,不用复制到连发键设置框。 取消F12这个热键开关,改为左右WIN键,而且屏蔽了这2个键,按了不会弹出的,变成开关,原来的Scroll Lock不变。 1.1.1: 解决个别杀毒软件误报的现象,原因是由于UPX压缩引起的。 UPX压缩的好处是生成的连发工具体积非常小,只有200KB+,不压缩有400KB+。 生成时加入对话框由用户自行决定是否用UPX压缩(以往默认使用UPX压缩,所以误报很正常)。 右键菜单加入热键。 1.1.0: 解决在关闭连发的情况下所有按键都不正常的BUG,优化了脚本,效率提高,(请务必更新)。 该BUG的表现:按住还能连发(速度慢),有时候随便按个键还按不出(按键不正常)。 修改版本号的形式为X.X.X,菜单加入英文单词提示。 -----------------使用说明----------------- 连发不是连招。。。连发的意思是按下一个键不放就自动连按该键,比如按下X键不放就实现自动攻击,比手动按要快得多,节省力气,还能减少键盘寿命o(∩_∩)o...,至于效果怎么样自己测试吧。 设置自己的连发键: 下载好之后运行 SET_AHK.exe 然后设置你要连发的按键,比如X键,然后点增加,然后点生成连发,就会在目录下面生成一个新文件: DNF_AHK.exe DNF_AHK.exe 可以单独运行的,你可以复制该文件到其他地方保存起来,SET_AHK.exe文件不需要了,当然了,要修改按键必须要用它。 如何使用连发工具: 运行上面生成好的 DNF_AHK.exe 就可以了,工具首次运行自动判断 Scroll Lock 键,会直接开启连发。注意:指示灯亮就表示开启,如果要打字就再按次Scroll Lock键,指示灯关闭就表示关闭连发功能,打字完再按次Scroll Lock键保持连发的开启状态,1.1.2以后版本增加连发热键开关:左右WIN键。 -----------------调整延时方法(非必须)----------------- 第一次运行建议先设置你的键盘延时,这样效果会更加好,当然你也可以不设置,一样可以用本连发工具,而且速度也很快: 直接运行 SetInterval.bat 文件(该文件只有在运行SET_AHK.exe的状态下才生成),点确定导入注册表即可。 这一步其实可以这样操作:控制面板---键盘---重复延时--短 托到最右边的短那里,然后重启电脑就可以了,本工具自带的注册表原理一样的,只是用 SetInterval.bat 弄好之后就可以不重启电脑直接生效,方便网吧用户。 该连发工具基于AutoHotkey核心制作而成。 Vista和WIN7系统请以系统管理员身份运行连发工具,否则可能无法连发,也请大家测试下这些系统是否能用。 56q.5d6d.com 离不开电脑制作

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值