Spark---RDD的创建分类和基础操作算子详解

一、RDD的创建

原生api提供了两种创建方式,一种就是读取文件textFile,还有一种就是加载一个scala集合parallelize。当然,也可以通过transformation算子来创建的RDD。

    //创建RDD
    //加载数据,textFile(参数1,参数2),参数1可以读取本地文件也可以读取hdfs上的文件,参数2为最小分区数量,但spark有自己的判断,在允许的范围内参数2有效,否则失效
    val rdd = sc.textFile("F:\\test\\words.txt")
    //适合加载一堆小文件,wholeTextFile(参数1,参数2),参数1可以读取本地文件也可以读取hdfs上的文件,参数2为最小分区数量,最多只能开到文件数量
    val rdd1 = sc.wholeTextFile("F:\\test\\words.txt")

    //从scala集合创建
    val list = List(1,2,3,4)
    val arr = Array(1,2,3,4)
    //parallelize(参数1,参数2)参数1为集合数据,参数2是指定分区数,没有就是没有指定分区数,默认是CPU核数
    val rdd2 = sc.parallelize(list)
    //makeRDD底层调用了parallelize
    val rdd3 = sc.makeRDD(arr)

    //从其他RDD转换而来
    val rdd4 = rdd1.flatMap(_.split(" "))

二、RDD的分类及基本操作

基本上分为两类:transformation和action

1、transformation

转换算子(Transformations) (如:map, filter, groupBy, join等),Transformations操作是Lazy的,也就是说从一个RDD转换生成另一个RDD的操作不是马上执行,Spark在遇到Transformations操作时只会记录需要这样的操作,并不会去执行,需要等到有Actions操作的时候才会真正启动计算过程进行计算。
1、map算子
对RDD集合中的每一个元素,都作用一次该func匿名函数,之后返回值为生成元素构成的一个新的RDD。

	 //map映射
    val rdd = sc.parallelize(1 to 7)
    //将每一个元素扩大10倍
    val res = rdd.map(_*10)
    //打印输出
    println(res.collect().toBuffer)

2.flatMap算子
集合中的每一个元素,都要作用func匿名函数,返回0到多个新的元素,这些新的元素共同构成一个新的RDD。是一个one-to-many的操作

	//flatMap=map+flatten
    val list = List(
      "jia jing kan kan kan",
      "gao di di di di",
      "zhan yuan qi qi"
    )
    //将集合转换为RDD
    val rdd = sc.parallelize(list)
    //按照指定分隔符进行切分
    val res = rdd.flatMap(_.split(" "))
    //将结果输出
    res.foreach(print)//zhanyuanqiqijiajingkankankangaodidididi

3.mapPartitions算子
mapPartitions(p: Iterator[A] => Iterator[B])一次性处理一个partition分区中的数据。执行性能要高于map,但是其一次性将一个分区的数据加载到执行内存空间,如果该分区数据集比较大,存在OOM的风险。

	//mapPartitions:一次操作一个分区的数据
    val rdd = sc.parallelize(Array(1,2,3,4,5),3)
    //一次操作一个分区的数据
    val res = rdd.mapPartitions(x=>Iterator(x.mkString("-")))
    res.foreach(println)
    /*1
    2-3
    4-5*/

4、mapPartitionsWithIndex算子
mapPartitionsWithIndex((index, p: Iterator[A] => Iterator[B])),该操作比mapPartitions多了一个index,代表就是后面p所对应的分区编号。

	//mapPartitionsWithIndex:查看每个分区当中都保存了哪些元素
    val rdd = sc.parallelize(1 to 16,2)
    //查看每个分区当中都保存了哪些元素
    val res = rdd.mapPartitionsWithIndex((index,item)=>Iterator(index+":"+item.mkString(",")))
    res.foreach(println)
    /*1:9-10-11-12-13-14-15-16
	0:1-2-3-4-5-6-7-8*/

