Scala学习第三天

Scala第三天学习
    柯里化函数
        柯里化(Currying)指的是将原来接受两个参数的函数变成新的接受一个参数的函数的过程。
新的函数返回一个以原有第二个参数为参数的函数。可以理解为高阶函数的简化。和我们文档中上面的函数的返回类是函数的例子进行比较。
        fn(a,b,c,d)=>fn(a)(b)(c)(d);
fn(a,b,c,d)=>fn(a,b)(c)(d);
fn(a,b,c,d)=>fn(a)(b,c,d);
    Scala集合深化
        scala.collection 中的所有集合。这些都是高级抽象类或特征,通常具有可变和不可变的实现。
            
        scala.collection.immutable中的所有集合。不变的
不会被改变。但仍可以进行模拟添加,移除或更新操作。但是这些操作将在每一种情况下都返回一个新的集合,同时使原来的集合不发生改变。
            
        scala.collection.mutable中的所有集合。可变的
可以在适当的地方被更新或扩展,可以修改,添加,移除一个集合的元素。
            
        Trait Traversable
            Traversable(遍历)是容器(collection)类的最高级别特性(trait),它唯一的抽象操作是foreach:
            实现Traversable的容器(collection)类仅仅需要定义与之相关的方法,其他所有方法可都可以从Traversable中继承。
        Trait Iterable
            容器(collection)结构的上层还有另一个trait。这个trait里所有方法的定义都基于一个抽象方法,
迭代器(iterator,会逐一的产生集合的所有元素)。
foreach是Traversable所有操作的基础,所以它的性能表现很关键。
            Iterable有两个方法返回迭代器:grouped和sliding。这些迭代器返回的不是单个元素,而是原容器(collection)元素的全部子序列。
grouped方法返回元素的增量分块
sliding方法生成一个滑动元素的窗口。
        Trait Seq
            Seq trait用于表示序列。序列,指的是一类具有一定长度的可迭代访问的对象,其中每个元素均带有一个从0开始计数的固定索引位置。
            常见操作
索引和长度的操作 (apply、isDefinedAt、length、indices、lengthCompare)
索引检索操作(indexOf、lastIndexOf、indexofSlice、lastIndexOfSlice、indexWhere、
lastIndexWhere、segmentLength、prefixLength)
返回等于给定值或满足某个谓词的元素的索引。
加法运算(+:,:+,padTo)
在序列的前面或者后面添加一个元素并作为新序列返回。
更新操作(updated,patch)
替换原序列的某些元素并作为一个新序列返回。
排序操作(sorted, sortWith, sortBy)
对序列元素进行排序。
反转操作(reverse, reverseIterator, reverseMap)
将序列中的元素以相反的顺序排列。
比较(startsWith, endsWith, contains, containsSlice, corresponds)
对两个序列进行比较,或者在序列中查找某个元素。
多集操作(intersect, diff, union, distinct)
对两个序列中的元素进行类似集合的操作,或者删除重复元素
            trait Seq 具有两个subtrait LinearSeq和IndexedSeq。
线性序列具有高效的 head 和 tail 操作
索引序列具有高效的apply, length, 和 (如果可变) update操作。
Vector 类提供一个在索引访问和线性访问之间有趣的折中。它同时具有高效的恒定时间的索
引开销,和恒定时间的线性访问开销。
        Trait Set
            集合是不包含重复元素的可迭代对象。
可变集合和不可变集合提供了+和++操作符来添加元素,-和--用来删除元素。但是这些操作在可变集合中通常很少使用,因为这些操作都要通过集合的拷贝来实现。更有效率的方法是+=和-=,另外还有批量操作符++=和--=。
            目前可变集合默认使用哈希表来存储集合元素,非可变集合则根据元素个数的不同,使用不同的方
式来实现。
空集用单例对象来表示。
元素个数小于等于4的集合可以使用单例对象来表达,元素作为单例对象的字段来存储。
元素超过4个,非可变集合就用哈希前缀树(hash trie)来实现。
采用这种表示方法,较小的不可变集合(元素数不超过4)往往会比可变集合更加紧凑和高
效。所以,在处理小尺寸的集合时,不妨试试不可变集合。
            集合的两个特质是 SortedSet 和 BitSet.
