12.SparkSql

SparkSql

Spark SQL is Apache Spark's module for working with structured data。 是spark处理结构化数据的模块。具有Integrated,Uniform Data Access,Hive Integration,Standard Connectivity的特点。

DataFrame, DataSet and RDD

DataSet是在1.6引入的一个分布式数据集合。it provides the benefits of RDDs (strong typing, ability to use powerful lambda functions) with the benefits of Spark SQL’s optimized execution engine.

A DataFrame is a Dataset organized into named columns,DataFrames can be constructed from a wide array of sources such as: structured data files, tables in Hive, external databases, or existing RDDs. 从各种源读来的数据都变成了DF

实际上,DF就是DS,在spark中有如下定义 type DataFrame = Dataset[Row],即DF就是泛型为[Row]的DS,DS为强类型,DF为弱类型

DF与RDD相比,DF中还记录了数据的结构信息,即schema。并且对RDD的一些执行过程做出了优化。

DF,DS和RDD的相互转换

package cn.lpc.spark.sql

import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD
import org.apache.spark.sql.{DataFrame, Dataset, Row, SparkSession}

object RDD2DF2DS {
    def main(args: Array[String]): Unit = {
        val builder: SparkSession.Builder = SparkSession.builder().master("local[2]").appName("sql")
        val spark: SparkSession = builder.getOrCreate()
        val list: List[Person] = Person("zs", 11) :: Person("ls", 13) :: Person("ww", 15) :: Nil
        // RDD
        val sc: SparkContext = spark.sparkContext
        val rdd: RDD[Person] = sc.parallelize(list)
        import spark.implicits._
        //rdd2df
        val df: DataFrame = rdd.toDF()
        println("rdd2df")
        df.show()
        //rdd2ds
        println("rdd2ds")
        val ds: Dataset[Person] = rdd.toDS()
        ds.show()
        //df2rdd
        println("df2rdd")
        val df2rdd: RDD[Row] = df.rdd
        df2rdd.collect().foreach(println)
        println("ds2rdd")
       //ds2rdd
       val ds2rdd: RDD[Person] = ds.rdd
        ds2rdd.collect().foreach(println)
        //df2ds
        println("df2ds")
        val df2ds: Dataset[Person] = df.as[Person]
        df2ds.show()
        //ds2df
        println("ds2df")
        val ds2df: DataFrame = ds.toDF()
        ds2df.show()
        spark.stop()
    }
}
case class Person(val name:String, val age:Int)

SparkSql的编程

sql编程无非就是读如数据,对数据进行计算后将数据写出或者传输。

读写数据

spark在读写数据时也是一种懒,需要load()和save()两种方法让它动起来。

读写文件系统中的数据
  • 读:通过sparkSession的实例化对象进行操作,最终返回的是一个df。sparkSession.read生成一个reader,.format()是声明读取的文件的类型,可以为json,parquet等等。load()最终进行操作,需要输入路径。
  • 写:通过DataFrame(DS)进行操作,将对象中存的数据写入到磁盘中。df.write生成一个writer,.format指定格式。save中指定路径并开始执行。由于文件的写入需要指定mode来对重复写进行处理,默认为error。
object ReadAndWrite {
    def main(args: Array[String]): Unit = {

        val spark: SparkSession = SparkSession.builder().master("local[2]").appName("ReadAndWrite").getOrCreate()
        val sc: SparkContext = spark.sparkContext
        import spark.implicits._


        val list: List[Person] = Person("zs", 11) :: Person("ls", 13) :: Person("ww", 15) :: Nil
        // RDD

        val rdd: RDD[Person] = sc.parallelize(list)
        val df: DataFrame = rdd.toDF()
        df.write.format("json").mode(SaveMode.Append).save("c://temp")

        val df1: DataFrame = spark.read.format("json").load("C:\\temp")

        

        spark.sql("select * from json.`C:\\temp`").show()
    }
}
读写数据库中的数据