5、sample算子
sample(withReplacement, fraction, seed):随机抽样算子,去代替全量研究会出现类似数据倾斜(dataSkew)等问题,无法进行全量研究,只能用样本去评估整体。
withReplacement:Boolean :有放回的抽样和无放回的抽样
fraction:Double:样本空间占整体数据量的比例,大小在[0, 1],比如0.2, 0.65
seed:Long:是一个随机数的种子,有默认值,通常不需要传参

	//sample:随机抽样算子,样品的预期大小个数不确定
    val rdd = sc.parallelize(1 to 10)
    //随机抽取样本占总体的0.5,有放回,会有重复
    val res = rdd.sample(true,0.5)
    println(res.collect().toBuffer)//ArrayBuffer(3, 8, 10, 10)
    //无放回,不会有重复
    val res1 = rdd.sample(false,0.8)
    println(res1.collect().toBuffer)//ArrayBuffer(2, 3, 4, 5, 6, 7, 8, 9)

	 //takeSample精确抽样,参数2为样本大小,确定抽几个
    val rdd = sc.parallelize(1 to 10)
    val res = rdd.takeSample(false,7)
    println(res.toBuffer)//ArrayBuffer(2, 3, 9, 8, 10, 6, 4)

6、union算子
rdd1.union(rdd2)
相当于sql中的union all,进行两个rdd数据间的联合,需要说明一点是,rdd1如果有N个分区,rdd2有M个分区,那么union之后的分区个数就为N+M。

 	//union :整合两个RDD当中的元素,并且整合分区数
    val rdd1= sc.parallelize(1 to 5,3)
    val rdd2= sc.parallelize(3 to 7,2)
    rdd1.mapPartitionsWithIndex((index,item)=>Iterator(index+":"+item.mkString(","))).foreach(println)
    /*0:1
    2:4,5
	1:2,3*/
   	rdd2.mapPartitionsWithIndex((index,item)=>Iterator(index+":"+item.mkString(","))).foreach(println)
   	/*0:3,4
	1:5,6,7*/
    val res = rdd1.union(rdd2)
    //查看有多少元素
    res.mapPartitionsWithIndex((index,item)=>Iterator(index+":"+item.mkString(","))).foreach(println)
    /*0:1
	1:2,3
	2:4,5
	4:5,6,7
	3:3,4*/
    println(res.collect().toBuffer)//ArrayBuffer(1, 2, 3, 4, 5, 3, 4, 5, 6, 7)
    //查看分区数
    println(res.getNumPartitions)//5

7、join算子

	//join:相同的key进行输出,不同的key不进行输出
    val rdd1 = sc.parallelize(Array((1,"a"),(2,"b"),(3,"c")))
    val rdd2 = sc.parallelize(Array((1,4),(2,5),(7,6)))
    //调用算子
    val res : RDD[(Int, (String, Int))]= rdd1.join(rdd2)
    println(res.collect().toBuffer) //ArrayBuffer((1,(a,4)), (2,(b,5)))

    //rightOuterJoin
    val res1 : RDD[(Int,(Option[String],Int))]= rdd1.rightOuterJoin(rdd2)
    println(res1.collect().toBuffer)//ArrayBuffer((1,(Some(a),4)), (2,(Some(b),5)), (7,(None,6)))

    //leftOuterJoin
    val res2:RDD[(Int,(String,Option[Int]))] = rdd1.leftOuterJoin(rdd2)
    println(res2.collect().toBuffer)//ArrayBuffer((1,(a,Some(4))), (2,(b,Some(5))), (3,(c,None)))

8、coalesce算子
coalesce(numPartition, shuffle=false): 分区合并的意思
numPartition:分区后的分区个数
shuffle:此次重分区是否开启shuffle,决定当前的操作是宽(true)依赖还是窄(false)依赖

	//coalesce:分区合并
    val rdd :RDD[Int]= sc.parallelize(1 to 16,4)
    println(rdd.getNumPartitions)//4
    //查看每个分区当中都保存了哪些元素
    rdd.mapPartitionsWithIndex((index,item)=>Iterator{
      index+":"+item.mkString(",")
    }).foreach(println)
    /*3:13,14,15,16
	0:1,2,3,4
	1:5,6,7,8
	2:9,10,11,12*/
    //缩减分区数,默认直接分区合并不会进行shuffle洗牌,也就是说默认只能缩减分区数不能增加
    val res:RDD[Int] = rdd.coalesce(3)
    //查看分区数
    println(res.getNumPartitions)//3
    //查看分区中都保存了那些元素
    res.mapPartitionsWithIndex((index,item)=>{
      Iterator(index+":"+item.mkString("-"))
    }).foreach(println)
    /*0:1-2-3-4
	1:5-6-7-8
	2:9-10-11-12-13-14-15-16*/
	
    //如果想要增加分区数,将shuffle改为true
    val res:RDD[Int] = rdd.coalesce(5,true)
    //查看分区数
    println(res.getNumPartitions)//5
    //查看分区中都保存了那些元素
    res.mapPartitionsWithIndex((index,item)=>{
      Iterator(index+":"+item.mkString("-"))
    }).foreach(println)
    /*0:10-13
    2:2-6-12-15
    3:3-7-16
    1:1-5-11-14
    4:4-8-9*/

