Spark常用算子-KeyValue数据类型的算子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
package com.test;
 
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
 
import org.apache.spark.Partitioner;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaPairRDD;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.api.java.Optional;
import org.apache.spark.api.java.function.Function;
import org.apache.spark.api.java.function.Function2;
import org.apache.spark.api.java.function.PairFunction;
import org.apache.spark.api.java.function.VoidFunction;
 
import scala.Tuple2;
 
/**
 * KeyValue数据类型的Transformation算子
 * @author FengZhen
 *
 */
public class SparkKeyValue{
     
    public static void main(String[] args){
        //SparkConf conf = new SparkConf().setAppName(SparkKeyValue.class.getName()).setMaster("local[2]");
        SparkConf conf = new SparkConf().setAppName(SparkKeyValue.class.getName());
        JavaSparkContext sc = new JavaSparkContext(conf);
        // 数据
        JavaRDD<String> ds = sc.textFile("hdfs://bjqt/data/labeldata/datalabel.csv");
        /**
         * 获取身份证:姓名
         */
        JavaPairRDD<String, String> pairRDD = ds.mapToPair(new PairFunction<String, String, String>() {
            @Override
            public Tuple2<String, String> call(String t) throws Exception {
                String line = t.replace("‘", "");
                String[] lines = line.split(",");
                return new Tuple2<String, String>(lines[1], lines[0]);
            }
        });
         
        Map<String, String> pairMap = pairRDD.collectAsMap();
        System.out.println("pairMap=" + pairMap);
        /**
         *一、输入分区与输出分区一对一
         *15、mapValues算子
         */
        /**
         * 15、mapValues算子
         * mapValues :针对(Key, Value)型数据中的 Value 进行 Map 操作,而不对 Key 进行处理。
         * 获取身份证号:姓氏
         */
        JavaPairRDD<String, String> firstNameRDD = pairRDD.mapValues(new Function<String, String>() {
            @Override
            public String call(String v1) throws Exception {
                return v1.substring(0, 1);
            }
        });
        Map<String, String> firstNameMap = firstNameRDD.collectAsMap();
        System.out.println("firstNameMap=" + firstNameMap);
         
        /**
         * 二、对单个RDD或两个RDD聚集
        单个RDD聚集
            16、combineByKey算子
            17、reduceByKey算子
            18、partitionBy算子
        两个RDD聚集
            19、Cogroup算子
         */
        /**
         * 16、combineByKey算子
         * 下面代码为 combineByKey 函数的定义:
          combineByKey[C](createCombiner:(V) C,
          mergeValue:(C, V) C,
          mergeCombiners:(C, C) C,
          partitioner:Partitioner,
          mapSideCombine:Boolean=true,
          serializer:Serializer=null):RDD[(K,C)]
            说明:
            createCombiner: V => C, C 不存在的情况下,比如通过 V 创建 seq C。
            mergeValue: (C, V) => C,当 C 已经存在的情况下,需要 merge,比如把 item V
            加到 seq C 中,或者叠加。
            mergeCombiners: (C, C) => C,合并两个 C。
            partitioner: Partitioner, Shuff le 时需要的 Partitioner。
            mapSideCombine : Boolean = true,为了减小传输量,很多 combine 可以在 map
            端先做,比如叠加,可以先在一个 partition 中把所有相同的 key 的 value 叠加,
            再 shuffle。
            serializerClass: String = null,传输需要序列化,用户可以自定义序列化类:
            例如,相当于将元素为 (Int, Int) 的 RDD 转变为了 (Int, Seq[Int]) 类型元素的 RDD
             
            createCombiner: V => C ,这个函数把当前的值作为参数,此时我们可以对其做些附加操作(类型转换)并把它返回 (这一步类似于初始化操作)
            mergeValue: (C, V) => C,该函数把元素V合并到之前的元素C(createCombiner)上 (这个操作在每个分区内进行)
            mergeCombiners: (C, C) => C,该函数把2个元素C合并 (这个操作在不同分区间进行)
             
            获取相同姓氏的身份证号集合
         */
        JavaPairRDD<String, String> namePairRDD = firstNameRDD.mapToPair(new PairFunction<Tuple2<String,String>, String, String>() {
            @Override
            public Tuple2<String, String> call(Tuple2<String, String> t)
                    throws Exception {
                return new Tuple2<String, String>(t._2, t._1);
            }
        });
        Map<String, String> namePairMap = namePairRDD.collectAsMap();
        System.out.println("namePairMap="+namePairMap);
         
        JavaPairRDD<String, List<String>> combineByKeyRDD = namePairRDD.combineByKey(new Function<String, List<String>>() {
            @Override
            public List<String> call(String v1) throws Exception {
                List<String> list = new ArrayList<String>();
                list.add(v1);
                return list;
            }
        }, new Function2<List<String>, String, List<String>>(){
            @Override
            public List<String> call(List<String> v1, String v2) throws Exception {
                v1.add(v2);
                return v1;
            }
        }, new Function2<List<String>, List<String>, List<String>>() {
            @Override
            public List<String> call(List<String> v1, List<String> v2)
                    throws Exception {
                List<String> list = new ArrayList<String>();
                list.addAll(v1);
                list.addAll(v2);
                return list;
            }
        });
        Map<String, List<String>> combineByKeyMap = combineByKeyRDD.collectAsMap();
        System.out.println("combineByKeyMap="+combineByKeyMap);
         
         
        /**
         * 17、reduceByKey算子
         * reduceByKey 是比 combineByKey 更简单的一种情况,只是两个值合并成一个值,( Int, Int V)to (Int, Int C),比如叠加。所以 createCombiner reduceBykey 很简单,就是直接返回 v,而 mergeValue和 mergeCombiners 逻辑是相同的,没有区别。
                函数实现:
           def reduceByKey(partitioner: Partitioner, func: (V, V) => V): RDD[(K, V)]
            = {
            combineByKey[V]((v: V) => v, func, func, partitioner)
            }
            计算每个姓氏对应的人数
         */
        JavaPairRDD<String, Integer> firstNameCountPairRDD = namePairRDD.mapToPair(new PairFunction<Tuple2<String,String>, String, Integer>() {
            @Override
            public Tuple2<String, Integer> call(Tuple2<String, String> t)
                    throws Exception {
                return new Tuple2<String, Integer>(t._1, 1);
            }
        });
        JavaPairRDD<String, Integer> reduceByKeyRDD = firstNameCountPairRDD.reduceByKey(new Function2<Integer, Integer, Integer>() {
            @Override
            public Integer call(Integer v1, Integer v2) throws Exception {
                return v1+v2;
            }
        });
        Map<String, Integer> reduceByKeyMap = reduceByKeyRDD.collectAsMap();
        System.out.println("reduceByKeyMap="+reduceByKeyMap);
         
        /**
         * 18、partitionBy算子(按key分)
         * partitionBy函数对RDD进行分区操作。
            函数定义如下。
            partitionBy(partitioner:Partitioner)
            如果原有RDD的分区器和现有分区器(partitioner)一致,则不重分区,如果不一致,则相当于根据分区器生成一个新的ShuffledRDD。
         */
        JavaPairRDD<String, String> idNamePairRDD = namePairRDD.mapToPair(new PairFunction<Tuple2<String,String>, String, String>() {
            @Override
            public Tuple2<String, String> call(Tuple2<String, String> t)
                    throws Exception {
                return new Tuple2<String, String>(t._2, t._1);
            }
        });
        //男女分区
        JavaPairRDD<String, String> partitionRDD = idNamePairRDD.partitionBy(new Partitioner() {
            //分区数量
            @Override
            public int numPartitions() {
                return 2;
            }
            //根据分区规则指定分区
            @Override
            public int getPartition(Object arg0) {
                String idCard = (String)arg0;
                char genderSign = idCard.charAt(idCard.length()-2);
                String genderStr = String.valueOf(genderSign);
                Integer genderInt = Integer.parseInt(genderStr);
                if (genderInt%2 == 0) {
                    return 0;
                } else {
                    return 1;
                }
            }
        });
        Map<String, String> partitionMap = partitionRDD.collectAsMap();
        System.out.println("partitionMap="+partitionMap);
         
        /**
         * 两个RDD聚集
         * 19、Cogroup算子
         * cogroup函数将两个RDD进行协同划分,cogroup函数的定义如下。
          cogroup[W](other: RDD[(K, W)], numPartitions: Int): RDD[(K, (Iterable[V], Iterable[W]))]
          对在两个RDD中的Key-Value类型的元素,每个RDD相同Key的元素分别聚合为一个集合,并且返回两个RDD中对应Key的元素集合的迭代器。
          (K, (Iterable[V], Iterable[W]))
          其中,Key和Value,Value是两个RDD下相同Key的两个数据集合的迭代器所构成的元组。
         */
         
        List<Tuple2<Integer, String>> DBName= new ArrayList<Tuple2<Integer,String>>();
        DBName.add(new Tuple2<Integer, String>(1,"Spark"));
        DBName.add(new Tuple2<Integer, String>(2,"Hadoop"));
        DBName.add(new Tuple2<Integer, String>(3,"Kylin")); 
        DBName.add(new Tuple2<Integer, String>(4,"Flink"));
        DBName.add(new Tuple2<Integer, String>(6,"Sqoop"));
         
        List<Tuple2<Integer, String>> numType = new ArrayList<Tuple2<Integer,String>>();
        numType.add(new Tuple2<Integer, String>(1,"String"));
        numType.add(new Tuple2<Integer, String>(2,"int"));
        numType.add(new Tuple2<Integer, String>(3,"byte"));
        numType.add(new Tuple2<Integer, String>(4,"bollean"));
        numType.add(new Tuple2<Integer, String>(5,"float"));
        numType.add(new Tuple2<Integer, String>(1,"34"));
        numType.add(new Tuple2<Integer, String>(1,"45"));
        numType.add(new Tuple2<Integer, String>(2,"47"));
        numType.add(new Tuple2<Integer, String>(3,"75"));
        numType.add(new Tuple2<Integer, String>(4,"95"));
        numType.add(new Tuple2<Integer, String>(5,"16"));
        numType.add(new Tuple2<Integer, String>(1,"85"));
         
        JavaPairRDD<Integer, String> DBNameRDD = sc.parallelizePairs(DBName);
        JavaPairRDD<Integer, String> numTypeRDD = sc.parallelizePairs(numType);
         
        JavaPairRDD<Integer, Tuple2<Iterable<String>, Iterable<String>>> coGroupRDD = DBNameRDD.cogroup(numTypeRDD);
        Map<Integer, Tuple2<Iterable<String>, Iterable<String>>> coGroupMap = coGroupRDD.collectAsMap();
        System.out.println("coGroupMap="+coGroupMap);
         
        /**
         * 三、连接
        20、join算子
        21、leftOutJoin和 rightOutJoin算子
         */
        /**
         * 20、join算子
         *  join 对两个需要连接的 RDD 进行 cogroup函数操作,将相同 key 的数据能够放到一个分区,
         *  在 cogroup 操作之后形成的新 RDD 对每个key 下的元素进行笛卡尔积的操作,返回的结果再展平,
         *  对应 key 下的所有元组形成一个集合。最后返回 RDD[(K, (V, W))]。
         *  下 面 代 码 为 join 的 函 数 实 现, 本 质 是通 过 cogroup 算 子 先 进 行 协 同 划 分, 再 通 过flatMapValues 将合并的数据打散。
         *  this.cogroup(other,partitioner).f latMapValues{case(vs,ws) => for(v<-vs;w<-ws)yield(v,w) }
         */
        JavaPairRDD<Integer, Tuple2<String, String>> joinRDD = DBNameRDD.join(numTypeRDD);
        Map<Integer, Tuple2<String, String>> joinMap = joinRDD.collectAsMap();
        System.out.println("joinMap="+joinMap);
         
        /**
         * 21、leftOutJoin和 rightOutJoin算子
         * LeftOutJoin(左外连接)和RightOutJoin(右外连接)相当于在join的基础上先判断一侧的RDD元素是否为空,
         * 如果为空,则填充为空。 如果不为空,则将数据进行连接运算,并返回结果。
            下面代码是leftOutJoin的实现。
            if (ws.isEmpty) {
            vs.map(v => (v, None))
            } else {
            for (v <- vs; w <- ws) yield (v, Some(w))
            }
         */
         JavaPairRDD<Integer, Tuple2<String, Optional<String>>> leftOuterJoinRDD = DBNameRDD.leftOuterJoin(numTypeRDD);
         Map<Integer, Tuple2<String, Optional<String>>> leftOuterJoinMap = leftOuterJoinRDD.collectAsMap();
         System.out.println("leftOuterJoinMap="+leftOuterJoinMap);
         
        JavaPairRDD<Integer, Tuple2<Optional<String>, String>> rightOuterJoinRDD = DBNameRDD.rightOuterJoin(numTypeRDD);
        Map<Integer, Tuple2<Optional<String>, String>> rightOuterJoinMap = rightOuterJoinRDD.collectAsMap();
        System.out.println("rightOuterJoinMap="+rightOuterJoinMap);
         
         
        /**
         * Action算子
      一、无输出
        22、foreach算子
      二、HDFS
        23、saveAsTextFile算子
        24、saveAsObjectFile算子
      三、Scala集合和数据类型
        25、collect算子
        26、collectAsMap算子
            27、reduceByKeyLocally算子
            28、lookup算子
        29、count算子
        30、top算子
        31、reduce算子
        32、fold算子
        33、aggregate算子
         */
         
        /**
         * Action算子
         * 本质上在 Action 算子中通过 SparkContext 进行了提交作业的 runJob 操作,触发了RDD DAG 的执行。
            例如, Action 算子 collect 函数的代码如下
            // Return an array that contains all of the elements in this RDD.
            def collect(): Array[T] = {
            //提交 Job
            val results = sc.runJob(this, (iter: Iterator[T]) => iter.toArray)
            Array.concat(results: _*)
            }
         */
        /**
         *  无输出
         * 22、foreach算子
         * foreach 对 RDD 中的每个元素都应用 f 函数操作,不返回 RDD 和 Array, 而是返回Uint
         */
        ds.foreach(new VoidFunction<String>() {
            @Override
            public void call(String t) throws Exception {
                System.out.println(t);
            }
        });
         
        /**
         * 二、HDFS
        23、saveAsTextFile算子
        24、saveAsObjectFile算子
         */
        /**
         * 23、saveAsTextFile算子
         * 函数将数据输出,存储到 HDFS 的指定目录。
            下面为 saveAsTextFile 函数的内部实现,其内部
            通过调用 saveAsHadoopFile 进行实现:
            this.map(x => (NullWritable.get(), new Text(x.toString))).saveAsHadoopFile[TextOutputFormat[NullWritable, Text]](path)
            将 RDD 中的每个元素映射转变为 (null, x.toString),然后再将其写入 HDFS。
         */
        //firstNameRDD.saveAsTextFile("hdfs://bjqt/data/testSaveAsText");
         
        /**
         * 24、saveAsObjectFile算子
         * saveAsObjectFile将分区中的每10个元素组成一个Array,然后将这个Array序列化,
         * 映射为(Null,BytesWritable(Y))的元素,写入HDFS为SequenceFile的格式。
          下面代码为函数内部实现。
          map(x=>(NullWritable.get(),new BytesWritable(Utils.serialize(x))))
         */
        //firstNameRDD.saveAsObjectFile("hdfs://bjqt/data/testSaveAsObject");
         
        /**
         * 三、Scala集合和数据类型
        25、collect算子
        26、collectAsMap算子
            27、reduceByKeyLocally算子
            28、lookup算子
        29、count算子
        30、top算子
        31、reduce算子
        32、fold算子
        33、aggregate算子
         */
         
        /**
         * 25、collect算子
         * collect 相当于 toArray, toArray 已经过时不推荐使用, collect 将分布式的 RDD 返回为一个单机的 scala Array 数组。
         * 在这个数组上运用 scala 的函数式操作。
         */
        /**
         * 26、collectAsMap算子
         * collectAsMap对(K,V)型的RDD数据返回一个单机HashMap。 对于重复K的RDD元素,后面的元素覆盖前面的元素。
         */
        /**
         * 27、reduceByKeyLocally算子
         * 实现的是先reduce再collectAsMap的功能,先对RDD的整体进行reduce操作,然后再收集所有结果返回为一个HashMap。
         */
        Map<String, Integer> reduceByKeyLocallyMap = firstNameCountPairRDD.reduceByKeyLocally(new Function2<Integer, Integer, Integer>() {
            @Override
            public Integer call(Integer v1, Integer v2) throws Exception {
                return v1+v2;
            }
        });
        System.out.println("reduceByKeyLocallyMap="+reduceByKeyLocallyMap);
         
        /**
         * 28、lookup算子
         * 下面代码为lookup的声明。
            lookup(key:K):Seq[V]
            Lookup函数对(Key,Value)型的RDD操作,返回指定Key对应的元素形成的Seq。
            这个函数处理优化的部分在于,如果这个RDD包含分区器,则只会对应处理K所在的分区,
            然后返回由(K,V)形成的Seq。 如果RDD不包含分区器,则需要对全RDD元素进行暴力扫描处理,搜索指定K对应的元素。
         */
        List<String> lookupList = firstNameRDD.lookup("441283198412125733");
        System.out.println("lookupList="+lookupList);
         
        /**
         * 29、count算子
         * count 返回整个 RDD 的元素个数。
          内部函数实现为:
          defcount():Long=sc.runJob(this,Utils.getIteratorSize_).sum
         */
        long count = ds.count();
        System.out.println("count="+count);
         
        /**
         * 30、top算子
         * top可返回最大的k个元素。 函数定义如下。
            top(num:Int)(implicit ord:Ordering[T]):Array[T]
            相近函数说明如下。
            ·top返回最大的k个元素。
            ·take返回最小的k个元素。
            ·takeOrdered返回最小的k个元素,并且在返回的数组中保持元素的顺序。
            ·first相当于top(1)返回整个RDD中的前k个元素,可以定义排序的方式Ordering[T]。
            返回的是一个含前k个元素的数组。
         */
//      List<Tuple2<String, String>> topList = firstNameRDD.top(2, new Comparator<Tuple2<String,String>>() {
//          @Override
//          public int compare(Tuple2<String, String> o1, Tuple2<String, String> o2) {
//              return o1._1.compareTo(o2._1);
//          }
//      });
//      System.out.println("topList="+topList);
        List<Tuple2<String, String>> takeList = firstNameRDD.take(2);
        System.out.println("takeList="+takeList);
//      List<Tuple2<Integer, String>> takeOrderedList = DBNameRDD.takeOrdered(2, new Comparator<Tuple2<Integer,String>>() {
//
//          @Override
//          public int compare(Tuple2<Integer, String> o1,
//                  Tuple2<Integer, String> o2) {
//              return o1._1 > o2._1?0:1;
//          }
//      });
//      System.out.println("takeOrderedList="+takeOrderedList);
        Tuple2<String, String> first = firstNameRDD.first();
        System.out.println("first="+first);
         
        /**
         * 31、reduce算子
         * reduce函数相当于对RDD中的元素进行reduceLeft函数的操作。 函数实现如下。
          Some(iter.reduceLeft(cleanF))
          reduceLeft先对两个元素<K,V>进行reduce函数操作,然后将结果和迭代器取出的下一个元素<k,V>进行reduce函数操作,
            直到迭代器遍历完所有元素,得到最后结果。在RDD中,先对每个分区中的所有元素<K,V>的集合分别进行reduceLeft。
            每个分区形成的结果相当于一个元素<K,V>,再对这个结果集合进行reduceleft操作。
          例如:用户自定义函数如下。
          f:(A,B)=>(A._1+”@”+B._1,A._2+B._2)
         */
        Tuple2<String, String> reduceTuple2 = firstNameRDD.reduce(new Function2<Tuple2<String,String>, Tuple2<String,String>, Tuple2<String,String>>() {
            @Override
            public Tuple2<String, String> call(Tuple2<String, String> v1,
                    Tuple2<String, String> v2) throws Exception {
                return new Tuple2<String, String>(v1._1+v2._1, v1._2+v2._2);
            }
        });
        System.out.println("reduceTuple2="+reduceTuple2);
         
        /**
         * 32、fold算子
         * fold和reduce的原理相同,但是与reduce不同,相当于每个reduce时,迭代器取的第一个元素是zeroValue。
         * fold((”V0@”,2))( (A,B)=>(A._1+”@”+B._1,A._2+B._2))
         */
        Tuple2<String, String> foldTuple2 = firstNameRDD.fold(new Tuple2<String, String>("", ""), new Function2<Tuple2<String,String>, Tuple2<String,String>, Tuple2<String,String>>() {
            @Override
            public Tuple2<String, String> call(Tuple2<String, String> v1,
                    Tuple2<String, String> v2) throws Exception {
                return new Tuple2<String, String>(v1._1+v2._1, v1._2+v2._2);
            }
        });
        System.out.println("foldTuple2="+foldTuple2);
         
        /**
         * 33、aggregate算子
         * aggregate先对每个分区的所有元素进行aggregate操作,再对分区的结果进行fold操作。
          aggreagate与fold和reduce的不同之处在于,aggregate相当于采用归并的方式进行数据聚集,这种聚集是并行化的。
            而在fold和reduce函数的运算过程中,每个分区中需要进行串行处理,每个分区串行计算完结果,
            结果再按之前的方式进行聚集,并返回最终聚集结果。
          函数的定义如下。
            aggregate[B](z: B)(seqop: (B,A) => B,combop: (B,B) => B): B
            广播(broadcast)变量:其广泛用于广播Map Side Join中的小表,以及广播大变量等场景。
            这些数据集合在单节点内存能够容纳,不需要像RDD那样在节点之间打散存储。
            Spark运行时把广播变量数据发到各个节点,并保存下来,后续计算可以复用。
            相比Hadoo的distributed cache,广播的内容可以跨作业共享。
             Broadcast的底层实现采用了BT机制。
         */
        String aggregateString = firstNameRDD.aggregate("", new Function2<String, Tuple2<String, String>, String>() {
            @Override
            public String call(String v1, Tuple2<String, String> v2) throws Exception {
                System.out.println("v1="+v1+"--v2="+v2._1+">"+v2._2);
                return v1+"="+v2._1;
            }
        }, new Function2<String, String, String>() {
            @Override
            public String call(String v1, String v2) throws Exception {
                System.out.println("<<v1="+v1+"--v2="+v2);
                return v1+"-"+v2;
            }
        });
        System.out.println("aggregateString="+aggregateString);
         
        sc.close();
    }
}

  

posted on   嘣嘣嚓  阅读(405)  评论(0编辑  收藏  举报

编辑推荐:
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 记一次.NET内存居高不下排查解决与启示

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5
点击右上角即可分享
微信分享提示