SortedSet 是指以特定的顺序(可以在创建集合时设置顺序)排列其元素(使用iterator或
foreach)的集合,默认表示是有序二叉树,即左子树上的元素小于所有右子树上的元素。这
样,一次简单的顺序遍历能按增序返回集合中的所有元素。Scala的类 immutable.TreeSet 使
用红黑树实现,它在维护元素顺序的同时,也会保证二叉树的平衡,即叶节点的深度差最多为
1。
BitSet是由单字或多字的紧凑位实现的非负整数的集合。其内部使用 Long 型数组来表示。第
一个 Long 元素表示的范围为0到63,第二个范围为64到127,以此类推(值为0到127的非可
变位集合通过直接将值存储到第一个或第两个 Long 字段的方式,优化掉了数组处理的消
耗)。对于每个 Long,如果有相应的值包含于集合中则它对应的位设置为1,否则该位为0。
位集合的大小取决于存储在该集合的最大整数的值的大小。假如N是为集合所要表示的最大整
数,则集合的大小就是 N/64 个长整形字,或者 N/8 个字节,再加上少量额外的状态信息字
节。当位集合包含的元素值都比较小时,它比其他的集合类型更紧凑。位集合的另一个优点是
它的 contains 方法(成员测试)、+= 运算(添加元素)、-= 运算(删除元素)都非常的高
        Trait Map
            Map是一种可迭代的键值对结构。Scala的Predef类提供了隐式转换,允许使用另一种语法:key ->value 代替(key,value)
        List
            Scala 列表类似于数组,它们所有元素的类型都相同,但是它们也有所不同:
列表是不可变的,值一旦被定义了就不能改变,
列表具有递归的结构
            构造列表的两个基本单位是 Nil 和 ::
不要忘记 Nil 是 长度为0的List 。
            Scala列表有三个基本操作:
head 返回列表第一个元素
tail 返回一个列表,包含除了第一元素之外的其他元素
isEmpty 在列表为空时返回true
            list遍历
                //遍历
list.foreach { x => println(x)}
list.foreach { println}
        Set
            Scala Set(集合)是没有重复的对象集合,所有的元素都是唯一的。
Scala 集合分为可变的和不可变的集合。
默认情况下,Scala 使用的是不可变集合,如果你想使用可变集合,需要引用
scala.collection.mutable.Set 包。
            Scala集合有三个基本操作:
head 返回集合第一个元素
tail 返回一个集合,包含除了第一元素之外的其他元素
isEmpty 在集合为空时返回true
            遍历Set
                //遍历
set1.foreach { println}
for(s <- set1){
 println(s)
}
println("*******")
             Set方法举例
                1. 交集: intersect , &
2. 差集: diff , &~
3. 子集: subsetOf
4. 最大: max
5. 最小: min
6. 转成数组: toList
7. 转成字符串: mkString
        Map
            Map(映射)是一种可迭代的键值对(key/value)结构。
所有的值都可以通过键来获取。
             创建Map
Map(1 –>"shanghai") 键 -> 值的对应关系创建
Map((1,"shanghai")) 元组的形式(key,value)
                // 空哈希表,键为字符串,值为整型
var map1:Map[Char,Int] = Map()
val map2 = Map(
 "1" -> "shanghai",
 2 -> "shanghai",
(3,"beijing")
)
            获取Map的值
map.get("1").get
map.get(100).getOrElse("no value") 此种方式如果 map 中没有对应项则赋值为 getOrElse里面的值。
                //获取值
println(map.get("1").get)
val result = map.get(8).getOrElse("no value")
println(result)
            遍历Entry
                //map遍历
for(x <- map){
 println("====key:"+x._1+",value:"+x._2)
}
map.foreach(f => {
 println("key:"+ f._1+" ,value:"+f._2)
})
            遍历key
                //遍历key
val keyIterable = map.keys
keyIterable.foreach { key => {
 println("key:"+key+", value:"+map.get(key).get)
} }
println("---------")
            遍历value
                //遍历value
val valueIterable = map.values
valueIterable.foreach { value => {
 println("value: "+ value)
} }
            合并Map
                val map1 = Map(
(1,"a"),  
(2,"b"),  
(3,"c")  
)
val map2 = Map(
(1,"aa"),
(2,"bb"),
(2,90),
(4,22),
(4,"dd")
)
map1.++(map2).foreach(println)
            Map方法举例
                filter :过滤,留下符合条件的记录