读写数据库中的数据与文件的操作基本一致,注意的是要指定一些参数。

 def main(args: Array[String]): Unit = {
        val builder: SparkSession.Builder = SparkSession.builder().master("local[2]").appName("sql")
        val spark: SparkSession = builder.getOrCreate()
        val list: List[Person] = Person("zs", 11) :: Person("ls", 13) :: Person("ww", 15) :: Nil
        // RDD
        val sc: SparkContext = spark.sparkContext

        val rdd1: RDD[Person] = sc.parallelize(list)
        import spark.implicits._
        val df: DataFrame = rdd1.toDF()
        df.write.format("jdbc")
            .option("url", "jdbc:mysql://192.168.6.200:3306/gmall")
            .option("user", "root")
            .option("password", "123456")
            .option("dbtable", "person")
            .save()
    }
def main(args: Array[String]): Unit = {
        val spark: SparkSession = SparkSession.builder().master("local[2]").appName("ReadAndWrite").getOrCreate()
        val sc: SparkContext = spark.sparkContext
        import spark.implicits._
        val df: DataFrame = spark.read
            .format("jdbc")
            .option("url", "jdbc:mysql://192.168.6.200:3306/gmall")
            .option("user", "root")
            .option("password", "123456")
            .option("dbtable", "person")
            .load()

        df.createOrReplaceTempView("temp_user_info")
        spark.sql("select *  from temp_user_info").show()
    }

数据的计算

不管怎么计算,最后都要show()一下

三种方式执行计算数据

可以直接通过执行spark.sql获取df后show即可,不过如果需要操作某个df或者ds,需要首先用ds或者df创建一个临时的视图,案例如下

    df.createOrReplaceTempView("user")
    spark.sql("select age from user").show()

直接操作某个文件中的数据时可以直接用sql读取,其实本质还是sql

spark.sql("select * from json.`C:\\temp`").show()

除了以上两种,其实还可以使用rdd风格的编程

 ds.select("age")
自定义函数

自定义函数可以分为UDF和UDAF,UDF的定义比较简单直接使用匿名函数即可。注意需要注册。

def main(args: Array[String]): Unit = {
        val builder: SparkSession.Builder = SparkSession.builder().master("local[2]").appName("sql")
        val spark: SparkSession = builder.getOrCreate()
        val list: List[Person] = Person("zs", 11) :: Person("ls", 13) :: Person("ww", 15) :: Nil
        // RDD
        val sc: SparkContext = spark.sparkContext

        val rdd: RDD[Person] = sc.parallelize(list)
        import spark.implicits._
        val df: DataFrame = rdd.toDF()
        spark.udf.register("doubleName",(s:String)=>s+s)
        df.createOrReplaceTempView("user")
        val sql : String = "select doubleName(name) as dname,age from user"
        spark.sql(sql).show()
        spark.stop()
    }

UDAF的函数比较复杂,复杂在于spark中的数据是分区的一个逻辑不能进行聚合,其实和RDD中的aggregate一致,需要有初值,分区内的计算,分区间的计算,还要指定输入和输出格式。

  • aggregate(num)(func,func1) num对应initialize的输出。func对应update,func1对应merge。逻辑上不难,注意处理update时输入的数据可能为null的情形。还需要注意注册。

对于DataSet的操作也可以定义UDAF。UDAF需要定义输入,缓冲区还有返回值的泛型。还有输入和输出的编码,如果是样例类就定义为product把。在使用时需要将定义的UDAF new一个并生成列

package cn.lpc.spark.sql

import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD
import org.apache.spark.sql.catalyst.expressions.Encode
import org.apache.spark.sql.{DataFrame, Dataset, Encoder, Encoders, Row, SparkSession, TypedColumn}
import org.apache.spark.sql.expressions.{Aggregator, MutableAggregationBuffer, UserDefinedAggregateFunction}
import org.apache.spark.sql.types.{DataType, DoubleType, IntegerType, StructField, StructType}

object DefineUDAF {
    def main(args: Array[String]): Unit = {
        // 用于弱类型,在sql语句中
        val builder: SparkSession.Builder = SparkSession.builder().master("local[2]").appName("sql")
        val spark: SparkSession = builder.getOrCreate()
        val list: List[Person] = Person("zs", 11) :: Person("ls", 13) :: Person("ww", 15) :: Nil
        val sc: SparkContext = spark.sparkContext

        val rdd: RDD[Person] = sc.parallelize(list)
        import spark.implicits._
        val df: DataFrame = rdd.toDF()

        df.createOrReplaceTempView("user")

        spark.udf.register("my_avg",new MyAvg)

        spark.sql("select my_avg(age) from user").show()

        val ds: Dataset[Person] = df.as[Person]

        // new一个并生成列。
        val value: TypedColumn[Person, Double] = new MyAvgDS().toColumn.name("ageAvg")
        // 输入的时person所以都弄好了
        ds.select(value).show()
        // 用于强类型,给ds使用
    }
}

