nico对《Java 8函数式编程》的笔记(33)

  • 第57页
    在collect方法中使用joining方法可以将流中的值串成一个字符串。
    String friends = names.stream()
          .collect(Collectors.joining(", ", "[ ", " ]"));
    System.out.println("friends: " + friends);
    
    运行结果:
    friends: [ Jack, Linda, Mark, JJ, M ]
    2016-07-24 18:19:45 回应
  • 第58页
    1. 先对一系列字符串进行分组,分组的结果是key(字符串首字母): value(字符串)
    2. 对每个key对应的value计算个数,返回结果是key(字符串首字母): value(个数)
    一般完成上述操作需要分成两次,但是在collect方法中使用groupingBy的两个参数的方法就可以很直接的解决这个问题。
    @Test
    public void test5() {
    	List<String> names = Arrays.asList("Jack", "Linda", "Mark", "JJ", "M");
    	Map<String, Long> map = lengthOfName(names);
    	System.out.println("map: " + map);
    }
    
    // 先把首字符相同的字符串放进同个map,首字符作为key,
    // 然后计算每个key对应的有多少个String对象,并把计算出来的值作为value
    public Map<String, Long> lengthOfName(List<String> names) {
    	return names.stream()
            .collect(Collectors.groupingBy(s -> String.valueOf(s.charAt(0)),
    	Collectors.mapping(s -> s, Collectors.counting())));
    }
    
    运行结果:
    map: {J=2, L=1, M=2}
    其实,可以对counting方法进行各种替换,如替换成toList方法,那么返回值就变成了Map<String, List<String>>。
    2016-07-24 21:57:31 回应
  • 第61页
    将一串字符串串在一起,原本在collect方法中使用joining方法分分钟解决,但是为了练习,使用reduce方法解决。
    @Test
    public void testStringCombiner() {
    	List<String> names = Arrays.asList("Jack", "Linda", "Mark", "JJ", "M");
    	StringCombiner combined = names.stream()
    			.reduce(new StringCombiner(", ", "[ ", " ]"),
    					StringCombiner::add,
    					StringCombiner::merge);
    	String result = combined.toString();
    	System.out.println("result: " + result);
    }
    
    public class StringCombiner {
    
    	private final String delim;
    	private final String prefix;
    	private final String suffix;
    	private final StringBuilder builder;
    
    	public StringCombiner(String delim, String prefix, String suffix) {
    		this.delim = delim;
    		this.prefix = prefix;
    		this.suffix = suffix;
    		builder = new StringBuilder();
    	}
    
    	// BEGIN add
    	public StringCombiner add(String element) {
    		if (areAtStart()) {
    			builder.append(prefix);
    		} else {
    			builder.append(delim);
    		}
    		builder.append(element);
    		return this;
    	}
    	// END add
    
    	private boolean areAtStart() {
    		return builder.length() == 0;
    	}
    
    	// BEGIN merge
    	public StringCombiner merge(StringCombiner other) {
    		if (other.builder.length() > 0) {
    			if (areAtStart()) {
    				builder.append(prefix);
    			} else {
    				builder.append(delim);
    			}
    			builder.append(other.builder, prefix.length(), other.builder.length());
    		}
    		return this;
    	}
    	// END merge
    
    	@Override
    	public String toString() {
    		if (areAtStart()) {
    			builder.append(prefix);
    		}
    		builder.append(suffix);
    		return builder.toString();
    	}
    
    }
    
    三个参数的reduce方法,第二个和第三个参数都是接收两个参数的方法,但是StringCombiner对象的add方法和merge方法都是接受一个参数的对象,那另一个参数去哪了?调用方法的对象就是第一个参数。
    所以,StringCombiner::add的两个参数分别是this和name,StringCombiner::merge同样如此。
    定制收集器(难点、并未掌握)
    import java.util.Collections;
    import java.util.Set;
    import java.util.function.BiConsumer;
    import java.util.function.BinaryOperator;
    import java.util.function.Function;
    import java.util.function.Supplier;
    import java.util.stream.Collector;
    
    public class StringCollector implements Collector<String, StringCombiner, String> {
        private static final Set<Characteristics> characteristics = Collections.emptySet();
    
        private String delim;
        private String prefix;
        private String suffix;
    	
    	public StringCollector() {
    		super();
    	}
    
    	public StringCollector(String delim, String prefix, String suffix) {
    		super();
    		this.delim = delim;
    		this.prefix = prefix;
    		this.suffix = suffix;
    	}
    
    	@Override
    	public Supplier<StringCombiner> supplier() {
    		return () -> new StringCombiner(delim, prefix, suffix);
    	}
    
    	@Override
    	public BiConsumer<StringCombiner, String> accumulator() {
    		return StringCombiner::add;
    	}
    
    	@Override
    	public BinaryOperator<StringCombiner> combiner() {
    		return StringCombiner::merge;
    	}
    
    	@Override
    	public Function<StringCombiner, String> finisher() {
    		return StringCombiner::toString;
    	}
    
    	@Override
    	public Set<java.util.stream.Collector.Characteristics> characteristics() {
    		return characteristics;
    	}
    
    }
    
    首先要实现Collector接口,supplier方法用来创建对象,accumulator方法用来添加当前元素进对象里,combiner方法用来合并两个对象,finisher方法返回收集操作的最终结果。
    Java 8 有一个java.util.StringJoiner类,它的作用和StringCombiner一样,有类似的API。
    2016-07-25 08:59:42 回应
  • 第66页
    在collect方法中使用reducing方法,写法恶心,而且效率底下,so 暂时不研究。
    2016-07-25 21:14:33 回应
  • 第66页
    新增一些对Map对象操作的方法。
    @Test
    public void test5_4() {
    	Map<String, String> map = new HashMap<String, String>(){{
    		put("J", "Jack");
    		put("L", "Linda");
    		put("M", "Mark");
    	}};
    	
    	String str1 = map.computeIfAbsent("A", n -> n + "_Article");
    	System.out.println("str1: " + str1);
    		
    	String str2 = map.compute("J", (n, m) -> n.toString() + "_" + m.toString());
                                               // n和m分别是key和value
    	System.out.println("str2: " + str2);
    }
    
    运行结果:
    str1: A_Article
    str2: J_Jack
    对Map对象的迭代,用key和value同时迭代。
    Map<String, Integer> resultMap = new HashMap<>();
    map.forEach((key, value)->{
    	resultMap.put(key, value.length());
    });
    System.out.println("resultMap: " + resultMap);
    
    运行结果:
    resultMap: {A=9, J=6, L=5, M=4}
    2016-07-25 22:23:58 回应
  • 第67页
    并没有认真做。
    2016-07-25 22:32:12 回应
  • 第69页
    并发:在一个CPU上,一个时间段,执行多个任务,利用不断切换任务让人误以为同时执行。
    并行:在多个CPU上,每个CPU上分别执行自己的任务,这是真正意义上的同时。
    数据并行化是指将数据分成块,为每块数据分配单独的处理单元。
    任务并行化相当于多线程。
    2016-07-26 08:12:44 回应
  • 第73页
    例6-3 使用蒙特卡洛模拟法并行化模拟掷骰子事件
    @Test
    public void testParallelDiceRolls() {
    	double fraction = 1.0 / N;
    	long start1 = System.currentTimeMillis();
    	Map<Integer, Double> resultMap1 = IntStream
    			.range(0, N)
    			.parallel()
    			.mapToObj(twoDiceThrows())
    			.collect(Collectors
                            .groupingBy(side->side, 
                                  Collectors.summingDouble(n -> fraction)));
    	System.out.println("resultMap1: " + resultMap1);
    	long time1 = System.currentTimeMillis() - start1;
    	System.out.println("time1: " + (time1 * 1.0 / 1000) + " s");
    	
    	long start2 = System.currentTimeMillis();
    	Map<Integer, Double> resultMap2 = IntStream
    			.range(0, N)
    			.mapToObj(twoDiceThrows())
    			.collect(Collectors
                            .groupingBy(side->side, 
                                  Collectors.summingDouble(n -> fraction)));
    	System.out.println("resultMap2: " + resultMap2);
    	long time2 = System.currentTimeMillis() - start2;
    	System.out.println("time2: " + (time2 * 1.0 / 1000) + " s");
    }
    	
    private IntFunction<Integer> twoDiceThrows() {
    	return i -> {
    		ThreadLocalRandom random = ThreadLocalRandom.current();
    		int firstThrow = random.nextInt(1, 7);
    		int secondThrow = random.nextInt(1, 7);
    		return firstThrow + secondThrow;
    	};
    }
    
    运行结果:
    resultMap1: {2=0.02780246, 3=0.05556867, 4=0.08336466000000001, 5=0.11114840999999999, 6=0.13888492000000002, 7=0.16660306000000003, 8=0.13885089999999997, 9=0.11113914999999999, 10=0.08334594999999999, 11=0.055508339999999996, 12=0.02778348}
    time1: 2.098 s
    resultMap2: {2=0.02775076, 3=0.05560599, 4=0.08328941000000001, 5=0.11112538, 6=0.13884923, 7=0.16664901000000001, 8=0.13892618, 9=0.11111408, 10=0.08337263, 11=0.05553474, 12=0.02778259}
    time2: 6.11 s
    当数据量足够大时,并行化计算可以提升相当多的速度,反之,数据量不足时,并行化计算不仅没有优势,速度还会比较慢。
    2016-07-26 20:32:24 回应
  • 第75页
    要使用并行化运算必须遵守一些规则和限制。
    reduce三个参数的方法中,如果是串行化运算就不会出现问题,但是并行化运算会出现初始值和每个元素进行运算。
    @Test
    public void testLimit() {
    	List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
    	int result1 = numbers.stream()
                  .reduce(2, (acc, x) -> acc + x, (a, b) -> a + b);
    	System.out.println("result1: " + result1);
    		
    	int result2 = numbers.stream().parallel()
                  .reduce(2, (acc, x) -> acc + x, (a, b) -> a + b);
    	System.out.println("result2: " + result2);
    	}
    
    运算结果:
    result1: 47
    result2: 63
    并行运算每个元素都与初始值计算了一次,所以,要进行并行运算,初始值必须为0。
    parallel方法是将流转换成并行流,
    sequential方法是将流转化成串行流。
    如果同时调用了parallel和sequential方法,最后调用的那个方法起效。
    2016-07-26 21:00:53 回应
  • 第75页
    影响并行流性能的主要因素有5个。
    1. 数据块大小
    输入数据的大小会影响并行化处理对性能的提升。将问题分解之后并行化处理,再将结果合并会带来额外的开销。因此只有数据足够大、每个数据处理管道花费的时间足够多时,并行化处理才有意义。
    2. 源数据结构
    每个管道的操作都基于一些初始数据源,通常是集合。将不同的数据源分割相对容易,这里的开销影响了在管道中并行处理数据时到底能带来多少性能上的提升。
    3. 装箱
    处理基本类型比处理装箱类型要快。
    4.核的数量
    极端情况下,只有一个核,因此完全没必要并行化。显然,拥有的核越多,获得潜在性能提升的幅度就越大。在实践中,核的数量不单指你的机器上有多少核,更是指运行时你的机器能使用多少核。这也就是说同时运行的其他进程,或者线程关联性(强制线程在某些核或CPU上运行)会影响性能。
    5. 单元处理开销
    比如数据大小,这是一场并行执行花费时间和分解合并操作开销之间的战争。花在流中每个元素身上的时间越长,并行操作带来的性能提升越明显。
    根据性能好坏,将核心类库提供的通用数据结构分成一下3组:
    1. 性能好
    ArrayList、数组或IntStream.range,这些数据结构支持随机读取,也就是说它们能轻而易举被任意分解。
    2. 性能一般
    HashSet、TreeSet,这些数据结构不易公平地被分解,但是大多数时候分解是可能的。
    3. 性能差
    有些数据结构难于分解,比如,可能要花O(N)的时间复杂度来分解问题。其中包括LinkedList,对半分解太难了。还有Stream.iterate和BufferedReader.lines,它们长度未知,因此很难预测该在哪里分解。
    在流中单独操作每一块的种类时,可以分成两种不同的操作:无状态的和有状态的。无状态操作整个过程中不必维护状态,有状态操作则有维护状态所需的开销和限制。
    无状态操作包括map、filter和flatMap,有状态操作包括sorted、distinct和limit。
    2016-07-26 22:26:36 回应

nico的其他笔记  · · · · · ·  ( 全部41条 )