count :统计符合条件的记录数
contains : map 中是否包含某个 key
exist :符合条件的记录存在与否
                val countResult  = map.count(p => {
 p._2.equals("shanghai")
})
println(countResult)
//filter
map.filter(_._2.equals("shanghai")).foreach(println)                    //contains
println(map.contains(2))
//exist
println(map.exists(f =>{
 f._2.equals("guangzhou")
}))
            可变长Map
                import scala.collection.mutable.Map
//如果你想要添加一个原来的map中没有的value类型,要么就手动显示[String,Any]
val map =  Map[String,Any](("name", "lisi"),("money",200))
map+=("age"->18)//单个添加
map+=("age1"->18,"age2"->18,"age3"->18)//多个添加
map-=("age")//删除直接针对key值。map.remove("age")
    元组Tuple
        元组的定义:大体与列表一样,不同的是元组可以包含不同类型的元素。元组的值是通过将单个的值包含在圆括号中构成的。
        创建元组
            val tuple1 = new Tuple(1)
val tuple2 = Tuple2(1,2)
val tuple3 =(1,2,3)
取值用 ._X 可以获取元组中的值
        遍历元祖
            虽然元组不是集合,但是在遍历使用时可以当作一个集合来用。
通过 tuple.productIterator 得到迭代器,进而实现遍历。迭代器只能使用一次,下次还想遍历就需要构建一个新的 iterator 。
            //遍历
val tupleIterator = tuple22.productIterator
while(tupleIterator.hasNext){
 println(tupleIterator.next())
}
        元组的简单方法
            例如 swap , toString 方法。注意 swap 的元素翻转只针对二元组。
            //翻转,只针对二元组
println(tuple2.swap)
//toString
println(tuple3.toString())
    Spark预热之WordCount
        <dependency>
  <groupId>org.apache.spark</groupId>
  <artifactId>spark-core_2.12</artifactId>
  <version>2.4.6</version>
</dependency>
        import org.apache.spark.SparkConf
import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD
import org.apache.spark.rdd.RDD.rddToPairRDDFunctions
object WordCount {
 def main(args: Array[String]): Unit = {
  val conf = new SparkConf()
  conf.setMaster("local").setAppName("WC")
  val sc = new SparkContext(conf)
  val lines :RDD[String] = sc.textFile("./words.txt")
  val word :RDD[String]  = lines.flatMap{lines => {
   lines.split(" ")
 }}
  val pairs : RDD[(String,Int)] = word.map{ x => (x,1) }
  val result = pairs.reduceByKey{(a,b)=> {a+b}}
  //简化写法
  lines.flatMap { _.split(" ")}.map { (_,1)}.reduceByKey(_+_).foreach(println)}}
    模式匹配
        Scala 提供了强大的模式匹配机制,应用也非常广泛。
一个模式匹配包含了一系列备选项,每个都开始于关键字  case 。
每个备选项都包含了一个模式及一到多个表达式。箭头符号  => 隔开了模式和表达式。
        object HelloMatch {
 def main(args: Array[String]): Unit = {
  val tuple = Tuple6(1, 2, 3f, 4, "abc", 55d)
  val tupleIterator = tuple.productIterator
  while (tupleIterator.hasNext) {
   matchTest(tupleIterator.next())
 }
}
 /**
 * 注意点:
 * 1.模式匹配不仅可以匹配值,还可以匹配类型
 * 2.模式匹配中,如果匹配到对应的类型或值,就不再继续往下匹配
 * 3.模式匹配中,都匹配不上时,会匹配到 case _ ,相当于default
 */
 def matchTest(x: Any) = {
  x match {
   case 1 => println("result is 1")
   case 2 => println("result is 2")
   case 3 => println("result is 3")
   case 4 => println("result is 4")
   case x: Int => println("type is Int")
   case x: String => println("type is String")
   case x: Double => println("type is Double")
   case _ => println("no match")
 }
}
}
    Actor Model
        Actor Model 是用来编写并行计算或分布式系统的高层次抽象(类似 java 中的 Thread )让程序员不