// 自定义弱类型
 class MyAvg extends UserDefinedAggregateFunction{
     //rdd2.aggregateByKey(缓冲区类型)()
     //输入数据类型
    override def inputSchema: StructType = StructType(StructField("c",DoubleType)::Nil)

     // 缓冲区类型
    override def bufferSchema: StructType = StructType(StructField("sum",DoubleType)::StructField("count",IntegerType)::Nil)

     //  The `DataType` of the returned value of this [[UserDefinedAggregateFunction]].
    override def dataType: DataType = DoubleType

     // 相同输入是否应该有相同的输出
    override def deterministic: Boolean = true

     // 对缓冲区做初始化
    override def initialize(buffer: MutableAggregationBuffer): Unit = {
        buffer(0)=0D
        buffer(1)=0
    }

     // 分区内聚合
    override def update(buffer: MutableAggregationBuffer, input: Row): Unit = {
        if(!input.isNullAt(0)){
            val value: Double = input.getDouble(0)
            buffer(0)= buffer.getDouble(0)+value
            buffer(1) = buffer.getInt(1) +1
        }
    }

     // 分区间聚合
    override def merge(buffer1: MutableAggregationBuffer, buffer2: Row): Unit = {
        buffer1(0) = buffer2.getDouble(0)+buffer1.getDouble(0)
        buffer1(1)  = buffer2.getInt(1)+buffer1.getInt(1)
    }


    override def evaluate(buffer: Row): Any = {
        buffer.getDouble(0)/buffer.getInt(1)
    }
}

// 自定义的强类型
class MyAvgDS extends Aggregator[Person,AgeAvg,Double] {
    // 对缓冲区做初始化
    override def zero: AgeAvg = {AgeAvg(0D,0L)}

    // 分区内聚合
    override def reduce(b: AgeAvg, a: Person): AgeAvg = {
        AgeAvg(b.ageSum+a.age,b.ageCount+1)
    }

    // 分区间聚合
    override def merge(b1: AgeAvg, b2: AgeAvg): AgeAvg = {
        AgeAvg(b1.ageSum + b2.ageSum,b1.ageCount+b2.ageCount)
    }

    // 返回最终聚合的值
    override def finish(reduction: AgeAvg): Double = {
        reduction.getAvg()
    }

    // 缓冲的编码器
    override def bufferEncoder: Encoder[AgeAvg] = Encoders.product

    // 输出的编码器
    override def outputEncoder: Encoder[Double] = Encoders.scalaDouble
}

case class AgeAvg(ageSum:Double,ageCount:Long){
    def getAvg()={
        ageSum/ageCount
    }
}

Spark和hive的操作

Spark具有内置hive

直接执行Sql语句就是在内置hive中执行

Spark操作外置hive

Spark操作Hive有三个步骤:

  • 将hive-site放入conf目录,如果使用Idea,则将该文件放入resource目录里面

  • 要有连接hive元数据的Jar包,比如元数据存在mysql中就需要mysql的依赖

  • 在创建sparkSession时需要有enableHiveSupport

  • spark提供了Spark-sql命令行工具直接执行sql语句

  • spark还提供了类似hiveserver2的工具thrift。1.使用bin/start-server,2.使用sbin/beeline连接。!connect jdbc:hive2://hadoop101:10000

  • 使用idea访问

def main(args: Array[String]): Unit = {
        val spark: SparkSession = SparkSession.builder()
            .master("local[2]")
            .appName("hive")
            .enableHiveSupport()
            .getOrCreate()

        spark.sql("show tables").show()
    }

Spark练习

计算各个区域前三大热门商品,并备注上每个商品在主要城市中的分布比例,超过两个城市用其他显示。

data
area    省份    商品名
华北    北京    商品A
华南    广东    商品B

