首页 文章 精选 留言 我的

精选列表

搜索[学习],共10000篇文章
优秀的个人博客,低调大师

[转载] 是时候学习真正的 spark 技术了

本文转自:https://mp.weixin.qq.com/s/awT4aawtTIkNKGI_2zn5NA 本站转载已经过作者授权。任何形式的转载都请联系原作者(孙彪彪/marketing@qiniu.com)获得授权并注明出处。 spark sql 可以说是 spark 中的精华部分了,我感觉整体复杂度是 spark streaming 的 5 倍以上,现在 spark 官方主推 structed streaming, spark streaming 维护的也不积极了, 我们基于 spark 来构建大数据计算任务,重心也要向 DataSet 转移,原来基于 RDD 写的代码迁移过来,好处是非常大的,尤其是在性能方面,有质的提升, spark sql 中的各种内嵌的性能优化是比人裸写 RDD 遵守各种所谓的最佳实践更靠谱的,尤其

优秀的个人博客,低调大师

Java虚拟机学习 - 垃圾收集器

HotSpot JVM收集器 上面有7中收集器,分为两块,上面为新生代收集器,下面是老年代收集器。如果两个收集器之间存在连线,就说明它们可以搭配使用。 Serial(串行GC)收集器 Serial收集器是一个新生代收集器,单线程执行,使用复制算法。它在进行垃圾收集时,必须暂停其他所有的工作线程(用户线程)。是Jvm client模式下默认的新生代收集器。对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集效率。 ParNew(并行GC)收集器 ParNew收集器其实就是serial收集器的多线程版本,除了使用多条线程进行垃圾收集之外,其余行为与Serial收集器一样。 Parallel Scavenge(并行回收GC)收集器 Parallel Scavenge收集器也是一个新生代收集器,它也是使用复制算法的收集器,又是并行多线程收集器。parallel Scavenge收集器的特点是它的关注点与其他收集器不同,CMS等收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间,而parallel Scavenge收集器的目标则是达到一个可控制的吞吐量。吞吐量= 程序运行时间/(程序运行时间 + 垃圾收集时间),虚拟机总共运行了100分钟。其中垃圾收集花掉1分钟,那吞吐量就是99%。 Serial Old(串行GC)收集器 Serial Old是Serial收集器的老年代版本,它同样使用一个单线程执行收集,使用“标记-整理”算法。主要使用在Client模式下的虚拟机。 Parallel Old(并行GC)收集器 Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。 CMS(并发GC)收集器 CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。 CMS收集器是基于“标记-清除”算法实现的,整个收集过程大致分为4个步骤: ①.初始标记(CMS initial mark) ②.并发标记(CMS concurrenr mark) ③.重新标记(CMS remark) ④.并发清除(CMS concurrent sweep) 其中初始标记、重新标记这两个步骤任然需要停顿其他用户线程。初始标记仅仅只是标记出GC ROOTS能直接关联到的对象,速度很快,并发标记阶段是进行GC ROOTS 根搜索算法阶段,会判定对象是否存活。而重新标记阶段则是为了修正并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间会被初始标记阶段稍长,但比并发标记阶段要短。 由于整个过程中耗时最长的并发标记和并发清除过程中,收集器线程都可以与用户线程一起工作,所以整体来说,CMS收集器的内存回收过程是与用户线程一起并发执行的。 CMS收集器的优点:并发收集、低停顿,但是CMS还远远达不到完美,主要有三个显著缺点: 1,CMS收集器对CPU资源非常敏感。在并发阶段,虽然不会导致用户线程停顿,但是会占用CPU资源而导致引用程序变慢,总吞吐量下降。CMS默认启动的回收线程数是:(CPU数量+3) / 4。 2,CMS收集器无法处理浮动垃圾,可能出现“Concurrent Mode Failure“,失败后而导致另一次Full GC的产生。由于CMS并发清理阶段用户线程还在运行,伴随程序的运行自热会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法在本次收集中处理它们,只好留待下一次GC时将其清理掉。这一部分垃圾称为“浮动垃圾”。也是由于在垃圾收集阶段用户线程还需要运行,,即需要预留足够的内存空间给用户线程使用,因此CMS收集器不能像其他收集器那样等到老年代几乎完全被填满了再进行收集,需要预留一部分内存空间提供并发收集时的程序运作使用。 在默认设置下,CMS收集器在老年代使用了68%的空间时就会被激活,也可以通过参数-XX:CMSInitiatingOccupancyFraction的值来提供触发百分比,以降低内存回收次数提高性能。要是CMS运行期间预留的内存无法满足程序其他线程需要,就会出现“Concurrent Mode Failure”失败,这时候虚拟机将启动后备预案:临时启用Serial Old收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了。 所以说参数-XX:CMSInitiatingOccupancyFraction设置的过高将会很容易导致“Concurrent Mode Failure”失败,性能反而降低。 3,最后一个缺点,CMS是基于“标记-清除”算法实现的收集器,使用“标记-清除”算法收集后,会产生大量碎片。空间碎片太多时,将会给对象分配带来很多麻烦,比如说大对象,内存空间找不到连续的空间来分配不得不提前触发一次Full GC。为了解决这个问题,CMS收集器提供了一个-XX:UseCMSCompactAtFullCollection开关参数,用于在Full GC之后增加一个碎片整理过程,还可通过-XX:CMSFullGCBeforeCompaction参数设置执行多少次不压缩的Full GC之后,跟着来一次碎片整理过程。 G1收集器 G1(Garbage First)收集器是JDK1.7提供的一个新收集器,G1收集器基于“标记-整理”算法实现,也就是说不会产生内存碎片。还有一个特点之前的收集器进行收集的范围都是整个新生代或老年代,而G1将整个Java堆(包括新生代,老年代)。 垃圾收集器参数总结: -XX:+<option>启用选项 -XX:-<option>不启用选项 -XX:<option>=<number> -XX:<option>=<string> 参数描述 -XX:+UseSerialGCJvm运行在Client模式下的默认值,打开此开关后,使用Serial+SerialOld的收集器组合进行内存回收 -XX:+UseParNewGC打开此开关后,使用ParNew+SerialOld的收集器进行垃圾回收 -XX:+UseConcMarkSweepGC使用ParNew+CMS+SerialOld的收集器组合进行内存回收,SerialOld作为CMS出现“ConcurrentModeFailure”失败后的后备收集器使用。 -XX:+UseParallelGCJvm运行在Server模式下的默认值,打开此开关后,使用ParallelScavenge+SerialOld的收集器组合进行回收 -XX:+UseParallelOldGC使用ParallelScavenge+ParallelOld的收集器组合进行回收 -XX:SurvivorRatio新生代中Eden区域与Survivor区域的容量比值,默认为8,代表Eden:Subrvivor=8:1 -XX:PretenureSizeThreshold直接晋升到老年代对象的大小,设置这个参数后,大于这个参数的对象将直接在老年代分配 -XX:MaxTenuringThreshold晋升到老年代的对象年龄,每次MinorGC之后,年龄就加1,当超过这个参数的值时进入老年代 -XX:UseAdaptiveSizePolicy动态调整java堆中各个区域的大小以及进入老年代的年龄 -XX:+HandlePromotionFailure是否允许新生代收集担保,进行一次minorgc后,另一块Survivor空间不足时,将直接会在老年代中保留 -XX:ParallelGCThreads设置并行GC进行内存回收的线程数 -XX:GCTimeRatioGC时间占总时间的比列,默认值为99,即允许1%的GC时间,仅在使用ParallelScavenge收集器时有效 -XX:MaxGCPauseMillis设置GC的最大停顿时间,在ParallelScavenge收集器下有效 -XX:CMSInitiatingOccupancyFraction设置CMS收集器在老年代空间被使用多少后出发垃圾收集,默认值为68%,仅在CMS收集器时有效,-XX:CMSInitiatingOccupancyFraction=70 -XX:+UseCMSCompactAtFullCollection由于CMS收集器会产生碎片,此参数设置在垃圾收集器后是否需要一次内存碎片整理过程,仅在CMS收集器时有效 -XX:+CMSFullGCBeforeCompaction设置CMS收集器在进行若干次垃圾收集后再进行一次内存碎片整理过程,通常与UseCMSCompactAtFullCollection参数一起使用 -XX:+UseFastAccessorMethods原始类型优化 -XX:+DisableExplicitGC是否关闭手动System.gc -XX:+CMSParallelRemarkEnabled降低标记停顿 -XX:LargePageSizeInBytes内存页的大小不可设置过大,会影响Perm的大小,-XX:LargePageSizeInBytes=128m Client、Server模式默认GC 新生代GC方式老年代和持久代GC方式 Client Serial 串行GC Serial Old串行GC Server Parallel Scavenge 并行回收GC Parallel Old 并行GC Sun/oracle JDK GC组合方式 新生代GC方式老年代和持久代GC方式 -XX:+UseSerialGC Serial串行GC Serial Old串行GC -XX:+UseParallelGC Parallel Scavenge 并行回收GC Serial Old并行GC -XX:+UseConcMarkSweepGC ParNew并行GC CMS并发GC当出现“Concurrent Mode Failure”时采用Serial Old串行GC -XX:+UseParNewGC ParNew并行GC Serial Old串行GC -XX:+UseParallelOldGC Parallel Scavenge 并行回收GC Parallel Old并行GC -XX:+UseConcMarkSweepGC-XX:+UseParNewGC Serial串行GC CMS并发GC当出现“Concurrent Mode Failure”时采用Serial Old串行GC 链接:http://blog.csdn.net/java2000_wl/article/details/8030172

