zoukankan      html  css  js  c++  java
  • Stream介绍及简单操作!

    一、对Stream的一些了解

    ​ Java API中有大量的方法产生stream;对于stream流的理解,个人认为如果了解inputStream和outputStream的人会更轻松一点。它的本质就是创建一个视图,一个用于计算的视图。

    Java中可以将集合看作是数组的视图,而stream流可以看作是集合的视图。所以它通常处理数组和集合!

    1.1 api介绍:

    A stream should be operated on (invoking an intermediate or terminal stream operation) only once. This rules out, for example, "forked" streams, where the same source feeds two or more pipelines, or multiple traversals of the same stream. A stream implementation may throw IllegalStateException if it detects that the stream is being reused. However, since some stream operations may return their receiver rather than a new stream object, it may not be possible to detect reuse in all cases.

    流应该只操作一次(调用中间或终端流操作)。例如,这排除了“分叉”流,即同一个源提供两个或多个管道,或同一个流的多次遍历。如果流实现检测到正在重用流,则可能抛出IllegalStateException。然而,由于一些流操作可能返回它们的接收器,而不是一个新的流对象,所以不可能在所有情况下都检测到重用。

    Streams have a BaseStream.close() method and implement AutoCloseable, but nearly all stream instances do not actually need to be closed after use. Generally, only streams whose source is an IO channel (such as those returned by Files.lines(Path, Charset)) will require closing. Most streams are backed by collections, arrays, or generating functions, which require no special resource management. (If a stream does require closing, it can be declared as a resource in a try-with-resources statement.)

    Stream pipelines may execute either sequentially or in parallel. This execution mode is a property of the stream. Streams are created with an initial choice of sequential or parallel execution. (For example, Collection.stream() creates a sequential stream, and Collection.parallelStream() creates a parallel one.) This choice of execution mode may be modified by the BaseStream.sequential() or BaseStream.parallel() methods, and may be queried with the BaseStream.isParallel() method.

    Streams有一个BaseStream.close()方法,并实现了AutoCloseable,但是几乎所有的stream实例实际上都不需要在使用后关闭。通常,只有源为IO通道的流(例如由文件返回的流)。行(路径,字符集))需要关闭。大多数流由集合、数组或生成函数支持,它们不需要特殊的资源管理。(如果流确实需要关闭,可以在try-with-resources语句中将其声明为资源)。

    流管道可以顺序执行,也可以并行执行。这种执行模式是流的属性。流是通过顺序执行或并行执行的初始选择创建的。(例如,Collection.stream()创建一个顺序流,而Collection.parallelStream()创建一个并行流。)这种执行模式的选择可以由BaseStream.sequential()或BaseStream.parallel()方法修改,也可以使用BaseStream.isParallel()方法查询。

    二、流操作

    2.1 创建流(生成流)

    ​ 我们分析过,流是集合或者数组的视图,所以流的来源,大多数是集合、数组或者生成函数。

    2.1.1 数组生成流

    		String context = new String("diag,,dgers,dage,gaergc,gol,d,g,g,l");
    		
    		// 1.	数组直接生成流
    		String[] str = context.split("\,");
    		Stream<String> words = Stream.of(str);
    
    		// 2. 直接数组格式生成流
    		Stream<String> words1 = Stream.of("a","b","cad","ds2");
    
    		// 3. 正则通常可以用来产生数组,所以正则中也有流的生成方法;
    		Stream<String> words2 = Pattern.compile("\,+").splitAsStream(context);
    

    2.1.2 集合生成流

    		String context = new String("diag,,dgers,dage,gaergc,gol,d,g,g,l");
    		// 1. 在集合中有直接得到stream的方法。
    		List<String> list = Arrays.asList(context);
    		list.stream();
    
    		// 2. 集合创建并行流
    		Collection.parallelStream();
    

    2.1.3 函数生成流

    	// 1. empty()函数生成空流
    	Stream<String> s = Stream.empty();
    
    	// 2. generate(Supplier<T> s)生成无限流
    	Stream<String> t = Stream.generate(() -> "hello world");
    
    	Supplier supplier = new Supplier() {
    			@Override
    			public Object get() {
    				return "hello world";
    			}
    		};
    	Stream<String> r = Stream.generate(supplier);
    
    	// 3.iterata(final T seed, final UnaryOperator<T> f)
    	Stream<Integer> e = Stream.iterate(0, (x) -> x + 2);
    

    关于generate(Supplier s)中的Supplier参数

    ​ 如果我说这是个"产生流的函数",可能很多人就能轻易了解,也有像我一样的人表示难以理解。在例子中我们可以看到传入的是一个Supplier的实现,而我们需要重写的方法只是"get()",就像用lambda重写runnable的run方法一样;

    2.1.4 其他生成流的API

    		Files.lines(Path path);
    

    2.2 过滤流/转换流

    ​ 故名思义,转换流是通过中间操作得到一个处理后的流,可以参考定义OutputStreamWriter或者InputStreamReader,我们会得到另外的流。不过此处实现方式略有不同,IO的转换流同装饰者模式,而Stream的过滤更像是对流里的数据进行一次过滤或者转换操作,返回满足条件的流。

    流不可重用,以下实例只做展示;

    filter

    		// 1.原始流,他是一个Integer数组的视图;
    		Stream<Integer> ints = Stream.of(1,2,3,5,6,7,9);
    		// 2.过滤后的流:取出其中 >5 的数组成的流;
    		Stream<Integer> result = ints.filter(n -> n > 5);
    

    关于filter(Predicate<? super T> predicate)中的Predicate参数

    ​ 这个接口同Supplier,我们传入需要重写的方法"boolean test(T t)",明显返回的是一个boolean!所以在例子中我用了 n>5 来作为过滤条件!

    map

    		String context = new String("diaG,,dgeSrsS,dagGe,gaergc,gol,D,g,g,L");
    		// 1. 原始流,String数组的流
    		Stream<String> s = Stream.of(context);
    		// 2. 转换后的流;将所有的str转换为小写
    		Stream<String> result = s.map(String::toLowerCase);
    

    关于map(Function<? super T, ? extends R> mapper)中的Function参数

    ​ 这个接口类同上述Supplier和Predicate,我们传入需要重写的方法"R apply(T t)",明显返回的是传入的范型"R"

    关于范型的一般参数类型:(但其实是可以随意自己定义的)

    E - Element (在集合中使用,因为集合中存放的是元素)
    T - Type(Java 类)
    K - Key(键)
    V - Value(值)
    N - Number(数值类型)
    ? - 表示不确定的java类型
    S、U、V - 2nd、3rd、4th types

    此处的R,就被定义为返回类型,是Result的意思

    flatMap

    		/**
    		 * 传入一个字符串:china
    		 * 得到流:"c","h","i","n","a"
    		 * @param s
    		 * @return
    		 */
    		public static Stream<String> letters(String s){
    			List<String> result = new ArrayList<>();
    			for (int i = 0; i < s.length(); i++) {
    				result.add(s.substring(i,i+1));
    			}
    			return result.stream();
    		}	
    		
    		
    		String context = new String("diag,dgers,dage,gaergc,gol,d,g,g,l");
    
    		// 1.我们得到一个数组组成的流:["diag","dgers","dage","gaergc","gol","d","g","g","l"]
    		Stream<String> s = Pattern.compile("\,").splitAsStream(context);
    
    		// 2.result:[["d","i","a","g"]["d","g","e","r","s"]...["g"]["l"]]
    		Stream<Stream<String>> result = s.map(w -> letters(w));
    		
    		// 3.flatResult:["d","i","a","g","d","g","e","r","s"..."g","l"]
    		Stream flatResult = s.flatMap(w -> letters(w));
    

    ​ 可以看到,flatMap的作用是:将多个流进行扁平化的处理,也就是合并或者说连接到一起组成新的流。

    关于map跟flatMap的了解,这张图画得不错。

    https://blog.csdn.net/mingwar/article/details/79643126

    limit

    // limit(long maxSize):限制大小
    Stream<Double> randoms = Stream.generate(Math::random).limit(100);
    

    skip

    		String context = new String("diag,,dgers,dage,gaergc,gol,d,g,g,l");
    		// skip(n),过滤掉第n个元素
    		Stream<String> words = Stream.of(context.split("\,")).skip(1);
    

    concat

    // concat(a,b):a后面接着b组成的流。(将a和b合并起来当流?)
    Stream<String> com = Stream.concat(letters("hello"),letters("world"));
    

    distinct

    		// 去重
    		Stream<String> words = Stream.of(context.split("\,")).distinct();
    

    sorted

    String context = new String("diag,,dgers,dage,gaergc,gol,d,g,g,l");
    List<String> list = Arrays.asList(context);
    
    // 1.默认排序
    list.stream().sorted();
    // 2.传入排序规则
    list.stream().sorted(Comparator.comparing(String::length).reversed());
    

    2.3 得到结果

    使用流处理数据都是为了更好更方便的得到数据,最后总是要得到结果。

    方式一:通过Optional做中间处理

    ​ Stream很多操作都返回一个Optional对象,这个对象的作用呢,不要想得太麻烦,他的最大的目的就是防止出现null;这是一个很简单的包装器,要么包装了T对象,要么没有包装对象。它里面的实际内容只有2种:

    • 有,即是需要的结果: T
    • 没有,啥都没有,就是为了防止null出现而使用它

    那么如何通过Optpional做中间处理呢,依然是2步

    • 得到Optional值
    • 得到最终目的值

    Optional可以直接从Stream的函数得到,然后通过Optional的方法得到值

    例子:得到最大值:max(Comparator<? super T> comparator);

    			
    		Integer[] context = new Integer[]{1,2,5,3,8,43,25,21,33};
    		Stream<Integer> stream = Arrays.asList(context).stream();	
    		// 第一步:得到Optional值
    		Optional<Integer> maxOptional = stream.max(Comparator.naturalOrder());
    		// 第二部:得到最终目的值
    		Integer max = maxOptional.orElse(0);
    

    ​ 我们可以看到从Optional类型到Integer类型,使用的是函数"orElse( default value )";前文已经讲过Optional的最大作用就是防止出现null;所以在函数"orElse( default value )"中传入默认值"default value",意识是如果无法得到想要的类型值,则使用默认值default value

    关于更多的Optional的使用方法,可以看Optional的API或者源码;

    只要我们理解:Optional只是为了防止出现null而使用的中间值,它很简单,不使用它我们依然能够得到我们想要的,但是会有"npe"的风险。

    方式二:直接得到结果

    ​ Stream可以直接返回我们需要的结果T,它可能是基本类型,也可能是引用类型。列出一些直接得到结果的函数:

    		Integer[] context = new Integer[]{1,2,5,3,8,43,25,21,33};
    		Stream<Integer> stream = Arrays.asList(context).stream();
    		
    		// 1. 遍历流
    		stream.forEach(w -> System.out.println(w));
    		stream.forEachOrdered(w -> System.out.println(w)); // 按照顺序遍历,在并行流中保证顺序
    
    		// 2. 将流构造成数组
    		Object[] objects = stream.toArray();// Object
    		String[] strings = stream.toArray(String[]::new);// 新建String数组
    
    		// 3. 构造散列表  ...未完,具体看api,太多了
    		List<Integer> collect = stream.collect(Collectors.toList());
    		// 指定类型的散列表
    		stream.collect(Collectors.toCollection(ArrayList::new));
    		stream.collect(Collectors.toCollection(LinkedList::new));
    
    		// 4. 构造集合 ...未完,具体看api,太多了
    		Set<Integer> set = stream.collect(Collectors.toSet());
    		// 指定类型的集合
    		stream.collect(Collectors.toCollection(TreeSet::new));
    
    		// 5. 得到迭代器
    		Iterator<Integer> iterator = stream.iterator();
    
    
    class person{
    		private String name;
    		private String id;
    
    		person(String id,String name){
    			this.id = id;
    			this.name = name;
    		}
      
    		getter and setter....
    	}
    
    		// 6. 得到Hash表
    		person p1 = new person("1","张三");
    		person p2 = new person("2","李四");
    		person p3 = new person("3","王五");
    		person p4 = new person("4","赵六");
    
    		person[] ps = new person[]{p1,p2,p3,p4};
    		Stream<person> stream = Arrays.stream(ps);
    
    		Map<String, String> collect = stream.collect(Collectors.toMap(person::getId, person::getName));
    		collect.forEach((k,v) -> System.out.println(k + ":" + v));
    

    三、并行流

    ​ 它首先是流,所以具有流的特征和用法,前文已述;但是它是并行的,意味着它没有顺序,可以并行操作。不过我们可以通过"stream.forEachOrdered() 保证顺序。

    • Collection.parallelStream() 可以从任何集合中的获取并行流
    		Integer[] context = new Integer[]{1,2,5,3,8,43,25,21,33};
    		// 从集合中获取并行流	
    		Stream<Integer> stream = Arrays.asList(context).parallelStream();
    
    • parallel() 可以将任意顺序的流转为并行流
    		Integer[] context = new Integer[]{1,2,5,3,8,43,25,21,33};
    		// 将普通流转为并行流
    		Stream<Integer> parallel = Arrays.stream(context).parallel();
    
    • 流在终结操作之前,所有的操作都是并行的
    • 流以及传递给流的函数不应该被阻塞
  • 相关阅读:
    开始我的博客
    POJ 1284:Primitive Roots(素数原根的个数)
    数据结构作业——图的存储及遍历(邻接矩阵、邻接表+DFS递归、非递归+BFS)
    NYOJ 85:有趣的数(打表,规律)
    NYOJ 12:喷水装置(二)(贪心,区间覆盖问题)
    HDU 2058:The sum problem(数学)
    HDU 1716:排列2(全排列)
    HDU 2048:神、上帝以及老天爷(错排公式,递推)
    NYOJ 6:喷水装置(一)(贪心)
    BZOJ 2002:Bounce 弹飞绵羊(分块)
  • 原文地址:https://www.cnblogs.com/dhcao/p/10878456.html
Copyright © 2011-2022 走看看