table
地区	商品名称		点击次数	城市备注
华北	商品A		100000	北京21.2%,天津13.2%,其他65.6%
华北	商品P		80200	北京63.0%,太原10%,其他27.0%
华北	商品M		40000	北京63.0%,太原10%,其他27.0%
东北	商品J		92000	大连28%,辽宁17.0%,其他 55.0%

该问题的主要难点在于城市备注的实现。

  • 按照area和商品分组,并且制作备注,和统计次数
 spark.sql(
            """
              |select
              |area,
              | product_name,
              | remark(city_name) remark,
              | count(*) times
              |from
              |firstData
              |group by area,product_name
              |""".stripMargin).createOrReplaceTempView("t2")
// remark为自定义函数

class Remark extends UserDefinedAggregateFunction {
    override def inputSchema: StructType = StructType(StructField("city_name",StringType)::Nil)

    override def bufferSchema: StructType = StructType(StructField("buffer",MapType(StringType,LongType))::StructField("count",LongType)::Nil)

    override def dataType: DataType = StringType

    override def deterministic: Boolean = true

    override def initialize(buffer: MutableAggregationBuffer): Unit = {
        buffer(0) = Map[String,Long]()
        buffer(1) = 0L
    }

    override def update(buffer: MutableAggregationBuffer, input: Row): Unit = {
        if(!input.isNullAt(0)){
            val city_name: String = input.getString(0)
            val temp_map: collection.Map[String, Long] =  buffer.getMap[String,Long](0)
            buffer(0) = temp_map + (city_name->(temp_map.getOrElse(city_name,0L)+1L))
            buffer(1) = buffer.getLong(1) + 1L
        }
    }

    override def merge(buffer1: MutableAggregationBuffer, buffer2: Row): Unit = {
        val temp_map1: collection.Map[String, Long] =  buffer1.getMap[String,Long](0)
        val temp_map2: collection.Map[String, Long] =  buffer2.getMap[String,Long](0)
        val count1: Long = buffer1.getLong(1)
        val count2: Long = buffer2.getLong(1)
        buffer1(0) = temp_map1.foldLeft(temp_map2){
            case (map,(k,v))=>map + ( k ->(map.getOrElse(k,1L)+v))
        }
        buffer1(1) = count1+count2

    }

    override def evaluate(buffer: Row): Any = {
        val count: Long = buffer.getLong(1)
        val format = new DecimalFormat(".00%")
        val tuples: List[(String, Long)] = buffer.getMap[String, Long](0).toList.sortBy(-_._2).take(2)
        val countTop2: Long = tuples.foldLeft(0L)((x, y) => x + y._2)
        tuples.map {
            case (k, v) => {
                k + ":" + format.format(v.toDouble/count)
            }
        }.mkString(", ") + ", 其他" +":"+ format.format((count-countTop2).toDouble / count)


    }
}
  • 按次数排序,需要开窗
 spark.sql(
            """
              |select
              |area,
              |product_name,
              |times,
              |remark,
              |rank() over(partition by area order by times desc) rk
              |from t2
              |""".stripMargin).createOrReplaceTempView("t3")
  • 取前三存入数据库之中
  spark.sql(
            """
              |select
              |area,
              |product_name,
              |remark,
              |times
              |from
              |t3
              |where rk <= 3
              |""".stripMargin).write.format("jdbc")
                    .option("url", "jdbc:mysql://192.168.6.200:3306/gmall")
                    .option("user", "root")
                    .option("password", "123456")
                    .option("dbtable", "adsTop3")
            .save()