必为多线程模式下共享锁而烦恼。 Actors 将状态和行为封装在一个轻量的进程/线程中,但是不和其他
Actors 分享状态,每个 Actors 有自己的世界观,当需要和其他 Actors 交互时,通过发送事件和消
息,发送是异步的,非堵塞的(fire-andforget),发送消息后不必等另外 Actors 回复,也不必暂停,每
个 Actors 有自己的消息队列,进来的消息按先来后到排列,这就有很好的并发策略和可伸缩性,可以
建立性能很好的事件驱动系统。
2.12版本后,actor彻底从scala中抽离了出来,所以我们在使用前需要引入相应的lib。
        <dependency>
  <groupId>com.typesafe.akka</groupId>
  <artifactId>akka-actor_2.12</artifactId>
  <version>2.5.9</version>
</dependency>
        Actor的特征
            ActorModel 是消息传递模型,基本特征就是消息传递
消息发送是异步的,非阻塞的
消息一旦发送成功,不能修改
Actor 之间传递时,接收消息的 actor 自己决定去检查消息, actor 不是一直等待,是异步非阻塞的
        具体写法
            Actor发送接收消息
                import akka.actor.Actor
import akka.actor.ActorSystem
import akka.actor.Props
class HelloActor extends Actor {
 override def receive: Receive = {
  case "hey" => println("hey yourself")
  case _ => println("hehe")}}
object Main extends App {
 val system = ActorSystem("HelloSystem")
 val helloActor = system.actorOf(Props[HelloActor], name = "helloActor")
 helloActor ! "hey"
 helloActor ! "good morning"}
            Actor与Actor之间通信
                import akka.actor.{Actor, ActorRef, ActorSystem, Props}
class MyActor extends Actor {
 override def receive: Receive = {
  case msg: String => {
   println(msg)
   Thread.sleep(1000)
   sender() ! "你说啥"}
  case Int => println("你竟然说数字")
  case _ => println("default")}}
class MyActor2 extends Actor {
 private val other: ActorRef = context.actorOf(Props(new MyActor),
"actor1child")
 override def receive: Receive = {
  case msg: String => {
   println(msg)
   other ! "nihao"}}}
object Test extends App {
 private val system: ActorSystem = ActorSystem("system")
 private val actor: ActorRef = system.actorOf(Props(new MyActor2), "actor1")
 actor ! "你好actor2"}
    Scala隐式参数
        隐式参数:
            隐式参数同样是编译器在找不到函数需要某种类型的参数时的一种修复机制,我们可以采用显
式的柯里化式的隐式参数申明,也可以进一步省略,采用 implicitly 方法来获取所需要的隐
式变量。
隐式参数相对比较简单,Scala中的函数申明提供了隐式参数的语法,在函数的最后的柯里化
参数
列表中可以添加隐式 implicit 关键字进行标记, 标记为 implicit 的参数在调用中可以省略
Scala编译器会从当前作用域中寻找一个相同类型的隐式变量,作为调用参数。
        -注意点
            同类型的参数的隐式值只能在作用域内出现一次,同一个作用域内不能定义多个类型一样的隐
式值。
implicit 关键字必须放在隐式参数定义的开头
一个方法只有一个参数是隐式转换参数时,那么可以直接定义 implicit 关键字修饰的参数,
调用时直接创建类型不传入参数即可。
一个方法如果有多个参数,要实现部分参数的隐式转换,必须使用柯里化这种方式,隐式关键字
出现在后面,只能出现一次。
        具体写法
             def main(args: Array[String]) {
  Param.print("jack")("hello")
  import Context._
  Param.print("jack")}
 object Context {
  implicit val ccc: String = "implicit"}
 object Param {
  def print(content: String)(implicit prefix: String) {
   println(prefix + ":" + content) }}
     Scala隐式类
        简介
            使用 implicit 关键字修饰的类就是隐式类。若一个 变量A 没有某些方法或者某些变量时,而这个 变量A 可以调用某些方法或者某些变量时
可以定义一个隐式类,隐式类中定义这些方法或者变量,隐式类中传入 A 即可。
        注意点
            隐式类必须定义在类,包对象,伴生对象中。