优秀的个人博客,低调大师

java8学习:用流收集数据

内容来自《 java8实战 》,本篇文章内容均为非盈利,旨为方便自己查询、总结备份、开源分享。如有侵权请告知,马上删除。书籍购买地址:java8实战 下面我们将采用这样一个实体类 @Data public class Dish { private final String name; private final boolean vegetarian; //是否是素食 private final int calories; //卡路里 private final Type type; //类型 public enum Type{ MEAT,FISH,OTHER; } public Dish(String name, boolean vegetarian, int calories, Type type) { this.name = name; this.vegetarian = vegetarian; this.calories = calories; this.type = type; } } 上面是一个菜单的一个实体类,下面我们添加到list中 List<Dish> menu = Arrays.asList( new Dish("apple",true,50, Dish.Type.OTHER), new Dish("chicken",false,350, Dish.Type.MEAT), new Dish("rich",true,150, Dish.Type.OTHER), new Dish("pizza",true,350, Dish.Type.OTHER), new Dish("fish",false,250, Dish.Type.FISH), new Dish("orange",true,70, Dish.Type.OTHER), new Dish("banana",true,60, Dish.Type.OTHER)); 好了到这就可以开始进行菜单中菜的分类了,那么我们可以先按着原来的办法,按着type分类,如下 @Test public void test() throws Exception { Map<Dish.Type,List<Dish>> groupByType = new HashMap<>(); for (Dish dish : menu) { Dish.Type type = dish.getType(); List<Dish> dishes = groupByType.get(type); if (dishes == null){ //如果为null说明第一次遇到某一个type dishes = new ArrayList<>(); groupByType.put(type,dishes); } dishes.add(dish); } System.out.println(groupByType); } 那么我们用Stream中的collect方法来实现 @Test public void test() throws Exception { Map<Dish.Type, List<Dish>> collect = menu.stream().collect(Collectors.groupingBy(menu -> menu.getType())); System.out.println("collect = " + collect); } 到这我们就知道了collect能给我们带来的便捷,下面将具体介绍collect的使用,以及自定义一个collect能接受的参数 使用 求上面菜单中有多少个菜 Long collect = menu.stream().collect(Collectors.counting()); //更好的做法 long count = menu.stream().count(); 查找最大和最小的卡路里 //既然是找出最大和最小的卡路里,那么肯定是有比较器的,比较器比较Dish中的卡路里属性 //最大 Comparator<Dish> comparator = Comparator.comparing(Dish::getCalories); Optional<Dish> collect = menu.stream().collect(Collectors.maxBy(comparator)); //对于返回Option很正常,万一比较列表没有值呢?所以会返回一个Option容器 //最小 Optional<Dish> collect1 = menu.stream().collect(Collectors.minBy(comparator)); 汇总:summingInt(),可接收一个把对象映射为求和所需int的函数,并返回收集器,(也就是说求和操作) //计算卡路里总和 Integer collect = menu.stream().collect(Collectors.summingInt(Dish::getCalories)); System.out.println(collect); //下面的更好一些,避免了拆箱装箱操作 int sum = menu.stream().mapToInt(Dish::getCalories).sum(); System.out.println("sum = " + sum); //当然有summingInt就会有summintLong,summingDouble 求卡路里平均数 Double collect = menu.stream().collect(Collectors.averagingInt(Dish::getCalories)); 还有一个方法的返回值包含上面所有的信息:summarizingInt() IntSummaryStatistics collect = menu.stream().collect(Collectors.summarizingInt(Dish::getCalories)); System.out.println("collect = " + collect); //输出如下,包含最大值最小值等信息 输出:collect = IntSummaryStatistics{count=7, sum=1280, min=50, average=182.857143, max=350} //想访问里面的任一属性只要通过get方法即可,比如:getMin(); 连接字符串 //将菜名全部连接起来 String collect = menu.stream().map(Dish::getName).collect(Collectors.joining()); //collect = applechickenrichpizzafishorangebanana System.out.println("collect = " + collect); //上面的结果看不清楚,join也提供了重载的方法,可以加入分隔符 String collect1 = menu.stream().map(Dish::getName).collect(Collectors.joining(",")); //collect1 = apple,chicken,rich,pizza,fish,orange,banana System.out.println("collect1 = " + collect1); String collect2 = menu.stream().map(Dish::getName).collect(Collectors.joining(",","[","]")); //collect2 = [apple,chicken,rich,pizza,fish,orange,banana] System.out.println("collect2 = " + collect2); ruducing //我们可以使用reducing方法来实现之前的求和操作 //0作为初始值,Dish::getCalories每次要执行的方法会返回一个值,Integer::sum对返回的值进行的操作 //初始值 转换函数 累计函数 Integer collect = menu.stream().collect(Collectors.reducing(0, Dish::getCalories, Integer::sum)); //实现上面求卡路里最大值 //可以将下面的一个参数的reducing看做是上面三个参数的特殊形式,它把流中的第一个项目作为起点,返回值是输入的参数dish1或dish2,所以如果输入参数 //不存在也会有Option Optional<Dish> collect = menu.stream().collect(Collectors.reducing(((dish1, dish2) -> dish1.getCalories() > dish2.getCalories() ? dish1 : dish2))); 我们前面说的reduce和现在说的collect有什么区别? reduce方法旨在把两个值结合起来生成一个新值,他是一个不可变的归约 collect方法的设计就是要改变容器,从而累计要输出的结果 reducing测试 menu.stream() .collect(Collectors.reducing(((dish1, dish2) -> dish1.getName() + dish2.getName()))); //这段代码可以通过编译吗? //不可以因为reducing需要一个BinaryOperator,而它的定义如下 public interface BinaryOperator<T> extends BiFunction<T,T,T> //如此可以看出它传入的TT返回也需要是T类型,所以我们传入Dish返回的也应该是Dish类型 分组 其实前面第一个例子我们应使用了分组了,那就是根据菜单中的type进行分组 //我们给groupingBy方法传递了一个FUnction,它提取了流中每一道Dish的type,然后根据type分组,我们把传入的Function称为分类函数,因为他用来把流中的元素分成不同的组 //map的key就是type类型,value就是属于type的所有Dish Map<Dish.Type, List<Dish>> collect = menu.stream().collect(Collectors.groupingBy(Dish::getType)); 但是如果我们的分类条件并不一定是方法引用的返回值呢?比如我们要卡路里> 120的和小于120的,那该怎么分? @Test public void test() throws Exception { Map<Integer, List<Dish>> collect = menu.stream() .collect(Collectors.groupingBy(dish -> { if (dish.getCalories() <= 120) return 1; //只需要区别开就好 else return 2;//只是区别开就好 })); System.out.println("collect = " + collect); } //输出:collect = {1=[Dish(name=apple, vegetarian=true, calories=50, type=OTHER), Dish(name=orange, vegetarian=true, calories=70, type=OTHER), Dish(name=banana, vegetarian=true, calories=60, type=OTHER)], // 2=[Dish(name=chicken, vegetarian=false, calories=350, type=MEAT), Dish(name=rich, vegetarian=true, calories=150, type=OTHER), Dish(name=pizza, vegetarian=true, calories=350, type=OTHER), Dish(name=fish, vegetarian=false, calories=250, type=FISH)]} 当然返回1和2是不清楚的,如果大于120算是高卡路里,否则就是低卡路里,那么就可以定义一个枚举,然后返回枚举值加以切分就好了 多级分组 如果我们不止只想分一层,比如我们要按是否是素食和肉食分组,然后再按卡路里是否<=60分组,这次我们不返回1和2,采用枚举返回,那么该怎么做 enum MyEnum{YES,NO} @Test public void test() throws Exception { Map<Boolean, Map<MyEnum, List<Dish>>> collect = menu.stream().collect(Collectors.groupingBy(Dish::isVegetarian, Collectors.groupingBy(dish -> { if (dish.getCalories() <= 60) return MyEnum.YES; else return MyEnum.NO; }))); System.out.println(collect); } //{false={NO=[Dish(name=chicken, vegetarian=false, calories=350, type=MEAT), Dish(name=fish, vegetarian=false, calories=250, type=FISH)]}, // true={YES=[Dish(name=apple, vegetarian=true, calories=50, type=OTHER), Dish(name=banana, vegetarian=true, calories=60, type=OTHER)], // NO=[Dish(name=rich, vegetarian=true, calories=150, type=OTHER), Dish(name=pizza, vegetarian=true, calories=350, type=OTHER), Dish(name=orange, vegetarian=true, calories=70, type=OTHER)]}} 按group收集数据 上面多级分组我们看到可以把第二个groupingby收集器传递给外层收集器来实现多级分组。传递给第一个groupingby的第二个收集器可以是任何类型,而不一定还是一个groupingby 求每种(type)菜的个数 Map<Dish.Type, Long> collect = menu.stream().collect(Collectors.groupingBy(Dish::getType, Collectors.counting())); //collect = {MEAT=1, FISH=1, OTHER=5} System.out.println("collect = " + collect); 求每组最高卡路里的dish @Test public void test() throws Exception { Map<Dish.Type, Optional<Dish>> collect = menu.stream() .collect(Collectors.groupingBy(Dish::getType, Collectors.maxBy(Comparator.comparingInt(Dish::getCalories)))); System.out.println("collect = " + collect); //collect = {MEAT=Optional[Dish(name=chicken, vegetarian=false, calories=350, type=MEAT)], // OTHER=Optional[Dish(name=pizza, vegetarian=true, calories=350, type=OTHER)], // FISH=Optional[Dish(name=fish, vegetarian=false, calories=250, type=FISH)]} } 对于上面的代码,Map的第二个泛型是Option类型的,但是我们可以想到,如果menu中没有对应的type,那么根本就不可能到maxBy方法让其返回Option,所以在这的Option并不是很有用,反而影响了我们的查看和使用。那么我们就可以把它去掉 Map<Dish.Type, Dish> collect = menu.stream().collect(Collectors.groupingBy(Dish::getType, Collectors.collectingAndThen(Collectors.maxBy(Comparator.comparingInt(Dish::getCalories)), Optional::get))); System.out.println("collect = " + collect); //collect = {OTHER=Dish(name=pizza, vegetarian=true, calories=350, type=OTHER), // FISH=Dish(name=fish, vegetarian=false, calories=250, type=FISH), // MEAT=Dish(name=chicken, vegetarian=false, calories=350, type=MEAT)} 对照上面,我们发现已经去掉了Option的包装,我们是用的Collectors.collectingAndThen方法,此方法接收两个参数:要转换的收集器和转换函数,首先他会找出最大的卡路里然后再将这个最大的卡路里对象进行转换:Option::get。所以我们的输出结果中就去掉了Optional 一些其他与groupingby使用的例子 求每组的卡路里总和 Map<Dish.Type, Integer> collect = menu.stream().collect(Collectors.groupingBy(Dish::getType, Collectors.summingInt(Dish::getCalories))); //{OTHER=680, MEAT=350, FISH=250} 和mapping组合使用 public void test() throws Exception { Map<Dish.Type, Set<Integer>> collect = menu.stream().collect( Collectors.groupingBy(Dish::getType, Collectors.mapping(dish -> { if (dish.getCalories() <= 120) return 1; else return 2; }, Collectors.toSet()))); System.out.println("collect = " + collect); } //collect = {MEAT=[2], FISH=[2], OTHER=[1, 2]} 分区 分区是分组的一种特殊情况:使用一个Predicate函数作为分类函数,所以分区就只能分为true和false区 //分开素食和肉食 Map<Boolean, List<Dish>> collect = menu.stream().collect(Collectors.partitioningBy(Dish::isVegetarian)); //然后我们通过collect.get(true)就能找到素食了 //当然用filter过滤也可以只不过是只能过滤是素食或者是肉食的Dish了 分区的优势 分区的好处就比如上面代码演示了,filter只能保留一个结果的Dish,要不是素食的要么不是素食的,而分区就都能保留下来 分区的重载方法可以传入一个groupingby进行区内分组 Map<Boolean, Map<Dish.Type, List<Dish>>> collect = menu.stream().collect(Collectors.partitioningBy(Dish::isVegetarian, Collectors.groupingBy(Dish::getType))); //根据是否是素食分区后,再将每个区内细分为各个类型 分区后的第二个参数也可以再次传入一个分区进行二次分区 记住分区内只能传入返回boolean值的表达式,否则无法通过编译 收集器接口Collector 此接口为实现具体的归约操作提供了范本,之前的toList或者groupingby都是此接口实现的,自己也可以通过这个接口自定义归约实现 接口定义 public interface Collector<T, A, R> { /** * 建立新容器 * 返回一个Supplier,他是用来创建一个空的累加器的实例,共数据收集过程使用 */ Supplier<A> supplier(); /** * 将元素添加到结果容器 * 会返回执行归约操作的函数,他就是将元素处理后添加到累加器的,他会有两个参数,一个是累加器,一个是元素本身 */ BiConsumer<A, T> accumulator(); /** * 对结果容器应用最终转换 * 必须返回在累加过程的最后要调用的一个函数,以便将累加器对象转换为整个集合操作的最终结果 * 如果accumulator方法操作完之后已经符合期待类型,那么此方法可以原样返回不做操作 */ Function<A, R> finisher(); /** * 合并两个结果容器:用于并行 * 会返回一个供归约操作使用的函数,定义了对流的各个子部分进行并行处理时,各个子部分归约所得的累加器要如何合并 */ BinaryOperator<A> combiner(); /** * 类似Spliterator中的characteristics方法 * 会返回一个不可变的Characteristics集合,它定义了收集器的行为 * 尤其是关于流是否可以并行归约,以及可以使用那些优化的提示 * 包括三个枚举 * CONCURRENT:accumulator方法可以从多个线程同事调用,且该收集器可以并行归约流,如果收集器没有标为UNORDERED,那么它仅在用于无序数据源时才可以并行归约 * UNORDERED:归约结果不受流中项目的遍历和累积顺序的影响 * IDENTITY_FINISH:累计器对象将会直接用作归约过程的最终结果,也就意味着,将累加器A不加检查的转换为结果R是安全的 */ Set<Characteristics> characteristics(); .... } T是流中要收集的项目的泛型 A是累加器的类型,累加器是在手机过程中用于累积部分结果的对象 R是收集操作得到的对象的类型(收集器返回值类型) 实现一个类似toList()方法的功能 public class MyToList<T> implements Collector<T, List<T>, List<T>> { @Override public Supplier<List<T>> supplier() { //创建一个list作为累加器供以后使用 return ArrayList::new; } @Override public BiConsumer<List<T>, T> accumulator() { //每次传入累加器和元素,然后把元素add到累加器 //也可以写做 return (list,t) -> list.add(t); return List::add; } @Override public BinaryOperator<List<T>> combiner() { //如果是并行的,那么这就是将累加器合并的操作 return (l1,l2) -> { l1.addAll(l2); return l1; }; } @Override public Function<List<T>, List<T>> finisher() { //因为我们要实现的功能就是将值放入List,现在的累加器正好是我们所需要的List类型,所以直接返回就好啦 //对于下面这个identity方法的解释 //Returns a function that always returns its input argument //输入进来的在返回出去 return Function.identity(); } // @Override public Set<Characteristics> characteristics() { //IDENTITY_FINISH:累计器对象将会直接用作归约过程的最终结果,因为我们不需要转换为其他类型的结果 //CONCURRENT:可以从多个线程同事调用,且该收集器可以并行归约流,但是没有标为UNORDERED,那么它仅在用于无序数据源时才可以并行归约 return Collections.unmodifiableSet(EnumSet.of(Characteristics.IDENTITY_FINISH,Characteristics.CONCURRENT)); } } 进行自定义收集而不去实现Collector 对于IDENTITY_FINISH的收集操作,还有一种办法可以得到同样的结果而无需从头实现新的Collectors接口 Stream有一个重载的collect方法可以接受另外三个方法-->supplier,accumulator,combiner,也就是说不用实现自己的类而是直接把实现逻辑传入固定的参数位置就能够使用,比如把Name收集到List ArrayList<Object> collect = menu.stream().map(Dish::getName).collect( ArrayList::new,//创建累加器 List::add,//对每个元素实现的累加器操作 List::addAll);//并行组合累加器的操作

优秀的个人博客,低调大师

java8学习:lambda表达式(1)

内容来自《 java8实战 》,本篇文章内容均为非盈利,旨为方便自己查询、总结备份、开源分享。如有侵权请告知,马上删除。书籍购买地址:java8实战 上一篇内容解释了行为参数化的概念并用实例演示了如何是行为参数化,文末提到了lambda表达式,那么本文将讲解lambda表达式 如平常的使用方法如下 Comparator<Apple> comparator = new Comparator<Apple>() { @Override public int compare(Apple o1, Apple o2) { return Integer.compare(o1.getWeight(),o2.getWeight()); } }; lambda:Comparator<Apple> comparator = (Apple a1,Apple a2) -> Integer.compare(a1.getWeight(),a2.getWeight()); 对照上面的普通使用方法和lambda,lambda没有名称,但是一样有参数列表,函数主体以及返回值,还可以拥有抛出的异常类型,但是上面的lambda还是可以进一步简化写法的,下面讲提到 lambda各部分名称 Comparator<Apple> comparator = (Apple a1,Apple a2) -> Integer.compare(a1.getWeight(),a2.getWeight()); 参数列表 ->只是用来分割参数列表和函数主体的 函数主体 如上清楚的标明了lambda各部分的名称,那么就会有疑问,他的返回值是什么?上面lambda的函数主体的结果就是其返回值,只是没有写return而已,如下 Comparator<Apple> comparator = (Apple a1,Apple a2) -> { return Integer.compare(a1.getWeight(),a2.getWeight()); }; //注意大括号 下面是一些lambda的例子 (String s) -> s.length(); //参数为String,函数主体已给出,函数返回值是int (int x , int y ) ->{ //参数为(int,int),函数主体给出,函数无返回值void System.out.println(x); System.out.println(y); } () -> 42; //没有参数,但是有返回值是int,就好比是 public int get(){return 42;} 下面这个两个lambda是不正确的 (Integer i) -> return i + 1; //如果是有return明确返回要使用花括号了,如上的Comparator代码中的return (String s) -> {"mes";} //“mes”是一个表达式不是一个语句,如果想修正此问题,那么可以去掉花括号,或者显示声明return lambda可以在那里使用呢? 我们现在只是在跟着文章懵逼的一个字符字符的敲上去,然后运行看结果,只是知道这样一种lambda形式,但是根本就不知道这个鬼东西可以出现在哪里啊?在这就要涉及到一个函数式接口的概念了 函数式接口就是接口中只有一个抽象方法,此方法不允许重载,而且你只要保证此接口中只有一个抽象方法,其他的default之类的其他方法任你编写,如下 @FunctionalInterface public interface PP { boolean isX (); default void cc(){ System.out.println("ccc"); } } 如上注解就类似与@Override注解一样,检测此接口是否是函数式接口,如果不是就会有报错信息了 了解了函数式接口是什么东西,那么我们回忆一下上一篇中,我们其实已经编写了函数式接口,但是我们只是不知道函数式接口这个概念,比如 interface Filter<T>{ boolean filter(T t); } public class Java8 { @Test public void test() throws Exception { List<Apple> apples = Arrays.asList(); getAppleByColor(apples,apple -> apple.getWeight() > 1000); } public <T> void getAppleByColor(List<T> ts,Filter<T> filter){ for (T t : ts) { if (filter.filter(t)){ System.out.println("..."); } } } } 如上的函数式接口Filter就是我们上次编写的,他只有一个抽象方法,然后我们就可以利用lambda对含有此接口为参数的方法传入lambda了,然后实现了行为参数化 我们也可以看上一篇提到的实例:Comparator和Runnable接口的定义,其中有很多方法,但是只有一个抽象方法,这就不贴出来了,自己查看源码就可以、 用函数式接口可以干啥?lambda表达式允许直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例,我们也可以用匿名内部类完后才能同样的事情,(这段话是书中的原话:自己的理解就是lambda实例化了接口并为唯一的抽象方法提供了实现),比如 Runnable runnable = new Runnable() { @Override public void run() { System.out.println("xx"); } }; Runnable runnable1 = () -> System.out.println("xx"); 如上两段代码,一个是匿名内部类实现的,一个是lambda提供实现,那么都是为Runnable接口中的抽象方法run提供了逻辑实现即System.out.println("xx") 函数描述符 抽象方法的方法签名基本就是lambda的表达式签名,这种就叫做函数描述符 就如上面代码Runnable,run方法的方法签名是void无返回并且无参数,那么对应的lambda就是无参数和无返回的void 实例 public static Callable<String> fe(){ return () -> "sd"; } 上面的lambda是否是正确的可以通过编译呢?我们来调用一下 static void main(String[] args) throws Exception { Callable<String> fe = fe(); System.out.println(fe.call()); //sd } 证明了上面的是正确的,我们来一步步的分析为什么它编译是正确的,首先观察Callable的定义 interface Callable { V call() throws Exception; } 首先它是一个函数是借口,那么返回一个lambda作为接口的实现肯定是没有问题的 其次我们Callable的泛型是String,并且此泛型作为了抽象方法的返回值,方法的返回值也就是String,到这就是看出Callable的lambda的描述信息是() -> String 我们再看return () -> "sd"; 这个lambda的返回值也是String,与上面我们分析的一致,所以编译无误 我们来一个错误的 Predicate<Apple> predicate = (Apple apple) -> apple.getWeight(); 为什么她是错误的,我们还的看Predicate的抽象方法的定义 test(T t) 她是返回boolean,但是我们的 apple.getWeight();返回int,这并匹配不上,所以是编译有错的 小实战 内容要求,读取文本,让你返回几行你就返回几行 观察要求,返回行的话便捷操作就使用到了BufferReader读取类的readLine,然后让返回随机行,那么这个肯定是根据意思随机返回了,很明显这里需要一个自定义函数式接口,以实现行为参数化 @FunctionalInterface interface MyLineReader { String process(BufferedReader reader) throws IOException; } public class A{ private String readLine(BufferedReader reader,MyLineReader lineReaderInterface) throws IOException { return lineReaderInterface.process(reader); } @Test public void test() throws Exception { BufferedReader reader = new BufferedReader(new FileReader("zookeeper.log")); String s = readLine(reader, readerIn -> readerIn.readLine() + "\n" + readerIn.readLine()); System.out.println(s); reader.close(); } } 如上定义函数式接口,然后创建方法,该方法参数一个是流,一个是函数式接口 然后传入流和利用lambda实现抽象方法的实现逻辑,这里我们想读几行出来就可以了 这里我们还是有啰嗦的地方,那么就是定义接口!每次我们实现一个lambda的不同的逻辑,那么就必须定义一个新接口,xxxoooo,其实java8已经为我们准备好了一些已经写好的接口,我们拿来用就好了,如果以后变成找不到合适的已经默认实现的接口,那么我们还是的自己定义接口。emmmm 接口全部都是java.util.funcation包下,这些接口我会在文末列出来的,如果写到这,那么就又懵逼了 下面我们先来看内置函数接口Predicate @FunctionalInterface public interface Predicate<T> { boolean test(T t); ... ... } 上面这个货不就是咱们编写的Filter嘛,拿过来比较一下 interface Filter<T>{ boolean filter(T t); } 除了缺注解缺public,类名方法名不一样,其他的都一样好吧!!虽然这么多不一样,但是使用方法是一样的啊!我们把方法参数Filter改为Predicate就好了,这种接口的定义为传入T返回boolean:(T t) -> boolean;这样的接口有人叫断言型接口,不过我感觉称为判断接口更容易理解,传入一个T参数,根据自己逻辑判断出true和false 试用一下 Predicate<Integer> predicate = (Integer i) -> i > 3; System.out.println(predicate.test(4)); //true System.out.println(predicate.test(1)); //false 下面是Consumer内置函数接口 @FunctionalInterface public interface Consumer<T> { void accept(T t); ... ... } 只要理解了上面的Predicate,那么这个很容易理解了,函数描述符为:(T t) -> void,不返回任何东西,那么这个接口就是肉包子打狗,T就是肉包子,传入后进行逻辑执行完毕后,是不反回东西的 试用一下 Consumer<Integer> consumer = (Integer i) -> System.out.println(i); consumer.accept(1); //1 consumer.accept(2); //2 下面是Supplier函数接口 @FunctionalInterface public interface Supplier<T> { T get(); } 这个理解也不难,就是不给他东西,但是他会造东西给你,函数描述符为:() - T 试用一下 Supplier<Integer> supplier = () -> new Integer(new Random().nextInt()); System.out.println(supplier.get()); //-1853289089 System.out.println(supplier.get()); //829915024 内置函数Function @FunctionalInterface public interface Function<T, R> { R apply(T t); ... ... } 传入一个T,返回一个R,你就可以用它传入一个apple,返回apple的颜色或者是重量,当让你也可以返回apple,泛型的限定并没有限制你必须返回什么东西 试用一下 Function<String,Integer> function = (String str) -> Integer.parseInt(str); System.out.println(function.apply("123")); //123 public class A{ @Test public void test() throws Exception { Function<A,A> function = (A a) -> a; A a = new A(); A apply = function.apply(a); System.out.println(a == apply); //true } } 好了到这算是了解了基本的函数式接口,那我们平常如果需要传入两个参数,返回一个东西,但是上面的也没有类似的泛型规定的函数式接口啊,这时候我们应该是去java.util.function包下找对应泛型的接口使用,如果没有就需要自己定义,定义方法跟上面定义Filter一样,只是泛型多加一个就好了,对于我们先在的要求是有内置的函数式接口的,比如BiFunction @FunctionalInterface public interface BiFunction<T, U, R> { R apply(T t, U u); .... } 传入T和U两个参数,返回R 到这我们基本是能应对各种的泛型要求的函数式接口了,但是需要注意一个问题,那就是拆箱和装箱的问题,我们知道基本类型是要比它对应的包装类型的占用空间要小的,如下 会装箱 Predicate<Integer> predicate = (Integer i) -> i == 3; 不会装箱 IntPredicate intPredicate = (int i) -> i == 3; 所以一般来说针对专门的输入参数类型的函数式接口的名称都要加上对应的原始类型前缀,比如上面的IntPredicate,IntConsumer,IntFunction等 好了这一篇内容以及内容不少了,下面是总结的java8内置的函数式接口,等下一篇内容继续详细了解lambda BiConsumer<T,U> BiFunction<T,U,R> BinaryOperator<T> BiPredicate<T,U> BooleanSupplier Consumer<T> DoubleBinaryOperator DoubleConsumer DoubleFunction<R> DoublePredicate DoubleSupplier DoubleToIntFunction DoubleToLongFunction DoubleUnaryOperator Function<T,R> IntBinaryOperator IntConsumer IntFunction<R> IntPredicate IntSupplier IntToDoubleFunction IntToLongFunction IntUnaryOperator LongBinaryOperator LongConsumer LongFunction<R> LongPredicate LongSupplier LongToDoubleFunction LongToIntFunction LongUnaryOperator ObjDoubleConsumer<T> ObjIntConsumer<T> ObjLongConsumer<T> Predicate<T> Supplier<T> ToDoubleBiFunction<T,U> ToDoubleFunction<T> ToIntBiFunction<T,U> ToIntFunction<T> ToLongBiFunction<T,U> ToLongFunction<T> UnaryOperator<T>

优秀的个人博客,低调大师

Dicom 学习笔记-DICOM C-Store 消息服务

引言 之前总体介绍了 DICOM 的消息服务,可以参考这篇博文,但是有关每个服务的详细信息没有讲解,本文就结合开源 DICOM 库 fo-dicom 详细介绍一下 C-Store 服务。 名词简介 在正式讲解前我们还需要弄明白以下几个名词 SCU:Service Class User,可以理解为客户端(用户端); SCP:Service Class Provider。可以理解为服务端; C-Store 消息服务 前文已经说明了 C-Store 服务用于一个 DIMSE-service-user 在同等的 DIMSE-service-user 上存储一个复合 SOP 实例,其实主要就是用来归档影像,在实际场景中,医院的设备(DR【普放】、CT【断层扫描】、MR【核磁】)做完检查后会产生影像文件(复合 SOP 实例),然后设备会通过 C-Store 服务将这些影像文件归档到 PACS 系统中。在这个过程中设备就相当于客户端,需要实现 C-Store SCU,PACS 系统相当于服务端,需要实现 C-Store SCP。 C-Store 流程如下: C-Store SCU 结合开源库 fo-dicom 我们可以很轻松的实现 C-Store SCU,fo-dicom 已经封装好了 C-Store Request,具体代码可以在 GitHub 上查看 DicomCStoreRequest.cs,我们只需要如下的代码就可以实现 C-Store SCU:这里需要引用命名空间【Dicom.Network】 using Dicom.Network; var client = new DicomClient(); client.NegotiateAsyncOps(); var request = new DicomCStoreRequest({DICOM file path}); request.OnResponseReceived += (req, response) => { Console.WriteLine("C-Store Response Received, Status: " + response.Status); }; client.AddRequest(request); client.Send({C-Store SCP IP}, {C-Store SCP Port}, false, {C-Store SCU AE Title}, {C-Store SCP AE Title}); DICOM file path 指待归档的 DICOM 文件路径; C-Store SCP IP 指 C-Store 服务端的 IP 地址或机器名; C-Store SCP Port 指 C-Store 服务端的端口; C-Store SCU AE Title 指 C-Store 客户端应用实体的名称; C-Store SCP AE Title 指 C-Store 服务端应用实体的名称; 基于以上代码就实现了一个简单的 C-Store 客户端,可用于归档单张影像,涉及到多张影像归档可在外层增加循环实现。 C-Store SCP C-Store SCP 可以通过派生 DicomService 服务类来实现 Dicom 服务的基本框架,然后实现 IDicomServiceProvider 和 IDicomCStoreProvider 接口来实现。具体代码可以参考这里。 最后我们将 C-Store SCP 的代码运行起来,然后使用上面 C-Store SCU 的代码来归档一张影像,然后我们会在 C-Store SCU 收到如下成功的消息【C-Store Response Received, Status: Success】,同时在 C-Store SCP 工程的【bin】目录下能找到一个【DICOM】文件夹,文件夹里面就是刚刚测试归档的影像。

资源下载

更多资源
优质分享App

优质分享App

近一个月的开发和优化,本站点的第一个app全新上线。该app采用极致压缩,本体才4.36MB。系统里面做了大量数据访问、缓存优化。方便用户在手机上查看文章。后续会推出HarmonyOS的适配版本。

Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

用户登录
用户注册