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)

在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)

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

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

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

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

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)

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)

其他的操作与数组相同

相互转化

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

二 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)

常用操作同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)

其他方法同

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)

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")

当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)
}

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

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

避免使用判断

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

简化

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

四 元组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)

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

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

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

1 对偶元组

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

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

因此map可以这样定义

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

遍历map

// a=1
// b=2
// c=1
map.foreach(
  t => {
    println(t._1 + "=" + t._2)
  }
)

将map转换为有序的list

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

改写wordcount第四步

val wordCount: Map[String, Int] = wordGroup.map(
      kv => {
        val k = kv._1
        val v = kv._2
        (k,v.size)
      }
    )

五 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)
  }

思路二:将(“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)

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", "河北", "帽子")
    )

(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))
     */

(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)))
     */

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

 val cntDatas: Map[String, Int] = groupDatas.mapValues(
      list => list.size
    )
    println(cntDatas)
    /**
     * Map(
     * 河南-衣服 -> 3, 
     * 河北-衣服 -> 6, 
     * 河南-帽子 -> 1, 
     * 河北-鞋 -> 4, 
     * 河南-电脑 -> 2, 
     * 河南-鞋 -> 6, 
     * 河北-电脑 -> 1, 
     * 河北-帽子 -> 3)
     */

(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))
 */

改变了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))
       * )
     */

(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))
// )

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

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

取前三名

val groupDatas1 = mapDatas1.groupBy(_._1).mapValues(
  list => list.map(_._2).sortBy(_._2)(Ordering.Int.reverse).take(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
}

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)	//多线程执行
}

线程安全问题

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

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

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

以下代码输出结果为: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;
}

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

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);
    }
}

解决线程安全问题

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

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

public User test(){
    User user = new Uesr();
    user.xxx;
    user.yyy;
    return user;
}
public void test(User user){
    user.xxx;
    user.yyy;
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

OneTenTwo76

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值