<think>首先,用户的问题是:“我想查找SparkSQL中判断数据包含某个数的方法 请问SparkSQL如何判断数据包含某个数”。用户指定我需要参考站内引用,并提供了一些引用内容。 引用内容总结: - [^1]: 讨论了SparkSQL数据加载和保存,默认使用parquet格式。 - [^2]: 提到使用SparkSQL的functions库,其中包含常用函数。 - [^3]: 展示了使用ANALYZE TABLE命令进行统计分析。 - [^4]: 描述SparkSQL的各种API使用。 - [^5]: 提到SparkSQLSQL语句类似于Hive,并给出Hive文档链接。 用户的问题是判断数据是否包含某个数。我需要理解这个需求。"判断数据包含某个数" 可能意味着检查某个列的值是否包含一个特定的数字或字符串。在SQL中,这可能涉及使用LIKE操作符或字符串函数来检查子字符串,或者如果是数字,可能涉及数值比较。 在SparkSQL中,判断数据包含某个数可能指的是: - 如果是字符串列:检查字符串是否包含特定的数值(如"123"包含"12")。 - 如果是数值列:检查列值是否等于某个数(直接比较)。 用户说“包含某个数”,所以更可能指的是字符串中包含数字子串,或者是模糊匹配。 SparkSQL提供了丰富的函数来处理数据。从引用[^2]中,它提到了使用functions库,里面有常用函数,类似于Hive函数库。 常见的方法: - 使用LIKE操作符:例如,`column LIKE '%5%'` 检查列是否包含数字5。 - 使用contains函数:在SparkSQL中,有字符串函数如`contains(column, '5')`。 - 或者使用instr或position函数:查找子串的位置。 如果数据是数值类型,直接用等值比较:`column = 5`。 但用户说“包含”,暗示可能不是精确匹配,而是部分包含。 现在,构建回答。根据系统级指令: - 使用中文回答。 - 结构清晰,逐步解决问题。 - 添加引用标识,如[^数字]。 - 生成相关问题。 回答结构: 1. **理解问题**:重申用户问题以确保理解。 2. **提供方法**:解释在SparkSQL中如何判断数据包含某个数。 - 使用SQL语法。 - 使用DataFrame API。 - 使用functions库。 3. **给出示例**:提供代码示例来演示。 4. **引用相关部分**:基于提供的引用,添加引用标识。例如,提到functions库时引用[^2]。 5. **生成相关问题**:在最后添加相关问题。 从引用中提取相关信息: - [^2] 提到使用SparkSQL的functions库,导入`import org.apache.spark.sql.functions._` ,包含常用函数如字符串函数。 - [^5] 提到SparkSQL SQL语句类似Hive,所以Hive的函数可能适用。 - 其他引用不太直接相关,但可能间接有用。 核心方法是使用字符串函数。 在SparkSQL中,判断字符串是否包含子串的函数有: - `LIKE '%pattern%'` - `contains(column, 'substring')` - `instr(column, 'substring') > 0` 示例代码: 假设有一个表,列名为"data",类型为字符串,判断是否包含"5"。 SQL方式: ```sql SELECT * FROM table WHERE data LIKE '%5%'; ``` 或 ```sql SELECT * FROM table WHERE contains(data, '5'); ``` 在DataFrame API中: ```scala import org.apache.spark.sql.functions._ val df = spark.read.table("table") val filteredDF = df.filter(col("data").contains("5")) ``` 在PySpark中: ```python from pyspark.sql.functions import col, lit df.filter(col("data").contains("5")) ``` 添加引用:当提到functions库时,引用[^2]。 如果数据类型是数值,直接比较: ```sql SELECT * FROM table WHERE data = 5; ``` 但用户说“包含”,所以强调字符串场景。 生成相关问题: 基于用户问题,可能的后续问题: 1. SparkSQL中如何处理模糊匹配? 2. SparkSQL的函数库有哪些常用字符串函数? 3. 如何优化包含搜索的性能? 4. SparkSQLHive在函数上的区别? 确保回答真实可靠:SparkSQL文档支持这些函数。 最终回答结构: - 开头解释问题。 - 提供方法。 - 给出示例代码。 - 添加引用。 - 生成相关问题。 </think>### SparkSQL中判断数据是否包含某个数的方法 在SparkSQL中,判断数据是否包含某个特定数字(或字符串)是常见的数据过滤操作。这通常涉及字符串匹配或数值比较,具体取决于数据类型(如字符串列或数值列)。SparkSQL提供了多种API方式实现这一功能,包括SQL语法、DataFrame API和内置函数库。以下内容基于您提供的引用信息(尤其参考了SparkSQL的函数库和数据处理方式),我将逐步解释方法和提供示例,确保回答清晰可靠。 #### 1. **理解用户需求** - 您的问题核心是“判断数据包含某个数”,这可能分为两种情况: - **字符串列**:数据为文本类型(如"123"),需检查字符串中是否包含特定数字(如是否包含"5")。 - **数值列**:数据为数值类型(如整数或浮点数),需检查值是否等于或包含指定数字(通常用等值比较)。 - SparkSQL支持SQL-like语法和函数库(如`functions`),类似Hive的函数使用方式[^2][^5]。默认情况下,数据加载后可通过这些函数高效处理。 #### 2. **核心方法** SparkSQL提供了灵活的字符串匹配和数值比较函数。以下是主要方法,参考了SparkSQL的`functions`库和SQL语法[^2][^4][^5]: - **使用字符串匹配函数**(适用于字符串列): - `LIKE`操作符:用于模糊匹配,如`%5%`表示包含数字"5"。 - `contains()`函数:直接检查字符串是否包含指定子串。 - `instr()`函数:返回子串位置,位置大于0表示包含。 - **使用数值比较**(适用于数值列): - 等值比较:如`column = 5`,检查是否等于指定数字。 - 范围比较:如`column BETWEEN 4 AND 6`,检查是否在范围内(如需“包含”多个数字)。 - **通用API支持**:SparkSQLSQL、DataFrame和DataSet API均可实现相同功能,开发人员可灵活切换[^4]。 #### 3. **具体实现示例** 以下示例基于SparkSQL(使用Scala和PySpark),假设有一个表`example_table`,其中列`data`需要检查是否包含数字"5"。代码示例包括SQL语法和DataFrame API两种方式。 ##### 示例1:使用SQL语法(直接在SparkSQL shell或Spark应用程序中运行) ```sql -- 场景:data列为字符串类型,判断是否包含"5" SELECT * FROM example_table WHERE data LIKE '%5%'; -- 模糊匹配,包含"5"即返回 -- 场景:data列为数值类型,判断是否等于5 SELECT * FROM example_table WHERE data = 5; -- 精确匹配 -- 使用contains函数(需确保列类型为字符串) SELECT * FROM example_table WHERE contains(data, '5') IS TRUE; -- 返回包含"5"的行 ``` - **解释**:`LIKE '%5%'`是最常用方法;`contains()`函数更高效,但要求列是字符串类型。SparkSQLSQL语法兼容Hive标准,类似文档[^5]中的说明。 ##### 示例2:使用DataFrame API(在Scala或Python中) - **Scala代码**(导入`functions`库,参考[^2]): ```scala import org.apache.spark.sql.functions._ // 导入函数库,包含contains等函数 import org.apache.spark.sql.DataFrame // 假设df是加载的DataFrame,列data为字符串类型 val df: DataFrame = spark.read.table("example_table") // 读取数据,参考[^1] val filteredDF = df.filter(col("data").contains("5")) // 判断包含"5" filteredDF.show() // 显示结果 // 如果data列为数值类型,直接比较 val filteredDFNum = df.filter(col("data") === 5) // 等于5 ``` - **PySpark代码**(Python版本): ```python from pyspark.sql.functions import col, lit # 读取数据 df = spark.table("example_table") # 通用数据加载方式,参考[^1] # 字符串列:判断包含"5" filtered_df = df.filter(col("data").contains("5")) # 使用contains函数 # 数值列:判断等于5 filtered_df_num = df.filter(col("data") == 5) filtered_df.show() # 输出结果 ``` - **关键点**: - `contains()`函数属于`org.apache.spark.sql.functions`库,提供高效字符串处理[^2]。 - 如果数据是嵌套格式(如parquet),SparkSQL能自动处理类型转换[^1]。 #### 4. **注意事项** - **数据类型处理**:如果列是数值类型(如int或double),直接使用`=`比较即可;如果列是字符串但存储数字,需优先转为字符串或用`LIKE`匹配。 - **性能优化**:对于大表,使用`LIKE`或`contains()`可能扫描全表。可结合分区或统计信息优化(如引用[^3]中的`ANALYZE TABLE`命令)。 - **函数库参考**:SparkSQL的函数库类似Hive,更多函数如`instr()`或`rlike()`(正则匹配)详见Hive官方文档[^5]。 #### 5. **总结** - **推荐方法**:对于字符串列,使用`contains()`函数或`LIKE '%pattern%'`;对于数值列,直接用等值比较。SparkSQL的API设计通用性强,可无缝切换SQL和DataFrame方式[^4]。 - 基于您的引用,这些方法在SparkSQL数据处理中可靠且高效[^1][^2][^5]。 如果您有特定数据场景(如列类型或样本数据),我可以提供更针对性的示例!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值