9、repartition算子
repartition底层调用了coalesce(numPartitions, shuffle = true),shuffle过程默认为ture

	val rdd :RDD[Int]= sc.parallelize(1 to 16,4)
    println(rdd.getNumPartitions)
    //查看每个分区当中都保存了哪些元素
    rdd.mapPartitionsWithIndex((index,item)=>{
      Iterator(index+":"+item.mkString("-"))
    }).foreach(println)
    /*0:1-2-3-4
	3:13-14-15-16
	2:9-10-11-12
	1:5-6-7-8*/
    //调用算子
    val res = rdd.repartition(2)
    println(res.getNumPartitions)
    //查看
    res.mapPartitionsWithIndex((index,item)=>Iterator(index+":"+item.mkString("-"))).foreach(println)
	/*分区不是两两合并,而是重新洗牌分为两个分区
	1:2-4-6-8-10-12-14-16
	0:1-3-5-7-9-11-13-15*/

10、sortBy算子
sortBy(func,[ascending], [numTasks])
ascending:true为升序,false为降序
numTasks:分区数

	//sortBy
    val rdd:RDD[(String,Int)] = sc.parallelize(List(("a",1),("b",8),("c",6)),3)
    println(rdd.getNumPartitions)//3
    //按照第二个字段进行排序
    val res = rdd.sortBy(_._2,false,2)
    println(res.collect().toBuffer)//ArrayBuffer((b,8), (c,6), (a,1))
    println(res.getNumPartitions)//2

11、sortByKey([ascending], [numTasks])算子
在一个(K,V)的RDD上调用,K必须实现Ordered接口,返回一个按照key进行排序的(K,V)的RDD

    val rdd:RDD[(String,Int)] = sc.parallelize(List(("a",1),("b",8),("c",6)),4)
    val res = rdd.sortByKey(true,3)
    println(res.collect().toBuffer)//ArrayBuffer((a,1), (b,8), (c,6))
    println(res.getNumPartitions)//3

12、groupBy和 groupByKey
groupByKey相比较reduceByKey而言,没有本地预聚合操作,显然其效率并没有reduceByKey效率高,在使用的时候如果可以,尽量使用reduceByKey等去代替groupByKey。

case class Student(id:Int,name:String,province:String)
    val stuRDD = sc.parallelize(List(
      Student(1, "张三", "安徽"),
      Student(2, "李梦", "山东"),
      Student(3, "王五", "甘肃"),
      Student(4, "周七", "甘肃"),
      Student(5, "Lucy", "黑吉辽"),
      Student(10086, "魏八", "黑吉辽")
    ))
    //按照省份进行排序
    //groupBy就是对不是kv键值对的数据进行分组
    val res = stuRDD.groupBy(stu=>stu.province)
    println(res.collect().toBuffer)//ArrayBuffer((安徽,CompactBuffer(Student(1,张三,安徽))), (黑吉辽,CompactBuffer(Student(5,Lucy,黑吉辽), Student(10086,魏八,黑吉辽))), (甘肃,CompactBuffer(Student(3,王五,甘肃), Student(4,周七,甘肃))), (山东,CompactBuffer(Student(2,李梦,山东))))

    //groupByKey针对的是kv键值对的数据,numPartition指的是分组之后的分区个数
    val stures=stuRDD.map(stu=>(stu.province,stu))
    //调用算子
    val result = stures.groupByKey()
    println(result.collect().toBuffer)//ArrayBuffer((安徽,CompactBuffer(Student(1,张三,安徽))), (黑吉辽,CompactBuffer(Student(5,Lucy,黑吉辽), Student(10086,魏八,黑吉辽))), (甘肃,CompactBuffer(Student(3,王五,甘肃), Student(4,周七,甘肃))), (山东,CompactBuffer(Student(2,李梦,山东))))

