• Scala基础【seq、set、map、元组、WordCount、队列、并行】


    一 seq集合

    LIst集合特性:数据有序(插入有序),可以存放重复数据

    val list = List(1,3,4,2,1)

    1 不可变List

    一般会采用List

    val seq = Seq(1,2,3,4)    //List(1, 2, 3, 4)
    val list = List(1,2,3,4)  //List(1, 2, 3, 4)
    
    • 1
    • 2

    在list首尾添加数据

    val ints: List[Int] = list :+ 5   // List(1, 2, 3, 4, 5)
    val ints1: List[Int] = 5 +: list  // List(5, 1, 2, 3, 4)
    
    • 1
    • 2

    Nil 在集合中表示空集合,主要作用是向里面存放数据

    println(ints2)    //List()
    val ints2 = 1 :: 2 :: 3 :: Nil  //List(1, 2, 3)
    
    • 1
    • 2

    把一个集合添加到另一个集合中

    :: 当成整体 :::当成个体

    val ints3 = 1 :: 2 :: 3 :: list :: Nil  //List(1, 2, 3, List(1, 2, 3, 4))
    val ints4 = 1 :: 2 :: 3 :: list ::: Nil //List(1, 2, 3, 1, 2, 3, 4)
    
    • 1
    • 2

    2 可变list

    val list = ListBuffer(1,2,3,4)
    
    val unit = list.update(0,5)     //改变自身,不会产生新集合
    println(list)                   //ListBuffer(5, 2, 3, 4)
    val ints = list.updated(0,6)    //会产生新集合,以前的集合不变
    println(list)                   //ListBuffer(5, 2, 3, 4)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    其他的操作与数组相同

    相互转化

    val list1: Seq[Int] = list.toList
    val buffer: mutable.Seq[Int] = list1.toBuffer
    
    • 1
    • 2

    二 set集合

    set集合特性:数据无序,不可重复

    val set = Set(1,2,3,4,1,2,3,4)

    1 不可变set

    常用操作同List,Array

    val set = Set(1,2,3,4,1,2,3,4)

    2 可变set

    val set = mutable.Set(1,2,3,4)

    set没有append、insert方法,因为它没有最后一个元素,没有位置的概念

    update方法用于更新set集合

    val set = mutable.Set(1,2,3,4)
    set.add(5)
    println(set)    //Set(1, 5, 2, 3, 4)
    set.update(5,true)
    println(set)    //Set(1, 5, 2, 3, 4)
    set.update(5,false)
    println(set)    //Set(1, 2, 3, 4)
    set.update(6,true)
    println(set)    //Set(1, 2, 6, 3, 4)
    set.remove(6)
    println(set)    //Set(1, 2, 3, 4)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    常用操作同List,Array

    三 Map集合

    Map集合特性:数据无序,k不能重复,v可以重复。当k相同的时候,v会被覆盖

    map以k-v键值对的方式存储数据

    scala中的k-v键值对十分特殊

    1 不可变Map

    默认情况下为不可变Map

    val map = Map(
      "a" -> 1,"b" -> 1,"c" -> 1,"d" -> 1
    )
    println(map)
    
    • 1
    • 2
    • 3
    • 4

    其他方法同

    2 可变Map

    val map = mutable.Map(
      "a" -> 1,"b" -> 1,"c" -> 1,"d" -> 1
    )
    map.put("e",2)
    println(map)    //Map(e -> 2, b -> 1, d -> 1, a -> 1, c -> 1)
    map.update("a",11)
    println(map)    //Map(e -> 2, b -> 1, d -> 1, a -> 11, c -> 1)
    map.remove("c")
    println(map)    //Map(e -> 2, b -> 1, d -> 1, a -> 11)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    java从HashMap中获取一个不存在的key,会返回null,因为HashMap允许存放空键空值

    空指针问题

    scala为了解决空指针问题专门设计了Option类型

    Option:选项,只有两个对象,Some和None

    val map = mutable.Map(
      "a" -> 1,"b" -> 1,"c" -> 1,"d" -> 1
    )
    val maybeInt: Option[Int] = map.get("a")
    val maybeInt1: Option[Int] = map.get("aaa")
    
    • 1
    • 2
    • 3
    • 4
    • 5

    当Some调用get方法会返回值,当None调用get会返回NoSuchElementException异常

    if( maybeInt.isEmpty){
      println("没有对应key的值")
    }else{
      println("对应key的值为" + maybeInt.get)    //对应key的值为1
    }
    if( maybeInt1.isEmpty){
      println("没有对应key的值" + maybeInt1.get)  //NoSuchElementException
    }else{
      println("对应key的值为" + maybeInt1.get)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    当取不到值的时候给一个默认值

    if( maybeInt1.isEmpty){
      println("没有对应key的值,提供的默认值为:" + maybeInt1.getOrElse(0))
      //没有对应key的值,提供的默认值为:0
    }else{
      println("对应key的值为" + maybeInt1.get)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    避免使用判断

    println(maybeInt.getOrElse(0))    //1
    println(maybeInt1.getOrElse(0))   //0
    
    • 1
    • 2

    简化

    println(maybeInt.getOrElse(0))    //1
    println(maybeInt1.getOrElse(0))   //0
    
    • 1
    • 2

    四 元组Tuple

    scala可以将无关的元素组合在一起形成一个整体进行访问,这个整体结构称之为元组的组合,简称元组

    -Tuple

    因为元组中的数据没有关系,所以只能通过顺序号进行访问

    Tuple是一个集合对象,也有对应的类型(Int,String,Int)

    val t : (Int,String,Int) = (1,"张三",20)
    println(t._1)
    println(t._2)
    println(t._3)
    println(t)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    scala中元组也有对应的类型(不常用)

    val t1 :Tuple3[Int,String,Int] = (1,"zhangsan",20)
    
    • 1

    Tuple类型最多存放元素的数量为22个,但是类型没有约束,也可以存放Tuple,List,Map等。

    1 对偶元组

    如果元组中的元素只有两个,称之为对偶元组,也可以叫做键值对

    val kv = (1,"zhangsan")
    //val tuple = 1 -> "zhangsan"
    
    • 1
    • 2

    因此map可以这样定义

    val map = Map(
      ("a",1),("b",2),("c",1)
    )
    println(map)    //Map(a -> 1, b -> 2, c -> 1)
    
    • 1
    • 2
    • 3
    • 4

    遍历map

    // a=1
    // b=2
    // c=1
    map.foreach(
      t => {
        println(t._1 + "=" + t._2)
      }
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    将map转换为有序的list

    val list: Seq[(String, Int)] = map.toList
    
    • 1

    改写wordcount第四步

    val wordCount: Map[String, Int] = wordGroup.map(
          kv => {
            val k = kv._1
            val v = kv._2
            (k,v.size)
          }
        )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    五 WordCount

    1 wordcount扩展

    修改原文件中内容样式为(“hello scala”,4),表示hello scala字符串出现4次

    思路一:将(“hello scala”,4)转换成(“hello scala hello scala hello scala hello scala”),再进行处理

    def main(args: Array[String]): Unit = {
        val list = List(
          ("hello scala",4),
          ("hello word",2)
        )
        val list1 = list.map(
          t => {
            val line = t._1
            val cnt = t._2
            (line + " ") * cnt
          }
        )
        println(list1)
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    思路二:将(“hello scala”,4)转换成(“hello”,4),(“scala”,4),再进行相同单词相加

    val list2 = list.flatMap(
      t => {
        val line = t._1
        val cnt = t._2
        val dates = line.split(" ")
        dates.map(
          word => {
            (word, cnt)
          }
        )
      }
    )
    println(list2)  //List((hello,4), (scala,4), (hello,2), (word,2))
    
    val groupData: Map[String, List[(String, Int)]] = list2.groupBy(_._1)
    println(groupData)
    
    /**
     * Map(
     *  scala -> List((scala,4)),
     *  word -> List((word,2)),
     *  hello -> List((hello,4), (hello,2))
     *  )
     */
    val groupData1 = groupData.mapValues(
      list => {
        list.map(_._2).sum
      }
    )
    println(groupData1) //Map(scala -> 4, word -> 2, hello -> 6)
    
    • 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

    2 wordcount练习

    完成不同省份的商品点击排行

    word(省份-商品)–count(1)

    (1)准备数据

    val datas = List(
          ("zhangsan", "河北", "鞋"),
          ("lisi", "河北", "衣服"),
          ("wangwu", "河北", "鞋"),
          ("zhangsan", "河南", "鞋"),
          ("lisi", "河南", "衣服"),
          ("wangwu", "河南", "鞋"),
          ("zhangsan", "河南", "鞋"),
          ("lisi", "河北", "衣服"),
          ("wangwu", "河北", "鞋"),
          ("zhangsan", "河北", "鞋"),
          ("lisi", "河北", "衣服"),
          ("wangwu", "河北", "帽子"),
          ("zhangsan", "河南", "鞋"),
          ("lisi", "河南", "衣服"),
          ("wangwu", "河南", "帽子"),
          ("zhangsan", "河南", "鞋"),
          ("lisi", "河北", "衣服"),
          ("wangwu", "河北", "帽子"),
          ("lisi", "河北", "衣服"),
          ("wangwu", "河北", "电脑"),
          ("zhangsan", "河南", "鞋"),
          ("lisi", "河南", "衣服"),
          ("wangwu", "河南", "电脑"),
          ("zhangsan", "河南", "电脑"),
          ("lisi", "河北", "衣服"),
          ("wangwu", "河北", "帽子")
        )
    
    • 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

    (2)将原始数据进行结构转换

    (人,省份,商品)=>(省份-商品,1)

    val mapDatas = datas.map(
          t => {
            (t._2 + "-" + t._3, 1)
          }
        )
    
    /**
         * List(
         * (河北-鞋,1), (河北-衣服,1), (河北-鞋,1), 
         * (河南-鞋,1), (河南-衣服,1), (河南-鞋,1), 
         * (河南-鞋,1), (河北-衣服,1), (河北-鞋,1), 
         * (河北-鞋,1), (河北-衣服,1), (河北-帽子,1), 
         * (河南-鞋,1), (河南-衣服,1), (河南-帽子,1), 
         * (河南-鞋,1), (河北-衣服,1), (河北-帽子,1), 
         * (河北-衣服,1), (河北-电脑,1), (河南-鞋,1), 
         * (河南-衣服,1), (河南-电脑,1), (河南-电脑,1), 
         * (河北-衣服,1), (河北-帽子,1))
         */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    (3)将转换结构后的数据进行分组

    val groupDatas: Map[String, List[(String, Int)]] = mapDatas.groupBy(_._1)
    
    /**
         * Map(
         * 河南-衣服 -> List((河南-衣服,1), (河南-衣服,1), (河南-衣服,1)), 
         * 河北-衣服 -> List((河北-衣服,1), (河北-衣服,1), (河北-衣服,1), (河北-衣服,1), (河北-衣服,1), (河北-衣服,1)), 
         * 河南-帽子 -> List((河南-帽子,1)), 
         * 河北-鞋 -> List((河北-鞋,1), (河北-鞋,1), (河北-鞋,1), (河北-鞋,1)), 
         * 河南-电脑 -> List((河南-电脑,1), (河南-电脑,1)), 
         * 河南-鞋 -> List((河南-鞋,1), (河南-鞋,1), (河南-鞋,1), (河南-鞋,1), (河南-鞋,1), (河南-鞋,1)), 
         * 河北-电脑 -> List((河北-电脑,1)), 
         * 河北-帽子 -> List((河北-帽子,1), (河北-帽子,1), (河北-帽子,1)))
         */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    (4)将分组后的数据进行统计聚合

     val cntDatas: Map[String, Int] = groupDatas.mapValues(
          list => list.size
        )
        println(cntDatas)
        /**
         * Map(
         * 河南-衣服 -> 3, 
         * 河北-衣服 -> 6, 
         * 河南-帽子 -> 1, 
         * 河北-鞋 -> 4, 
         * 河南-电脑 -> 2, 
         * 河南-鞋 -> 6, 
         * 河北-电脑 -> 1, 
         * 河北-帽子 -> 3)
         */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    (5)将聚合的结果进行结构转换

    将相同省份的数据准备放在一起

    (省份-商品,count)=>(省份,(商品,count))

    val mapDatas1 = cntDatas.map(
      kv => {
        val k = kv._1
        val cnt = kv._2
        val ks = k.split("-")
        (ks(0), (ks(1), cnt))
      }
    )
    println(mapDatas1)
    /**
     * Map(河南 -> (鞋,6), 河北 -> (帽子,3))
     */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    改变了K的结构,K相同,会进行覆盖,所以不能使用map集合

     val mapDatas1 = cntDatas.toList.map(
          kv => {
            val k = kv._1
            val cnt = kv._2
            val ks = k.split("-")
            (ks(0), (ks(1), cnt))
          }
        )
        /**
         * List(
           * (河南,(衣服,3)), 
           * (河北,(衣服,6)), 
           * (河南,(帽子,1)), 
           * (河北,(鞋,4)), 
           * (河南,(电脑,2)), 
           * (河南,(鞋,6)), 
           * (河北,(电脑,1)), 
           * (河北,(帽子,3))
           * )
         */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    (6)将转换后的结果按照省份进行分组

    val groupDatas1 = mapDatas.groupBy(_._1)
    /**
     * Map(
     * 河南 -> 
     *    List((河南,(衣服,3)), (河南,(帽子,1)), (河南,(电脑,2)), (河南,(鞋,6))), 
     * 河北 -> 
     *    List((河北,(衣服,6)), (河北,(鞋,4)), (河北,(电脑,1)), (河北,(帽子,3))))
     */
    val groupDatas1 = mapDatas1.groupBy(_._1).mapValues(
      list => list.map(_._2)
    )
    println(groupDatas1)
    //Map(
    // 河南 -> List((衣服,3), (帽子,1), (电脑,2), (鞋,6)), 
    // 河北 -> List((衣服,6), (鞋,4), (电脑,1), (帽子,3))
    // )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    (7)将转换结构后的数据进行排序

    val groupDatas1 = mapDatas1.groupBy(_._1).mapValues(
      list => list.map(_._2).sortBy(_._2)(Ordering.Int.reverse)
    )
    
    • 1
    • 2
    • 3

    取前三名

    val groupDatas1 = mapDatas1.groupBy(_._1).mapValues(
      list => list.map(_._2).sortBy(_._2)(Ordering.Int.reverse).take(3)
    )
    
    • 1
    • 2
    • 3

    六 队列

    Scala也提供了队列(Queue)的数据结构,队列的特点就是先进先出。进队和出队的方法分别为enqueue和dequeue。

    def main(args: Array[String]): Unit = {
        val que = new mutable.Queue[String]()
        // 添加元素
        que.enqueue("a", "b", "c")
        val que1: mutable.Queue[String] = que += "d"
        println(que eq que1)	//true
        // 获取元素
        println(que.dequeue())	//a
        println(que.dequeue())	//b
        println(que.dequeue())	//c
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    kafka中如何保证消费数据的有序

    kafka中的topic有多个分区,每一个分区可以理解为一个队列

    分区内有序,分区间无序,指的是存储有序与无序

    消费者是以消费者组为单位进行消息的消费

    所以如何保证消费数据的有序呢

    • kafka分区存储有序:所有数据都放在一个分区内,有多个分区也只能放在一个分区
    • kafka生产有序:kafka在生产数据时,如果失败,会将消息放在队尾,重新发送,使用Deque,双端队列实现kafka生产有序

    七 并行

    以下三种概念建立在多线程的基础之上

    并行:多核,一个物理核拆分成了多个虚拟核,每个线程可以抢占一个,当多个线程抢占同一个虚拟核称为并发,反之为并行,同时执行

    并发:多个线程不按照顺序抢占CPU资源,当一个线程抢到之后,其他线程阻塞,也可以理解为交叉执行,一个线程执行一段时间

    串行:一个线程完成之后才能轮到另一个线程执行,多个线程按照顺序抢占CPU资源

    Scala为了充分使用多核CPU,提供了并行集合(有别于前面的串行集合),用于多核环境的并行计算

    def main(args: Array[String]): Unit = {
        val result1 = (0 to 100).map{x => Thread.currentThread.getName}
        val result2 = (0 to 100).par.map{x => Thread.currentThread.getName}
    
        println(result1)	//全为main线程执行
        println(result2)	//多线程执行
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    线程安全问题

    多线程并发执行时,对共享内存(堆)中的共享对象的属性进行修改,所导致的数据冲突问题,称为线程安全问题

    方法不会出现问题,因为执行方法时会进行压栈,栈内存每个线程独享

    并行是在多个核内执行,除修改第三方资源不会出现问题

    以下代码输出结果为:lisi,lisi,main方法…

    public class TestThreadSafe {
        public static void main(String[] args) {
    
            final User1 user1 = new User1();
    
            Thread t1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    user1.name = "zhangsan";
                    try {
                        Thread.sleep(1000);
                    }catch (Exception e){
    
                    }
                    System.out.println(user1.name);
                }
            });
    
            Thread t2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    user1.name = "lisi";
                    try {
                        Thread.sleep(1000);
                    }catch (Exception e){
    
                    }
                    System.out.println(user1.name);
                }
            });
    
            t1.start();
            t2.start();
            System.out.println("main方法执行完毕");
        }
    }
    class User1{
        public String name;
    }
    
    • 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

    多例不会出现线程安全问题,以下为多例

    public static void main(String[] args) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    Test();
                }
            });
            new Thread(new Runnable() {
                @Override
                public void run() {
                    Test();
                }
            });
        }
        
        public static void Test(){
            StringBuilder builder = new StringBuilder();
            for(int i = 0 ; i < 100 ; i++){
                builder.append(i);
            }
            System.out.println(builder);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    解决线程安全问题

    栈上分配,在栈上面分配对象而不是在堆上面,缺点是当对象执行完毕会被弹栈回收

    逃逸分析:方法返回对象,但对象被回收了,或者是对象来自于外部,方法执行完毕之后,也将对象弹栈

    public User test(){
        User user = new Uesr();
        user.xxx;
        user.yyy;
        return user;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    public void test(User user){
        user.xxx;
        user.yyy;
    }
    
    • 1
    • 2
    • 3
    • 4
  • 相关阅读:
    ldd--cppad--static_assert--gflags
    分布式存储之hash取余算法
    前端list.push,封装多个对象
    基于OpenTelemetry实现Java微服务调用链跟踪
    Docker部署Nginx-常用命令
    Python学习记录 包
    多跳推理真的可解释吗?10.24
    ActiViz中不规则网络数据体绘制技术介绍
    ruoyi前后端分离3.8.6版本,把用户昵称设置到Vuex中,使任何组件都能直接获取用户的昵称
    SSL知识讲解
  • 原文地址:https://blog.csdn.net/weixin_43923463/article/details/126076511