【IDEA】数据湖 Hudi 0.12.0 基础使用

news2024/12/23 4:09:40

文章目录

      • 创建 Maven 项目
      • 插入数据
      • 查询数据
      • 更新数据
      • 增量查询
      • 删除数据

前言

集群系统:CentOS 7.5

服务器信息:

服务器角色IP
hadoop104服务器Master192.168.0.104
hadoop105服务器Slave1192.168.0.105
hadoop106服务器Slave2192.168.0.106

使用的组件版本如下:

组件名称版本号
JDK1.8
Hadoop3.1.3
Spark3.1.1
Hive3.1.2
MySQL5.7
Hudi0.12.0

创建 Maven 项目

在 IDEA 中创建一个 Maven 项目,这里不再赘述。

项目创建完成后,将 Hadoop 的配置文件 core-site.xmlhdfs-site.xml 文件与 Spark 的日志管理文件放入到项目中的 resources 目录下。

添加依赖,可以根据我的进行修改。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.study</groupId>
    <artifactId>Study_Hudi</artifactId>
    <version>1.0-SNAPSHOT</version>

    <repositories>
        <repository>
            <id>aliyun</id>
            <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
        </repository>
    </repositories>

    <properties>
        <scala.version>2.12.10</scala.version>
        <spark.version>3.1.1</spark.version>
        <hadoop.version>3.1.3</hadoop.version>
        <hudi.version>0.12.0</hudi.version>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <!-- 依赖Scala语言 -->
        <dependency>
            <groupId>org.scala-lang</groupId>
            <artifactId>scala-library</artifactId>
            <version>${scala.version}</version>
        </dependency>
        <!-- Spark Core 依赖 -->
        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-core_2.12</artifactId>
            <version>${spark.version}</version>
        </dependency>
        <!-- Spark SQL 依赖 -->
        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-sql_2.12</artifactId>
            <version>${spark.version}</version>
        </dependency>

        <!-- Hadoop Client 依赖 -->
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-client</artifactId>
            <version>${hadoop.version}</version>
        </dependency>

        <!-- hudi-spark3 -->
        <dependency>
            <groupId>org.apache.hudi</groupId>
            <artifactId>hudi-spark3.1-bundle_2.12</artifactId>
            <version>${hudi.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-avro_2.12</artifactId>
            <version>${spark.version}</version>
        </dependency>

        <dependency>
            <groupId>joda-time</groupId>
            <artifactId>joda-time</artifactId>
            <version>2.10</version>
        </dependency>

    </dependencies>

    <build>
        <outputDirectory>target/classes</outputDirectory>
        <testOutputDirectory>target/test-classes</testOutputDirectory>
        <resources>
            <resource>
                <directory>${project.basedir}/src/main/resources</directory>
            </resource>
        </resources>
        <!-- Maven 编译的插件 -->
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.0</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>net.alchim31.maven</groupId>
                <artifactId>scala-maven-plugin</artifactId>
                <version>3.2.0</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>testCompile</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

插入数据

import org.apache.hudi.QuickstartUtils.{DataGenerator, convertToStringList}
import org.apache.spark.sql.{DataFrame, Dataset, SaveMode, SparkSession}

import java.util

object HudiDemo {

    def insertData(spark: SparkSession, tableName: String, tablePath: String): Unit = {
        // 导入隐式转换
        import spark.implicits._

        // 利用官方提供的模拟数据对象生成 100 条数据(JSON 格式)
        val generator = new DataGenerator()
        val list_datas: util.List[String] = convertToStringList(generator.generateInserts(100))

        // 序列化数据
        import scala.collection.JavaConverters._
        val json_datas: Dataset[String] = spark.sparkContext.parallelize(list_datas.asScala).toDS()

        // 加载数据
        val dataFrame: DataFrame = spark.read.json(json_datas)
        
        // 将数据插入数据到 Hudi 表
        import org.apache.hudi.DataSourceWriteOptions._
        import org.apache.hudi.config.HoodieWriteConfig._

        dataFrame.write
                .mode(SaveMode.Append)
                .format("hudi")
                .option("hoodie.insert.shuffle.parallelism", "2")
                .option("hoodie.upsert.shuffle.parallelism", "2")
                .option(PRECOMBINE_FIELD.key(), "ts")
                .option(RECORDKEY_FIELD.key(), "uuid")
                .option(PARTITIONPATH_FIELD.key(), "partitionpath")
                .option(TBL_NAME.key(), tableName)
                .save(tablePath)

    }

    def main(args: Array[String]): Unit = {
        // 创建 Spark 对象
        val spark: SparkSession = SparkSession.builder()
                .appName("hudi_demo")
                .master("local[*]")
                // 设置 hudi 序列化方式,固定写法
                .config("spark.serializer","org.apache.spark.serializer.KryoSerializer")
                .getOrCreate()

        // 定义表名称和 HDFS 存储路径
        val tableName = "trips"
        val tablePath = "/hudi_warehouse/demo"

        insertData(spark,tableName,tablePath)

        // 释放资源
        spark.stop()

    }

}

要点解析:

使用 DataGenerator 生成随机数据,并将数据转换为一个字符串列表,这个是官方提供的,生成的是出租车相关数据,因为 Hudi 是由 Uber(美国的打车 APP) 开发的。

将生成的数据使用 spark.sparkContext.parallelize() 方法将数据转换为一个 Spark Dataset,每个元素都是一个字符串,代表一条数据。JavaConverters 提供了将 Java List 转换为 Scala List 的方法。

在写入数据那块,使用 DataFrame 中的 write 方法将数据插入到 Hudi 表中,详细参数解释如下:

  • mode(SaveMode.Append) 表示追加模式;

  • format("hudi") 表示将数据源指定为 Hudi;

  • option() 方法用于设置 Hudi 表的属性:

    • PRECOMBINE_FIELD(预合并字段)

    • RECORDKEY_FIELD(记录键字段)

    • PARTITIONPATH_FIELD(分区路径字段)

    • TBL_NAME(指定表名称)

  • save() 方法将数据保存到 Hudi 表中。

数据表写入后,我们可以在 HDFS 中查看到:

在这里插入图片描述

查询数据

import org.apache.hudi.QuickstartUtils.{DataGenerator, convertToStringList}
import org.apache.spark.sql.{DataFrame, Dataset, SaveMode, SparkSession}

import java.util

object HudiDemo {

    def queryData(spark: SparkSession, path: String): Unit = {

        import spark.implicits._

        val dataFrame: DataFrame = spark.read.format("hudi").load(path)

        // 获取表结构
        dataFrame.printSchema()

        // 查询支付费用小于 30 的数据
        dataFrame.createOrReplaceTempView("uber")
        spark.sql(
            """
              |select
              |     driver,
              |     rider,
              |     fare,
              |     _hoodie_commit_time
              |from
              |     uber
              |where
              |     fare < 30
              |order by
              |     fare
              |limit 10
              |""".stripMargin).show(truncate = false)
    }

    def main(args: Array[String]): Unit = {
        // 创建 Spark 对象
        val spark: SparkSession = SparkSession.builder()
                .appName("hudi_demo")
                .master("local[*]")
                // 设置序列化方式
                .config("spark.serializer","org.apache.spark.serializer.KryoSerializer")
                .getOrCreate()

        // 定义表名称和 HDFS 存储路径
        val tableName = "trips"
        val tablePath = "/hudi_warehouse/demo"

        queryData(spark,tablePath)

        // 释放资源
        spark.stop()

    }

}

要点解析:

读取 Hudi 表的数据十分简单,直接指定数据存储路径,然后就可以加载为 DataFrame 格式进行操作了。

这里说一下 dataFrame.printSchema() 查询到的 Hudi 表结构:

root
 |-- _hoodie_commit_time: string (nullable = true)
 |-- _hoodie_commit_seqno: string (nullable = true)
 |-- _hoodie_record_key: string (nullable = true)
 |-- _hoodie_partition_path: string (nullable = true)
 |-- _hoodie_file_name: string (nullable = true)
 |-- begin_lat: double (nullable = true)
 |-- begin_lon: double (nullable = true)
 |-- driver: string (nullable = true)
 |-- end_lat: double (nullable = true)
 |-- end_lon: double (nullable = true)
 |-- fare: double (nullable = true)
 |-- rider: string (nullable = true)
 |-- ts: long (nullable = true)
 |-- uuid: string (nullable = true)
 |-- partitionpath: string (nullable = true)
  • _hoodie_commit_time:记录该数据被写入 Hudi 表的提交时间,Hudi 自添加字段

  • _hoodie_commit_seqno:记录该数据被写入 Hudi 表的提交序号,Hudi 自添加字段

  • _hoodie_record_key:记录该数据在 Hudi 表中的唯一标识,用于进行数据的查找和更新,Hudi 自添加字段

  • _hoodie_partition_path:记录该数据在 Hudi 表中所属的分区路径,Hudi 自添加字段

  • _hoodie_file_name:记录该数据所在的数据文件名,Hudi 自添加字段

  • begin_lat:表示起点的纬度。

  • begin_lon:表示起点的经度。

  • driver:表示司机的姓名。

  • end_lat:表示终点的纬度。

  • end_lon:表示终点的经度。

  • fare:表示该行程的费用。

  • rider:表示乘客的姓名。

  • ts:表示该行程的时间戳。

  • uuid:表示该行程的唯一标识。

  • partitionpath:表示该数据在 Hudi 表中所属的分区路径。

最终输出结果如下所示:

+----------+---------+------------------+-------------------+
|driver    |rider    |fare              |_hoodie_commit_time|
+----------+---------+------------------+-------------------+
|driver-213|rider-213|2.3197037715185997|20230420180316005  |
|driver-213|rider-213|2.3929870003208786|20230420180316005  |
|driver-213|rider-213|4.477960629065403 |20230420180316005  |
|driver-213|rider-213|4.816835556452426 |20230420180316005  |
|driver-213|rider-213|5.585015784895486 |20230420180316005  |
|driver-213|rider-213|6.105928762642976 |20230420180316005  |
|driver-213|rider-213|6.330332057511468 |20230420180316005  |
|driver-213|rider-213|8.123010514625829 |20230420180316005  |
|driver-213|rider-213|8.61052303992933  |20230420180316005  |
|driver-213|rider-213|9.40943595299718  |20230420180316005  |
+----------+---------+------------------+-------------------+

拓展:

查询 Hudi 表数据时,可以依据时间进行过滤查询,设置属性:"as.of.instant",时间的格式为:"20210728141108""2021-07-28 14: 11: 08",如下所示:

        val dataFrame: DataFrame = spark.read.format("hudi").option("as.of.instant", "20210728141108").load(path)

更新数据

import org.apache.hudi.QuickstartUtils.{DataGenerator, convertToStringList}
import org.apache.spark.sql.{DataFrame, Dataset, SaveMode, SparkSession}

import java.util

object HudiDemo {

    def insertData(spark: SparkSession, tableName: String, tablePath: String): DataGenerator = {
        // 导入隐式转换
        import spark.implicits._

        // 利用官方提供的模拟数据对象生成 100 条数据(JSON 格式)
        val generator = new DataGenerator()
        val list_datas: util.List[String] = convertToStringList(generator.generateInserts(100))

        // 序列化数据
        import scala.collection.JavaConverters._
        val json_datas: Dataset[String] = spark.sparkContext.parallelize(list_datas.asScala).toDS()

        // 加载 JSON 数据
        val dataFrame: DataFrame = spark.read.json(json_datas)

        // 将数据插入数据到 Hudi 表
        import org.apache.hudi.DataSourceWriteOptions._
        import org.apache.hudi.config.HoodieWriteConfig._

        dataFrame.write
                .mode(SaveMode.Append)
                .format("hudi")
                .option("hoodie.insert.shuffle.parallelism", "2")
                .option("hoodie.upsert.shuffle.parallelism", "2")
                .option(PRECOMBINE_FIELD.key(), "ts")
                .option(RECORDKEY_FIELD.key(), "uuid")
                .option(PARTITIONPATH_FIELD.key(), "partitionpath")
                .option(TBL_NAME.key(), tableName)
                .save(tablePath)

        return generator

    }

    def updateData(spark: SparkSession, table: String, path: String, dataGen: DataGenerator): Unit = {

        import spark.implicits._

        // 模拟产生更新数据
        import org.apache.hudi.QuickstartUtils._
        import scala.collection.JavaConverters._

        val updates: util.List[String] = convertToStringList(dataGen.generateUpdates(100))
        val updateDF: DataFrame = spark.read.json(spark.sparkContext.parallelize(updates.asScala, 2).toDS())

        // 更新数据至 Hudi 表
        import org.apache.hudi.DataSourceWriteOptions._
        import org.apache.hudi.config.HoodieWriteConfig._

        updateDF.write
                .mode(SaveMode.Append)
                .format("hudi")
                .option("hoodie.insert.shuffle.parallelism", "2")
                .option("hoodie.upsert.shuffle.parallelism", "2")
                .option(PRECOMBINE_FIELD.key(), "ts")
                .option(RECORDKEY_FIELD.key(), "uuid")
                .option(PARTITIONPATH_FIELD.key(), "partitionpath")
                .option(TBL_NAME.key(), table)
                .save(path)
    }

    def main(args: Array[String]): Unit = {
        // 创建 Spark 对象
        val spark: SparkSession = SparkSession.builder()
                .appName("hudi_demo")
                .master("local[*]")
                // 设置序列化方式
                .config("spark.serializer","org.apache.spark.serializer.KryoSerializer")
                .getOrCreate()

        // 定义表名称和 HDFS 存储路径
        val tableName = "trips"
        val tablePath = "/hudi_warehouse/demo"

        val generator: DataGenerator = insertData(spark, tableName, tablePath)
        updateData(spark,tableName,tablePath,generator)

        // 释放资源
        spark.stop()

    }

}

由于官方提供的工具类 DataGenerator 模拟生成更新 update 数据时,必须要与插入 insert 数据使用同一个 DataGenerator 对象,所以在插入数据完成后,返回 DataGenerator 对象,然后再次生成模拟数据,最后更新写入 Hudi 中。

增量查询

当 Hudi 中表的类型为:COW 时,支持两种方式查询:快照查询和增量查询,默认为快照查询。

如果是增量查询,需要指定时间戳,当 Hudi 表中数据满足:instant_time > beginTime 时,数据将会被加载读取。

此外,也可以设置某个时间范围:endTime > instant_time > begionTime,获取相应的数据。

import org.apache.hudi.QuickstartUtils.{DataGenerator, convertToStringList}
import org.apache.spark.sql.{DataFrame, Dataset, SaveMode, SparkSession}

import java.util

object HudiDemo {

    def incrementalQueryData(spark: SparkSession, path: String): Unit = {

        import spark.implicits._

        // 加载 Hudi 表数据,获取 commitTime 时间,作为增量查询时间阈值
        import org.apache.hudi.DataSourceReadOptions._
        
        spark.read
                .format("hudi")
                .load(path)
                .createOrReplaceTempView("uber")
        
        val commits: Array[String] = spark
                .sql(
                    """
                      |select
                      |  distinct(_hoodie_commit_time) as commitTime
                      |from
                      |  uber
                      |order by
                      |  commitTime desc
                      |""".stripMargin
                )
                .map(row => row.getString(0))
                .take(100)
        
        val beginTime: String = commits(commits.length - 1)
        
        println(s"beginTime = ${beginTime}")

        // 设置 Hudi 数据 CommitTime 时间阈值,进行增量查询数据
        val tripsIncrementalDF: DataFrame = spark.read
                .format("hudi")
                // 设置查询数据模式为:incremental,增量读取
                .option(QUERY_TYPE.key(), QUERY_TYPE_INCREMENTAL_OPT_VAL)
                // 设置增量读取数据时开始时间
                .option(BEGIN_INSTANTTIME.key(), beginTime)
                .load(path)

        // 将增量查询数据注册为临时视图,查询费用 fare 大于 20 的数据
        tripsIncrementalDF.createOrReplaceTempView("result")
        spark
                .sql(
                    """
                      |select
                      |  `_hoodie_commit_time`, fare, begin_lon, begin_lat, ts
                      |from
                      |  result
                      |where
                      |  fare > 20.0
                      |""".stripMargin
                )
                .show(10, truncate = false)
    }
    
    def main(args: Array[String]): Unit = {
    
        // 创建 Spark 对象
        val spark: SparkSession = SparkSession.builder()
                .appName("hudi_demo")
                .master("local[*]")
                // 设置序列化方式
                .config("spark.serializer","org.apache.spark.serializer.KryoSerializer")
                .getOrCreate()

        // 定义表名称和 HDFS 存储路径
        val tableName = "trips"
        val tablePath = "/hudi_warehouse/demo"

        incrementalQueryData(spark,tablePath)

        // 释放资源
        spark.stop()

    }

}

最终输出结果如下:

beginTime = 20230420180316005
+-------------------+------------------+-------------------+-------------------+-------------+
|_hoodie_commit_time|fare              |begin_lon          |begin_lat          |ts           |
+-------------------+------------------+-------------------+-------------------+-------------+
|20230420191528978  |38.66148557641057 |0.1688421548397122 |0.7327922821557408 |1681766807190|
|20230420191522934  |66.64889106258252 |0.09632451474505643|0.47805950282725407|1681403127621|
|20230420191528978  |68.55454228464582 |0.23574926083757652|0.9978872086544781 |1681824914068|
|20230420191528978  |63.727536726737064|0.9162255698100301 |0.13790358857355933|1681614366597|
|20230420191528978  |87.67786901640419 |0.5117763107378623 |0.5256297280989708 |1681932619296|
|20230420191522934  |52.69712318306616 |0.37272120488128546|0.3748535764638379 |1681447723048|
|20230420191528978  |95.62675391327986 |0.723282980554737  |0.5241875096707868 |1681923702941|
|20230420191528978  |87.29826695295154 |0.4020819430848185 |0.6616708725249253 |1681484734371|
|20230420191528978  |66.76801186951676 |0.17564160456980282|0.876587418131608  |1681770062715|
|20230420191528978  |39.9997692098926  |0.30356883150425995|0.08732209622598563|1681852678149|
+-------------------+------------------+-------------------+-------------------+-------------+

删除数据

使用官方的数据生成类 DataGenerator,基于已有数据构建要删除的数据,最终保存到 Hudi 表中。

import org.apache.hudi.QuickstartUtils.{DataGenerator, convertToStringList}
import org.apache.spark.sql.{DataFrame, Dataset, SaveMode, SparkSession}

import java.util

object HudiDemo {

    def deleteData(spark: SparkSession, table: String, path: String): Unit = {
        import spark.implicits._

        // 加载 Hudi 表数据,获取条目数
        val tripsDF: DataFrame = spark.read.format("hudi").load(path)
        println(s"Count = ${tripsDF.count()}")

        // 模拟要删除的数据
        val dataframe: DataFrame = tripsDF.select($"uuid", $"partitionpath").limit(2)
        import org.apache.hudi.QuickstartUtils._

        val dataGen: DataGenerator = new DataGenerator()
        val deletes: util.List[String] = dataGen.generateDeletes(dataframe.collectAsList())

        import scala.collection.JavaConverters._
        val deleteDF: DataFrame = spark.read.json(spark.sparkContext.parallelize(deletes.asScala, 2))

        // 保存数据至 Hudi 表,设置操作类型为:DELETE
        import org.apache.hudi.DataSourceWriteOptions._
        import org.apache.hudi.config.HoodieWriteConfig._

        deleteDF.write
                .mode(SaveMode.Append)
                .format("hudi")
                .option("hoodie.insert.shuffle.parallelism", "2")
                .option("hoodie.upsert.shuffle.parallelism", "2")
                // 设置数据操作类型为delete,默认值为upsert
                .option(OPERATION.key(), "delete")
                .option(PRECOMBINE_FIELD.key(), "ts")
                .option(RECORDKEY_FIELD.key(), "uuid")
                .option(PARTITIONPATH_FIELD.key(), "partitionpath")
                .option(TBL_NAME.key(), table)
                .save(path)

        // 再次加载 Hudi 表数据,统计条目数,查看是否减少 2 条
        val hudiDF: DataFrame = spark.read.format("hudi").load(path)
        println(s"Delete After Count = ${hudiDF.count()}")
    }

    def main(args: Array[String]): Unit = {
    
        // 创建 Spark 对象
        val spark: SparkSession = SparkSession.builder()
                .appName("hudi_demo")
                .master("local[*]")
                // 设置序列化方式
                .config("spark.serializer","org.apache.spark.serializer.KryoSerializer")
                .getOrCreate()

        // 定义表名称和 HDFS 存储路径
        val tableName = "trips"
        val tablePath = "/hudi_warehouse/demo"

        deleteData(spark,tableName,tablePath)

        // 释放资源
        spark.stop()

    }

}

最终输出结果如下:

Count = 200
Delete After Count = 198

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

看完这篇文章你就彻底懂啦{保姆级讲解}-----(LeetCode刷题977有序数组的平方) 2023.4.20

目录 前言算法题&#xff08;LeetCode 977有序数组的平方&#xff09;—&#xff08;保姆级别讲解&#xff09;分析题目算法思想&#xff08;重要&#xff09;暴力解法代码&#xff1a;双指针法&#xff08;快慢指针法&#xff09;代码&#xff1a; 结束语 前言 本文章一部分内…

C++ Primer (第五版)-第九章 顺序容器

文章目录 一、概述二、内容9.1、顺序容器概述如何选择合适的容器 9.2、容器库概览迭代器容器类型成员列表初始化赋值和Swap容器的大小关系运算符 9.3 顺序容器操作9.3.1向顺序容器添加元素访问元素删除元素改变容器大小 9.4 Vector 对象如何增长9.5、额外的string 操作9.5.2、改…

【散文诗】单片机运行下和非运行下的 ROM 和 RAM

目录 一、两种处理器的结构体系1. 哈佛结构体系2. 冯诺依曼结构体系3. 两种结构的总结 二、单片机运行下和非运行下的内存分配1. 非运行时的单片机程序在ROM内的分布2. 运行时的单片机程序在RAM内的分布 三、单片机程序和操作系统应用程序的对比四、编译流程 一、两种处理器的结…

Java 8 中使用 Lambda 表达式和 Stream API 解决 LeetCode 的两数之和问题

Java 8 中使用 Lambda 表达式和 Stream API 解决 LeetCode 的两数之和问题 当我们在面对一个数列&#xff0c;需要查找其中两个元素的和为给定目标值时&#xff0c;可以使用两数之和&#xff08;Two Sum&#xff09;问题来解决。这个问题在 LeetCode 上有很高的重要性和普遍性&…

入行IC| 数字IC设计和验证哪个好?(内含薪资对比)

网上有一个很火的问题那就是数字IC设计和验证哪个好&#xff1f;接下来我们从以下几个维度来对比一下这两个岗位。 入行门槛 从上述对比就可以看出&#xff0c;IC设计要比验证门槛高。 具体工作内容有哪些&#xff1f; 数字IC前端设计 1.配合芯片架构的设计&#xff1b;一…

技术干货|直流电源自动测试系统功能介绍

直流电源是一种将交流电转换为恒定电压或电流输出的电子设备。在实际生产生活中&#xff0c;直流电源被广泛应用于各种场合。但由于各种原因&#xff0c;包括工艺、质量等因素&#xff0c;直流电源存在一定的出厂偏差。为了确保直流电源的精度和稳定性&#xff0c;在生产过程中…

layui框架学习(23:代码文本修饰模块)

Layui中的代码文本修饰模块layui.code主要用于修饰代码区域或文本行&#xff0c;其基本用法是使用预设类layui-code标识包含代码或文本的元素&#xff0c;然后调用layui.code函数渲染样式。Layui官网教程及示例中主要使用pre元素包含带修饰的代码或文本&#xff08;pre元素可定…

Myslq架构和原理

这里写自定义目录标题 Myslq体系架构连接层存储引擎 MYSQL原理单表访问连接查询&#xff08;原理&#xff09; Myslq体系架构 连接层 存储引擎 查看日志文件&#xff1a;show variables like ‘log_error’\G; BIN LOG 日志&#xff1a; show variables like ‘log_bin’; MYS…

Leetcode 622. 设计循环队列

文章目录 1.题目描述2.原题链接3.思路分析4.接口实现 &#xff1a;FrontRearenQueue(value):deQueue():isEmpty(): 检查循环队列是否为空isFull():myCircularQueueFree 5.代码实现 1.题目描述 设计你的循环队列实现。 循环队列是一种线性数据结构&#xff0c;其操作表现基于 FI…

16.泛型

泛型 一、什么是泛型 泛型的本质是参数化类型&#xff0c;即给类型指定一个参数&#xff0c;然后在使用时再指定此参数具体的值&#xff0c;那样这个类型就可以在使用时决定了。这种参数类型可以用在类、接口和方法中&#xff0c;分别被称为泛型类、泛型接口、泛型方法。 二…

【ONE·C++ || 模板进阶】

总言 主要介绍模板相关内容&#xff1a;非类型模板参数、类模板特化、模板的分离编译。 文章目录 总言1、非类型模板参数1.1、主要介绍1.2、std::array 简要说明 2、模板的特化2.1、基本介绍2.2、函数模板特化2.3、类模板特化2.3.1、基本说明2.3.2、用途举例2.3.3、分类&#…

统信UOS 20 安装达梦数据库V8

统信UOS 20 安装达梦数据库V8 1、安装教程2、启动数据库实例服务失败解决方法3、使用dm管理工具连接数据库 1、安装教程 https://blog.csdn.net/OceanWaves1993/article/details/129936878 此教程进行到启动数据库实例步骤时 使用下面命令启动数据库实例服务时&#xff0c;报…

找高清图片素材,这8个网站就够了

相信很多设计师、自媒体都为找素材而烦恼&#xff0c;很多朋友不知道去哪里找图片素材&#xff0c;找到了版权还不明确&#xff0c;怕造成侵权&#xff0c;今天我就把我独家珍藏的8个图片素材网站分享给大家&#xff0c;免费下载&#xff0c;还可以商用&#xff0c;建议收藏起来…

APIs -- DOM浏览器

1. Window对象 1.1 BOM&#xff08;浏览器对象模型&#xff09; BOM(Browser Object Model)是浏览器对象模型 window对象是一个全局对象&#xff0c;也可以说是JavaScript中的顶级对象像document、alert()、console.log()这些都是window的属性&#xff0c;基本BOM的属性和方…

crackme例子1

样本 jadx 静态分析 其中v2为查v5表得到&#xff0c;v3为用户输入index 下面就分别分析这几个值是啥 abcdefghddddd 得到v5和v4 解压assets下abcdefghddddd 拖入010 editor查看&#xff0c;实际是一个带相关数据的png文件 v5为图片位置89473开始&#xff0c;长度768字节&am…

Web安全 SQL注入漏洞测试.(可以 防止恶意用户利用漏洞)

Web安全 SQL注入漏洞测试 SQL注入就是 有些恶意用户在提交查询请求的过程中 将SQL语句插入到请求内容中&#xff0c;同时程序的本身对用户输入的内容过于相信&#xff0c;没有对用户插入的SQL语句进行任何的过滤&#xff0c;从而直接被SQL语句直接被服务端执行&#xff0c;导致…

AutoSAR软件组件开发的两类工作流程(Matlab/Simulink)

目录 前面 自顶向下 导入arxml文件 生成模型框架 搭建算法模型 生成代码 自下向上 前面 如何在Matlab进行AutoSAR软件组件SWC的开发&#xff1f;也就是下图红框标识出来的部分。 常规的有两种方式自顶向下与自下而上&#xff1a; 从上往下&#xff1a;从软件组件描述文…

改bug神器ChatGPT AI测试将取代人工吗?

最近ChatGPT大火&#xff0c;各大论坛中都会出现它的关键词。 机器和人对话本不是什么新鲜事&#xff0c;而ChatGPT上线仅5天&#xff0c;用户数量就超百万&#xff0c;之所以能在短时间吸引到这么多用户尝鲜&#xff0c;是因为它比“人工智障”的AI前辈们聪明多了~ 玩了一会…

Vue 3 组件通信

本文采用<script setup />的写法&#xff0c;比options API更自由。那么我们就来说说以下七种组件通信方式&#xff1a; props emit v-model refs provide/inject eventBus vuex/pinia 举个例子 本文将使用下面的演示&#xff0c;如下图所示&#xff1a; 上图中…

继承下的类型转换

一、私有/保护继承下的向上类型转换 示例&#xff1a; 图中蓝色、黄色代码均不允许使用&#xff0c;原因是在私有继承下&#xff0c;派生类和基类的public接口完全不搭界&#xff08;所实现的功能没有重叠&#xff09;&#xff0c;因此不允许强制转换&#xff0c;也无任何意义…