隐式类的构造必须只有一个参数,同一个类,包对象,伴生对象中不能出现同类型构造的隐式类。
        具体写法
            class Rabbit(s:String){
 val name = s
}
object Lesson_ImplicitClass {
 implicit class Animal(rabbit:Rabbit){
  val tp = "Animal"
  def canFly() ={
   println(rabbit.name +" can fly...")}}
 def main(args: Array[String]): Unit = {
  val rabbit = new Rabbit("rabbit")
  rabbit.canFly()
  println(rabbit.tp)}}
    Scala隐式视图
        隐式转换函数是指在同一个作用域下面,一个给定输入类型并自动转换为指定返回类型的函数,这个函数和函数名字无关,和入参名字无关,只和入参类型以及返回类型有关。注意是同一个作用域。
隐式转换为目标类型:把一种类型自动转换到另一种类型
        def main(args: Array[String]): Unit = {
  hello("str")
  hello(123)
  hi("str")
  hi(123)}
def hello(param: String): Unit = {
  println(param)}
def hi(param: String): Unit = {
  println(param)}
implicit def typeConverter(param: Int): String = "yjxxt:" + param
implicit def boolean2String(param: Boolean): String = "Boolean:" + param
        隐式转换调用类中本不存在的方法
编译器在xiaoming对象调用时发现对象上并没有learn方法,此时编译器就会在作用域范围内
查找能使其编译通过的隐式视图
找到learnView方法后,编译器通过隐式转换将对象转换成具有这个方法的对象,之后调用
learn方法
        class Person {
 def learn(name: String) = println("正在学习【" + name + "】...")
}
object Converter {
 implicit def learnView(b: Boy) = new Person
 implicit def learnView(g: Girl) = new Person}
class Boy
class Girl
object HelloImplicitView {
 def main(args: Array[String]): Unit = {
  import com.yjxxt.p0805.Converter._
  val xiaoming = new Boy
  xiaoming.learn("数学")
  val xiaofang = new Girl
  xiaofang.learn("数学")}}
    Scala隐式值
        将p变量标记为implicit,所以编译器会在方法省略隐式参数的情况下去搜索作用域内的隐式值作为
缺少参数。
如果此时你又在REPL中定义一个隐式变量,再次调用方法时就会报错
隐式转换必须满足无歧义规则,在声明隐式参数的类型是最好使用特别的或自定义的数据类型,不
要使用Int,String这些常用类型,避免碰巧匹配
        def main(args: Array[String]): Unit = {
  person("admin")
  implicit val name1 = "Harry"
  //  implicit val name2 = "Potter"
  person
}
//name为隐式参数
def person(implicit name: String) = println(name)
    隐式转换
        隐式转换是在 Scala 编译器进行类型匹配时,如果找不到合适的类型,那么隐式转换会让编译器在
作用范围内自动推导出来合适的类型。
隐式的使用方式
1.将方法或变量标记为implicit
2.将方法的参数列表标记为implicit
3.将类标记为implicit
        Scala支持两种形式的隐式转换:
隐式值:用于给方法提供参数
隐式视图:用于类型间转换或使针对某类型的方法能调用成功
隐式参数:参数列表
    样例类(case classes)
        概念
            case class是一种可以用来快速保存数据的类,可以认为是java中的pojo类,用于对象数据的保存。
            默认实现方法:
apply :
不需要使用new关键字就能创建该类对象
unapply :
可以通过模式匹配来获取类属性,是Scala中抽取器的实现和模式匹配的关键方法。
getter /setter :
默认构造参数默认被声明为val,实现了类构造参数的getter方法
如果构造参数是声明为var类型,实现setter和getter方法(不建议)
toString : equals : hashCode : copy
JavaBean规范的常见方法
        实现
            object Hello08040018 {
 def main(args: Array[String]): Unit = {
  val user: SysUser = SysUser("admin", "123456", "管理员")
  user match {
   case SysUser(uname, passwd, nickname) => println(uname, passwd,
nickname)}}}
case class SysUser(uname: String, passwd: String, nickname: String)
    方法
        isDefinedAt : 这个函数的作用是判断传入来的参数是否在这个偏函数所处理的范围内。
            def main(args: Array[String]): Unit = {
  println(pf.isDefinedAt(1))
  println(pf.isDefinedAt(4))
}
def pf: PartialFunction[AnyVal, String] = {
  case 1 => "One"
  case 2 => "Two"
  case 3 => "Three"
}
        orElse : 将多个偏函数组合起来使用,效果类似case语句。
            def main(args: Array[String]): Unit = {
  println(pf(1))
}
def onePf: PartialFunction[Int, String] = {
  case 1 => "One"
}
def twoPf: PartialFunction[Int, String] = {
  case 2 => "Two"
}
def threePf: PartialFunction[Int, String] = {
  case 3 => "Three"
}
def pf = onePf orElse twoPf orElse threePf
        andThen: 相当于方法的连续调用,比如g(f(x))。
            def main(args: Array[String]): Unit = {
  var pf12 = onePf andThen twoPf
  println(pf12(1))
}
def onePf: PartialFunction[Int, String] = {
  case 1 => "string"
}
def twoPf: PartialFunction[String, Double] = {
  case "string" => 2.0
}
        applyOrElse:它接收2个参数,第一个是调用的参数,第二个是个回调函数。如果第一个调用的参数匹配,返回匹配的值,否则调用回调函数。
            def main(args: Array[String]): Unit = {
  println(onePf.applyOrElse(1, { num: Int => "more" }))
  println(onePf.applyOrElse(2, { num: Int => "more" }))
}
def onePf: PartialFunction[Int, String] = {
  case 1 => "one"
}
    偏函数
        偏函数(Partial Function),是一个数学概念它不是"函数"的一种, 它跟函数是平行的概念。