13、reduceByKey算子

	//reduceByKey,会进行预聚合,效率比groupbykey高,聚合的是key对应的value值
    case class Student(id: Int, name:String, province: String)
    val stuRDD = sc.parallelize(List(
      Student(1, "张三", "安徽"),
      Student(2, "李梦", "山东"),
      Student(3, "王五", "甘肃"),
      Student(4, "周七", "甘肃"),
      Student(5, "Lucy", "黑吉辽"),
      Student(10086, "魏八", "黑吉辽")
    ))
    //按照相同的省份进行聚合
    val res = stuRDD.map(stu=>(stu.province,1))
    val count = res.reduceByKey(_+_)
    println(count.collect().toBuffer)//ArrayBuffer((安徽,1), (黑吉辽,2), (甘肃,2), (山东,1))

14、foldByKey算子

	//foldByKey与reduceByKey的区别就是多了一个初始值
    case class Student(id: Int, name:String, province: String)
    val stuRDD = sc.parallelize(List(
      Student(1, "张三", "安徽"),
      Student(3, "王五", "甘肃"),
      Student(5, "Lucy", "黑吉辽"),
      Student(2, "李梦", "山东"),
      Student(4, "周七", "甘肃"),
      Student(10086, "魏八", "黑吉辽")
    ), 2)
    //查看每个分区当中都保存了哪些元素
    stuRDD.mapPartitionsWithIndex((index,item)=>Iterator(index+":"+item.mkString(","))).foreach(println)
    /*1:Student(2,李梦,山东),Student(4,周七,甘肃),Student(10086,魏八,黑吉辽)
	0:Student(1,张三,安徽),Student(3,王五,甘肃),Student(5,Lucy,黑吉辽)*/
    //调用算子进行聚合
    val res = stuRDD.map(stu=>(stu.province,1))
    //初始化的值针对的是每个分区当中,相同key下只有一个初始值
    val sount = res.foldByKey(1)(_+_)
    println(sount.collect().toBuffer)//ArrayBuffer((安徽,2), (甘肃,4), (山东,2), (黑吉辽,4))

15、combineByKey算子

 	//combineByKey,reduceByKey和groupByKey底层都是通过combineByKeyWithClassTag来实现的
    val array = sc.parallelize(Array(
      "hello you",
      "hello me",
      "hello you",
      "hello you",
      "hello me",
      "hello you"
    ), 5)
    //按照分隔符进行切分
    val word = array.flatMap(line=>line.split(" "))
    //每个单词记为一次
    val word1 = word.map((_,1))
    //调用算子
    //第一个参数是初始化,第二个参数是小聚合,分区之内聚合,第三个参数是大聚合,分区之间聚合
    val res = word1.combineByKey(createCombiner,mergeValue,mergeCombiner)
    println(res.collect().toBuffer)//ArrayBuffer((me,2), (hello,6), (you,4))

    //例子
    val rdd: RDD[Int] = sc.parallelize(List(1,1,1,2,2,2,2,2,3,3,3,3,3,4,4,4,4,4,4,4),2)
    //将数据转为key,value形式
    val rdd1: RDD[(Int, Int)] = rdd.map((_,1))
    //查看每个分区当中都保存了哪些元素
    rdd1.mapPartitionsWithIndex((index,item)=>Iterator(index+":"+item.mkString(","))).foreach(println)
    //调用算子
    //初始化:针对每个分区当中,相同key下第一条元素进行初始化
    val result = rdd1.combineByKey(-_,(a:Int,b:Int)=>a+b,(a:Int,b:Int)=>a+b)
    result.foreach(println)

  def createCombiner(num:Int)={
    num
  }
  def mergeValue(sum:Int,num:Int)={
    sum+num
  }
  def mergeCombiner(sum:Int,num:Int)={
    sum+num
  }

16、aggregateByKey算子
combineByKey和aggregateByKey的区别就相当于reduceByKey和foldByKey。

val array = sc.parallelize(Array(
      "hello you",
      "hello me",
      "hello you",
      "hello you",
      "hello me",
      "hello you"
    ), 2)
    //切分并将每个单词记为1次
    val wordAndOne: RDD[(String, Int)] = array.flatMap(_.split(" ")).map((_,1))
    //查看每个分区当中都保存了哪些元素
    wordAndOne.mapPartitionsWithIndex((index,item)=>Iterator(index+":"+item.mkString(","))).foreach(println)
    /*1:(hello,1),(you,1),(hello,1),(me,1),(hello,1),(you,1)
	0:(hello,1),(you,1),(hello,1),(me,1),(hello,1),(you,1)*/
    //调用算子进行聚合
    //第一个参数是分区之内进行聚合,也就是小聚合
    //第二个参数是分区之间进行聚合,也就是大聚合
    //初始化的值针对的是每个分区当中,相同key下只有一个初始值
    val res = wordAndOne.aggregateByKey(1)(_+_,_+_)
    println(res.collect().toBuffer)//ArrayBuffer((hello,8), (me,4), (you,6))