Scala中的Partia Function是一个Trait,其的类型为PartialFunction[A,B],其中接收一个类型为A
的参数,返回一个类型为B的结果。
            {
case 1=>"hello world 1"
case 2=>"hello world 2
}
模式匹配1,或者2,则会返回一个字符串
但是这里不能匹配除1,2之外的其他的Int,如果你传入一个3,那么不会有任何的返回
如果我们把这个当做一个函数来看待,那么他就是只接受Int类型且值为1或者2的函数调用
结论:偏函数相对于函数来讲,是缩小版的函数,或者说是残缺版的函数。
        如果一个方法中没有 match 只有 case ,这个函数可以定义成 PartialFunction偏函数 。
偏函数定义时,不能使用括号传参,默认定义 PartialFunction 中传入一个值,匹配上了对应的
case ,返回一个值,只能匹配同种类型。
一个 case 语句就可以理解为是一段匿名函数。
            def main(args: Array[String]): Unit = {
  println(pf(1))
  println(pf(6))
  println(pf(true))
}
def pf: PartialFunction[AnyVal, String] = {
  case 1 => "One"
  case 2 => "Two"
  case 3 => "Three"
  case i: Int => "Int"
  case i: Double => "Int"
  case _ => "Other"
}
    WordCount
        统计一个数组中每个单词出现的次数
        def main(args: Array[String]): Unit = {
  //定义一个数组
  val strings: Array[String] = Array[String]("Hello Word", "Hello Moto",
"Hello Word")
  //正常版
  list.flatMap((ele: String) => {
   ele.split(" ")
 }).map((ele: String) => {
   new Tuple2(ele, 1)
 }).groupBy((ele: (String, Int)) => {
   ele._1
 }).mapValues((list: List[(String, Int)]) => {
   list.size
 }).toList.sortBy((tuple: (String, Int)) => {
   tuple._2
 }).foreach(println)
  //简写模式
  strings.flatMap(_.split(" ")).map((_,
1)).groupBy(_._1).mapValues(_.size).toList.sortBy(_._2).foreach(println)
}
    OPtion
        Scala Option(选项)类型用来表示一个值是可选的(有值或无值)。
Option[T] 是一个类型为 T 的可选值的容器: 如果值存在, Option[T] 就是一个 Some[T] ,如果不存在, Option[T] 就是对象 None 。
            val myMap: Map[String, String] = Map("key1" -> "value")
val value1: Option[String] = myMap.get("key1")
val value2: Option[String] = myMap.get("key2")
println(value1) // Some("value1")
println(value2) // None
        getOrElse() 方法来获取元组中存在的元素或者使用其默认的值
            val a:Option[Int] = Some(5)
val b:Option[Int] = None
println("a.getOrElse(0): " + a.getOrElse(0) )
println("b.getOrElse(10): " + b.getOrElse(10) )
        isEmpty() 方法来检测元组中的元素是否为 None
            val a:Option[Int] = Some(5)
val b:Option[Int] = None
println("a.isEmpty: " + a.isEmpty )
println("b.isEmpty: " + b.isEmpty )
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值