2、action

操作/行动(Actions)算子 (如:count, collect, foreach等),Actions操作会返回结果或把RDD数据写到存储系统中。Actions是触发Spark启动计算的动因。
1、foreach算子
foreach主要功能,就是用来遍历RDD中的每一条纪录,其实现就是将map或者flatMap中的返回值变为Unit即可,即foreach(A => Unit)
2、count算子
统计该rdd中元素的个数
3、collect算子
该算子的含义就是将分布在集群中的各个partition中的数据拉回到driver中,进行统一的处理;但是这个算子有很大的风险存在,第一,driver内存压力很大,第二数据在网络中大规模的传输,效率很低;所以一般不建议使用,如果非要用,请先执行filter。
4、take&first算子
返回该rdd中的前N个元素,如果该rdd的数据是有序的,那么take(n)就是TopN;而first是take(n)中比较特殊的一个take(1)。
5、takeOrdered(n)
返回前几个的排序
6、reduce算子
reduce是一个action操作,reduceByKey是一个transformation。reduce对一个rdd执行聚合操作,并返回结果,结果是一个值。
7、countByKey算子
统计key出现的次数。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/580108.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

TCP协议的可靠性详解

由于网络部分内容相对于来说比较多,本文只针对TCP协议来进行讲解,后面UDP/Http/Https的讲解有可能会单独出一篇文章。 udp协议相对来来说会比tcp简单不少,同时面试频率tcp也会高上不少。 同时本博客也仅仅只是做出部分讲解&#xff0c…

代码随想录算法训练营Day11 | 20.有效的括号、1047.删除字符串中的所有相邻重复项、150.逆波兰表达式求值

20.有效的括号 题目:给定一个只包括 (,),{,},[,] 的字符串 s ,判断字符串是否有效。 有效字符串需满足: 左括号必须用相同类型的右括号闭合。左括号必须以正确的顺序闭合。每个右…

B站美化插件,支持自定义,太酷辣~

大公司的软件和网站通常具有优雅的默认界面设计。 以国内二次元聚集地B站为例,可以说它的UI设计非常吸引人。与其他视频网站繁复的设计相比,B站的界面设计可以说是遥遥领先 然而,总有些人对默认的用户界面感到不满意,他们渴望尝试…

数字逻辑电路基础-有限状态机

文章目录 一、有限状态机基本结构二、verilog写一个基础有限状态机(moore型状态机)三、完整代码一、有限状态机基本结构 本文主要介绍使用verilog编写有限状态机FSM(finite state machine),它主要由三部分组成,下一状态逻辑电路,当前状态时序逻辑电路和输出逻辑电路。 有…

Spring Security认证流程分析

我自己的思路 先分别实现 userdetailsService,userDetails,passwordEncoder三个接口, 然后就是写登录逻辑 本文章用的是继承UsernamePasswordAuthenticationFilter这个接口 因为这个框架默认登录逻辑是在这里面的,里面的核心就是…

【Vue3+Tres 三维开发】01-HelloWord

预览 什么是TRESJS 简单的说,就是基于THREEJS封装的能在vue3中使用的一个组件,可以像使用组件的方式去创建场景和模型。优势就是可以快速创建场景和要素的添加,并且能很明确知道创景中的要素构成和结构。 项目创建 npx create-vite@latest # 选择 vue typescript安装依赖…

广西民族师范学院领导一行莅临泰迪智能科技开展“访企拓岗”活动

4月25日,广西民族师范学院数理与电子信息工程学院党委副书记、纪委书记主战河,数理与电子信息工程学院副院长陆克盛、专任教师韦吉栋、黎运宇、黄恒秋、王贵富莅临广东泰迪智能科技股份有限公司就深入实施“访企拓岗”、强化校企合作、促进毕业生充分就业…

搞定Microchip MPU的U-boot源码仿真调试

文章目录 准备工作编译at91bootstrap和U-boot源码下载并编译at91bootstrap源码下载并编译u-boot源码 使用Eclipse导入U-boot源码并进行配置cfg配置文件内容仿真调试视频教程 在嵌入式Linux开发中,免不了接触到U-boot,随着U-boot功能越来越强大&#xff0…

2024年4月26日力扣每日一题(1146)

2024年4月26日力扣每日一题(1146) 前言 ​ 这道题在做的时候感觉很简单,题意很容易理解,但直接去做直接干爆内存,参考了一下灵神的代码,豁然开朗,觉得这道题很有意思,便想着写篇博…

【YOLO改进】换遍IoU损失函数之GIoU Loss(基于MMYOLO)

GIoU损失函数 论文链接:https://arxiv.org/pdf/1902.09630 GIoU(Generalized Intersection over Union)损失函数是一种用于改善目标检测模型中边界框回归的方法。它是基于传统的IoU(交并比)损失的一个改进版本,解决了…

node.js的安装与配置

Node.js 是一种基于 JavaScript 编程语言的服务器端平台,它可以让你在浏览器之外运行 JavaScript 代码。以下是 Node.js 的安装步骤: 下载 Node.js: 访问 Node.js官网。根据你的操作系统选择合适的版本下载。 运行安装文件: 在下载…

计算机视觉——使用OpenCV GrabCut算法从图像中移除背景

GrabCut算法 GrabCut算法是一种用于图像前景提取的技术,由Carsten Rother、Vladimir Kolmogorov和Andrew Blake三位来自英国剑桥微软研究院的研究人员共同开发。该技术的核心目标是在用户进行最少交互操作的情况下,自动从图像中分割出前景对象。 在Gra…

直流有刷电机入门

文章目录 123455.25.3 1 2 电刷 材质是 石墨 3 130马达 就几毛钱 几块钱这学的就是减速电机P MAX一定 pf*v 降低速度 扭矩就会大 4 还有空载电流 过大负载 时 有堵转电流 (可分析电流 来看电机工作状态)RPM 转每分钟 5 5.2 这的线圈 是简化后的转子绕组…

Ubuntu终端常用指令

cat cat 读取文件的内容 1、ls 一、 1、ll 显示当前目录下文件的详细信息,包括读写权限,文件大小,文件生成日期等(若想按照更改的时间先后排序,则需加-t参数,按时间降序(最新修改的时间排在最前)执行: $ ll -t, 按时间升序执行: $ ll -t | tac): ll 2、查看当前所处路径(完整…

服务器数据恢复—服务器重装系统导致XFS分区丢失的数据恢复案例

服务器数据恢复环境: 一台服务器MD1200磁盘柜,通过raid卡将15块磁盘组建成一组raid5磁盘阵列。raid5阵列分配了2个lun,操作系统层面对lun进行分区:1个分区采用LVM扩容方式加入到了root_lv中,其余分区格式化为XFS文件系…

大数据时代,保护个人隐私小Tips Get 起来!

随着大数据时代的到来,我们的隐私正处于越来越易被侵犯的风险中。在各种社交媒体和信息共享平台上,我们需要输入各种个人信息,而这些信息可能被不法分子盗取,甚至被用来进行欺诈行为。在如今的大数据时代,保护个人隐私…

元宇宙中的DAPP:你了解多少?

元宇宙是什么?这是一个在当今科技圈炙手可热的话题。而在元宇宙中,DAPP起着至关重要的角色,它作为连接现实世界与虚拟世界的桥梁,为未来的数字世界开启了一个全新的篇章。 一、元宇宙:一个虚拟的数字世界 元宇宙是一…

【JavaWeb】Day51.Mybatis动态SQL(一)

什么是动态SQL 在页面原型中,列表上方的条件是动态的,是可以不传递的,也可以只传递其中的1个或者2个或者全部。 而在我们刚才编写的SQL语句中,我们会看到,我们将三个条件直接写死了。 如果页面只传递了参数姓名name 字…

【麒麟(Linux)系统远程连接到windows系统并进行文件传输】

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言使用步骤总结 前言 一般来说,windows自带远程桌面,使用的RDP协议,Linux上支持RDP协议的软件很多,常用的是Remmi…

Java 网络编程之TCP(五):分析服务端注册OP_WRITE写数据的各种场景(二)

接上文 二、注册OP_WRITE写数据 服务端代码: import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectableChannel; import java.nio.channels.SelectionKey; import java.nio.channels.S…