Matroska解封装原理与实践

本期作者

背景

Matroska是一种开放标准、功能强大的多媒体封装格式,可容纳多种不同类型的视频、音频及字幕流,其常见的文件扩展名为.mkv、.mka等。与应用广泛的MP4相比,Matroska更加灵活开放,可以同时容纳多个字幕,甚至可以包含章节、标签等信息,成为了许多用户的偏爱。B站Web投稿页上传的所有视频中,封装格式为Matroska的视频占比超过2%,是除MP4以外占比最高的格式。

Web投稿页作为稿件生产的第一个环节,在获取到用户上传的视频后会根据视频内容为用户投稿提供辅助,其中包含:

  • 获取视频元信息,为快速转码提供预分析,缩短转码等待时间,同时为页面其他功能提供视频的基本数据。

  • 获取视频抽帧画面,对视频画面进行计算,根据计算结果进行AI智能推荐及自动填写,如封面推荐、分区推荐等,为用户填写稿件信息提供辅助。

而这些内容的实现,都基于对视频文件的解析,其中又包含了解封装和解码两个部分。

通用方案

此前,Web投稿页的实现方案为使用Emscripten将FFmpeg编译为WebAssembly后在Web平台运行,借助FFmpeg的音视频处理能力来进行解封装和解码。这种方案的优点是支持的视频格式齐全,可以解析几乎所有视频格式,但其缺点也很明确——解析速度慢,无法使用硬件加速,性能不佳,内存消耗大,甚至可能导致页面崩溃。

图片

升级方案

针对MP4格式视频的解析,Web投稿页已经实现了方案升级,改为使用mp4box(https://www.npmjs.com/package/mp4box)进行解封装,然后结合WebCodecs API进行解码,方案升级后获取视频信息和视频画面效率提高了70%。Matroska格式是除MP4以外占比最高的格式,在Web投稿始终占据着一席之地,对其解析方案进行改造升级势在必行。使用WebCodecs API进行解码实现起来并不复杂,已有诸多成熟的实践,需要着重解决的问题就是如何对Matroska视频进行高效解封装。

图片

技术调研

Matroska简介

Matroska是一种多媒体封装格式,是EBML在多媒体领域的应用。EBML是一种八位位组对齐的二进制格式,由一系列Elements(元素)组成,各个Element可顺序排列,可层层嵌套,能够灵活地表示和存储各种数据。而EBML中的所存储的具体数据内容,随着EBML文件类型的不同而不同,由文件类型所定义的EBML Schema来确定(详见附录1)。Matroska继承了EBML的层级化结构,在EBML基础上定义了一套独特的Schema模式,以实现其强大的功能和特性(详见附录2)。

从宏观结构上看,Matroska文件仅由两个主要的Element组成:EBML Header和 Segment,而Segment中又存储着8个可能的Element,称为Top Element。其中, SeekHead对Matroska解析非常重要,其内部包含了其他Top Element的位置索引,可用于方便快速搜索需要的信息:比如从Info、Tracks中获取视频的元信息,从Cues、Cluster获取具体的视频数据(详见附录3)。

图片

从微观组成上看,无论是EBML Header、Segment还是SeekHead等等,Matroska中的每个Element都是标准的EBML Element,有着确定的解析方式。

图片

由此,遵循Matroska特定的EBML Schema结构,按照EBML Element的组成和编码方式进行逐个解析,即可对整个Matroska文件的内容进行读取。

 相关开源项目

在MDN文档的WebCodecs API部分可以看到,文档中提及了一些对视频进行解封装的开源项目,对于MP4格式的文件,可以使用mp4box来进行解封装,对于WebM文件,则可以使用jswebm(https://www.npmjs.com/package/jswebm)。

图片

WebM是一种基于Matroska的多媒体封装格式,其规范与Matroska规范基本一致,只是对视频编码和音频编码作出了一些限制(见附录4)。jswebm在解析过程中对文件的音视频编码进行了判定,如果不符合则抛出错误。如果将这部分判定功能进行忽略,jswebm是可以同时解析Matroska、WebM文件的。

jswebm使用起来非常简单,相关代码如下:

const demuxer = new JsWebm();
demuxer.queueData(buffer);
while (!demuxer.eof) {demuxer.demux();
}
console.log(demuxer);
console.log(`total video packets : ${demuxer.videoPackets.length}`);
console.log(`total audio packets : ${demuxer.audioPackets.length}`);

而细读源码,可以发现其工作方式非常简单明了,就是顺序暴力读取:

1.文件读取层面:

图片

  • 将整个文件的ArrayBuffer传入,SDK支持将文件一次性完整传入,也可进行切片顺序传入,按照传入的方式将文件内容按顺序存储在列表中;

  • 对存好的ArrayBuffer列表从头分片读取,分片大小和数量与传入时一致;

  • 内容解析过程中如果当前分片已用尽,则继续读取下一个分片,直到完成整个文件的解析。

2.内容解析层面:

图片

  • 首先读取EBML Header中的内容,然后获取到Segment部分,按照各个Top Element在Segment部分中的实际存储顺序,用递归的方式顺序解析

  • 根据Element Id的不同,对各个Element Data采用不同的解析方式,并对解析结果进行存储

初步使用下来,从功能上来讲,我们想要的信息jswebm最终的确都能获取到,但想要在生产环境进行使用,有几个关键的问题不可忽略:

  • jswebm的文件读取方式决定了它内存占用极大,相当于整个视频文件都要读取到内存中,且浏览器对转换为ArrayBuffer的文件大小有限制,对于较大的视频文件,转换会直接失败

  • 只能对整个文件进行完整解析,而实际使用时并不需要整个文件的完整信息,效率低

  • API不完善,没有提供诸如获取视频元信息、获取某个时间点的视频帧数据等常用方法,需要根据解析后吐出的结果来进行二次复杂运算

  • 错误处理比较粗糙,视频文件有异常时解析过程无法正常抛出错误

因此,直接使用jswebm来对视频进行解析并不可行,无法满足Web投稿的实际需要。

方案实现

方案设计

Web投稿页对视频的解析需要在较短的时间内完成,这样才能确保计算结果能够及时曝光给用户,能够给用户投稿提供有效的帮助。这个过程中,内存的占用是非常重要的指标,如果内存占用过大,引发页面崩溃,则会打断投稿流程。实际生产环境使用时,有以下诉求:

  • 内存占用不能过大,需要进行有效控制,不能随着视频文件大小的增大而线性增大

  • 提高解析效率,缩短解析时间

  • 提供API单独获取视频元信息

  • 提供API获取某个时间点的视频帧数据,后续可结合WebCodecs API进行解码

因此,需要设计更加灵活、更加高效的工作流,打造更高性能、更实用的Matroska解封装SDK。

针对Web投稿页的实际诉求,主要进行以下设计:

1.数据读取层面:

图片

  • 直接传入文件的引用地址,不需要预先转换为ArrayBuffer

  • 从头开始读取文件,对当前读取位置进行记录,根据传入的分片大小配置及当前位置,动态获取当前位置的ArrayBuffer分片

  • 除了可以从头顺序读取文件外,还允许从给定的文件位置开始读取,记录读取位置并获取分片

  • 内容解析过程中如果当前分片已用尽,则更新当前读取位置,重新获取新的分片,直到完成整个文件的解析

2.内容解析层面:

图片

  • 读取EBML Header中的内容,获取Segment部分,优先解析Segment中SeekHead的内容,记录各个Top Element在文件中的的实际位置

  • 提供API,允许直接获取视频元信息或视频帧数据。根据所调用API的不同,去解析文件的不同部分:

  • 如果需要获取视频元信息,则根据SeekHead中记录的位置,直接去解析Tracks和Info部分

  • 如果需要获取视频帧数据,则根据SeekHead中记录的位置,直接去解析Cues和Cluster部分

  • 根据Element Id的不同,对各个Element Data采用不同的读取方式,并对解析结果进行存储

  • 定义解析过程中的通用错误类型,补充异常场景的错误抛出

根据Web投稿的实际应用需求,实现三个功能模块,分别进行文件预处理、视频基础信息获取、视频帧数据获取。各个模块之间存在一定的依赖关系,会对前置模块的运行结果进行校验。例如,获取视频基础信息时会使用到文件的索引信息,因此会预先检查文件是否完成了预处理工作。

图片

每个功能模块内部又包含着一些子功能,来对视频文件进行不同的处理和分析:

1、文件预处理

图片

  • 文件初始化:用于接收文件,按照传入的参数设置文件读取时的每个分片大小。分片大小可以根据实际需要来进行合理设置,当设置的分片大小过大时,文件分片会占据较大的内存,当设置的分片大小过小时,会频繁触发文件分片的动态获取,对解析耗时产生影响。

  • 解析文件索引信息:获取文件的Segment元素,优先搜索Segment中的SeekHead元素,对SeekHead中的内容进行解析读取,获取Segment中各个Top Element的索引位置并进行存储。

2、视频基础信息获取

图片

  • 获取视频元信息:根据SeekHead中所记录的Tracks和Info的位置,对两者进行解析,获取视频宽、高、视频编码、音频编码等信息。

3、视频帧数据获取

图片

  • 获取全部视频帧数据:根据SeekHead中所记录的Cues和Cluster的位置,对两者进行解析,获取视频所有帧数据及是否是关键帧等信息。

  • 获取某时间点视频帧数据:根据SeekHead中所记录的Cues的位置,对其进行解析,计算与所需时间点最接近的关键帧存储位置和时间索引,根据关键帧存储位置对视频帧数据进行解析和输出。

新的方案中,SDK的工作方式和代码逻辑改变很大,因此需要另起炉灶,创建新的SDK——mkv-demuxer(https://www.npmjs.com/package/mkv-demuxer)。

mkv-demuxer重点解决了内存问题,优化了解析过程中的异常处理,并提供了几个有用的API,基本满足了Web投稿页的应用需求。

使用方式

mkv-demuxer的使用非常简单,首先创建一个解封装器实例,将文件进行传入,配置解析时的分片大小,然后根据实际需要调取相应的API即可。

import MkvDemuxer from 'mkv-demuxer'
const demuxer = new MkvDemuxer()
const filePieceSize = 1 * 1024 * 1024
await demuxer.initFile(file, filePieceSize)
const meta = await demuxer.getMeta()
const data = await demuxer.getData()
const frame = await demuxer.seekFrame(10)

其中,getMeta可以获取视频文件基本的容器信息以及视频轨道、音频轨道的编码信息,返回的内容的格式为:

{info: {duration: 10000,muxingApp: "Lavf58.47.100",...},video: {codecID: "V_VP9",width: 1280,height: 720,...},audio: {codecID: "A_OPUS",channels: 2,rate: 48000,...},
}

getData可以获取到视频轨、音频轨的所有数据packet信息,包含每个packet在文件中的位置、所在时间戳。同时,cues中存储了视频所有关键帧的时间戳和相对位置信息,可与其他信息结合,得到关键帧在文件中具体位置。getData所返回内容的格式为:

{cues: [{cueTime: 50,cueTrackPositions: {cueClusterPosition: 660,cueRelativePosition: 2539,cueTrack: 1,},},...],videoPackets: [{end: 3311,isKeyframe: true,keyframeTimestamp: 0.05,size: 51,start: 3260,timestamp: 0.05,},...],audioPackets: [{end: 1998,size: 1273,start: 725,timestamp: 0.001,},...],
};

seekFrame可以根据给定的时间戳返回最接近该时间戳的关键帧数据信息,所返回的内容为:

{end: 3311,isKeyframe: true,keyframeTimestamp: 0.05,size: 51,start: 3260,timestamp: 0.05,
};

性能表现

对于改造前后两个SDK的性能表现,可以抽取不同的视频进行测试,验证改造效果。经过多次测试和对比,发现改造后内存占用大大减小,不再受到视频本身体积的影响,能够平稳保持在较低水平;解析视频速度大大提高,基本都能在一秒内完成。尤其对于高分辨率、大体积的视频文件,使用两个SDK进行解析时性能表现差异极大,优化效果最为明显。

以一个4K视频为例,其基本信息如下:

属性名

视频大小

1.61G

视频编码

VP9

分辨率

3840x2160

码率

10023 kb/s

对改造前后的内存占用变化及解析视频时间进行记录,可以得到如下结果:

图片

图片

改造后内存占用减少了98.34%,解析视频时间减少97.21%。改造后的mkv-demuxer性能表现良好,可以在生产环境进行使用。

Web投稿实际应用

Web投稿页在获取到用户上传的视频后会分别获取视频元信息和视频抽帧画面,用以计算推荐封面和推荐分区,推荐结果获取的速度对用户体验有着直接的影响。

  • 推荐封面的获取:从视频中截取至多30张低清截帧画面,对低清图片进行AI打分,基于打分结果排序重新截取10张高清截帧画面,作为推荐封面

图片

  • 推荐分区的获取:从视频中均匀截取10张截帧画面,对截帧画面进行打包,上传压缩包,等待AI计算,将计算结果作为推荐分区

图片

将Matroska视频的截帧方案进行升级,使用mkv-demuxer完成解封装,调用WebCodecs API对视频帧数据进行高性能解码,再结合WebGL以更快的速度绘制截帧画面,当用户投递Matroska视频时,获取到推荐封面和推荐分区的速度会变快。当视频无法解析或浏览器不支持WebCodecs API时,则会自动降级为旧方案,因此成功率不会受到影响。

数据结果

将升级方案与通用的FFmpeg+WebAssembly方案进行对比,可以得到如下结果:

  • 视频画面截帧过程耗时缩短在80%以上,视频分辨率越高效果越明显

  • 推荐封面获取总耗时减少2.29s,缩短了16.93%

  • 推荐分区获取总耗时减少8.13s,缩短了21.36%

具体数据如下:

推荐封面获取总耗时:

图片

推荐分区获取总耗时:

图片

由于推荐封面总流程和推荐分区获取总流程中除了画面截帧还包含了许多其他步骤,且受限于WebCodecs API的兼容性,实验组用户中很大比例的用户最终使用的还是旧版降级方案,因此与本地进行的截帧性能测试数据相比,耗时减少的效果得到了一定的稀释。但整体来说,新的方案对用户投稿体验的改善是比较可观的,且随着未来WebCodecs在Web平台的逐渐兼容,优化效果会越加明显。

后续规划

基于Web投稿页的实际运作需求以及mkv-demuxer现有的解析能力,可以规划如下优化策略:

一、针对Web投稿页上的Matroska视频,可以利用mkv-demuxer的解封装能力,优化视频边传边转流程。在这一过程中,mkv-demuxer能够更加快速地获取视频帧数据,并进行逐帧遍历,精确计算视频的比特率、大小等关键数据。将这些信息更早地同步至视频云,可以加速视频转码的整体流程,提升用户体验。

二、进一步提升mkv-demuxer的解析能力,完善对Matroska格式的全面支持。通过增加对Tag、Attachments等内容的解析能力,兼容对EBML Stream的解析,可以更全面地解析Matroska文件,提取更多有用的信息。

相关资料

EBML相关:

  • https://datatracker.ietf.org/doc/html/rfc8794

  • https://docs.racket-lang.org/ebml/index.html

Matroska相关:https://www.matroska.org/index.html

WebM相关:

  • https://www.webmproject.org/

  • https://cloudinary.com/guides/video-fomats/webm-format-what-you-should-know

工具推荐:

  • Hex Fiend

  • MKVToolNix

项目地址及npm包:

  • github仓库:https://github.com/SuperYanjun/mkv-demuxer

  • npm包:https://www.npmjs.com/package/mkv-demuxer

附录

1.   EBML的结构:

图片

EBML文件仅由两个部分组成:EBML header和 EBML body。EBML必须以EBML header开头,用以声明如文件类型、编写/读取EBML所需版本等重要信息,提供对EBML body处理方式的声明。而EBML body中的内容,随着EBML文件类型的不同而不同,又包含很多其他的EBML Element。

2.   Matroska基于EBML的限制:

Matroska本身是基于EBML的,只是在通用的EBML基础上制定了Matroska独特的EBML Schema,同时做出了一些规定和限制:

  • EBML Header中的DocType值必须是matroska,EBMLMaxIDLength必须是4,EBMLMaxSizeLength必须为1~8

  • 至少包含一个EBML Document,更复杂的Matroska可包含多个EBML Document,形成EBML Stream

  • 每个EBML Document必须以EBML Header开始,随后跟着一个Segment

  • 存在8个可能的顶级Top Element

3.   Matroska的Top Element:

Matroska不同的Top Element,存储了文件的不同内容:

  • SeekHead:包含了其他Top Element的位置索引,由于Matroska对8个Top Element的位置顺序没有进行规定,如果没有位置索引,想要寻找其中某个元素则需要对整个文件进行搜索。

  • Info:包含了Segment内容的整体信息,如标题、时长等。

  • Tracks:包含了每个轨道的信息,如轨道类型、分辨率、采样率、编码等。

  • Chapters:包含了所有章节内容,有了章节划分便可以在视频中进行跳转。

  • Cluster:包含了每个轨道的具体数据内容,每个Cluster中包含了至少一个SimpleBlock或BlockGroup元素,存储了视频具体的数据信息;每个Cluster都包含一个时间戳,用于表明该Cluster中第一个元素的播放开始时间,这种组织形式有利于对视频数据进行查找和进行错误恢复。

  • Cues:用于播放时进行时间索引,每个Cues元素都包含至少一个CuePoint元素,用于存储该索引下BlockGroup或SimpleBlock的位置。

  • Attachments:用于为Matroska文件增加附件,如图片、字体、网页等。

Tags:包含了对文件的描述数据和额外信息,如歌手、流派、评论、导演等。

4.   WebM与Matroska的区别:

WebM由Google推出,目标是构建一个开放的、免费的视频格式。WebM在互联网中使用非常方便,因为它通常压缩率高,体积较小,使得共享和传输媒体内容变得更加容易,也节省了存储空间。WebM是基于Matroska多媒体容器格式进行开发的,所不同的是,WebM对视频编码和音频编码作出了一定限制,并且定义了新的DocType的值:

  • 视频编码必须是VP8或VP9

  • 音频编码必须是Vorbis或Opus

  • EBML Header中的DocType必须是webm

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

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

相关文章

C#引用外部组件的常用方法

我们在开发程序过程中,时常会使用到第三方组件,比如一些通信、UI组件等。常用的引用方法有下面几种。 01 NuGet引用 NuGet是.NET的一个包管理平台,很多开源组件会通过NuGet进行管理和发布。比如我们常用的S7NetPlus等。 从NuGet中引用组件…

零基础学Python专栏文章导航站

零基础学Python专栏文章导航站 专栏导读零基础入门篇 专栏导读 本文是零基础学Python的文章导航站。专栏分为零基础入门篇、模块篇、网络爬虫篇、Web开发篇、办公自动化篇、数据分析篇… 为了方便专栏订阅者更方便的阅读专栏文章,点击链接即可跳转到具体文章&#…

FL Studio v21.2.3.4004 中文永久版网盘下载(含Key.reg注册表补丁)

软件介绍 FL Studio21水果编曲软件汉化版是一款专业的音乐制作软件,被广泛地应用于电子音乐、hip-hop、流行乐等多种音乐类型的制作。该软件提供了丰富的音频编曲工具和音乐效果器,让用户可以轻松地创作出高品质的音乐作品。同时,这也是一款…

“我,月薪4500,副业收入2w”:用Python做副业,到底有多赚钱?

现在在年轻人打工的第一目标是什么? 就是:搞钱!搞钱!搞钱! 但赚钱谈何容易,很多人工作只有一点“死”工资,每月再扣除房租水电、花呗信用卡的钱,能用的钱真的不多了,更…

Linux yum搭建Keepalived,2 台机器都有虚拟 IP 问题

文章目录 Keepalived 搭建一、安装二、keepalived配置1、配置文件详解global_defs模块参数vrrp_instance模块参数vrrp_script模块参数 2、修改配置文件3、启动服务 Tips:1️⃣问题:两台机器上面都有VIP的情况2️⃣完整配置文件 Keepalived 搭建 服务IP服务器Keepal…

van-uploader 在app内嵌的webview中的一些坑

问题: 部分版本在ios 中没有问题,但是安卓中不触发图片选择和拍照(之前是可以的,可能是没有锁定版本,重新发版导致的)。在ios中下拉文案是英文,html配置lang等于 zh 也没有用,ios里…

Linux实现cp指令(4.14)

参数&#xff1a;argc是参数总个数 argv是数组的指针&#xff0c;例如argv[0]是cp&#xff0c;argv[1]是src.c&#xff0c;argv[2]是dec.c。 思路&#xff1a; 打开src.c读src.c到buf打开/创建desc.c将buf写入des.cclose两个文件 #include <sys/types.h> #include <…

有依赖的的动态规划问题

题目 题型分析 这是比较典型的动态规划的问题。动态规划是什么呢&#xff1f;本质上动态规划是对递归的优化。例如&#xff0c;兔子数列&#xff1a;f(x) f(x - 1) f(x -2), 我们知道 f 代表了计算公式&#xff0c;这里解放思想一下&#xff0c;如果 f 替换为数组&#xff0…

信息系统项目管理师——成本管理计算专题(一)

常见考点如下: ①问项目预算、BAC、成本基准、应急储备、管理储备的含义及它们之间的区别 ②给出成本基准和管理储备求项目预算&#xff0c;或者给出预算求成本基准等等 ③看图找 PV、AC、EV、SV、CV、BAC、EAC、ETC等 ④根据题干求项目的PV、AC、EV、SV、CV、BAC、EAC、ETC等 …

k8s高可用集群部署介绍 -- 理论

部署官网参考文档 负载均衡参考 官网两种部署模式拓扑图和介绍 介绍两种高可用模式 堆叠 拓扑图如下&#xff08;图片来自k8s官网&#xff09;&#xff1a; 特点&#xff1a;将etcd数据库作为控制平台的一员&#xff0c;由于etcd的共识算法&#xff0c;所以集群最少为3个&…

产业园区综合计费解决方案/预付费系统/用户侧能源计量及收费/无人值守远程抄表收费

安科瑞薛瑶瑶18701709087 园区管理的难点 ◆计费方式多样&#xff1a;园区类型多样&#xff0c;计费逻辑存在多样&#xff0c;工业电价、商业电价、两部制电价等 ◆缴费方式多样&#xff1a;租户性质不同&#xff0c;存在公对公&#xff0c;私对公&#xff0c;线下支付&…

LeetCode 73.矩阵置零————2024 春招冲刺百题计划

给定一个 m x n 的矩阵&#xff0c;如果一个元素为 0 &#xff0c;则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。 输入&#xff1a;matrix [[1,1,1],[1,0,1],[1,1,1]] 输出&#xff1a;[[1,0,1],[0,0,0],[1,0,1]] 示例 2&#xff1a; 输入&#xff1a;matrix […

华为OD机试 - 连续天数的最高利润额(Java 2024 C卷 100分)

华为OD机试 2024C卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;A卷B卷C卷&#xff09;》。 刷的越多&#xff0c;抽中的概率越大&#xff0c;每一题都有详细的答题思路、详细的代码注释、样例测试…

高效、稳定、兼容:中国星坤MINI PCIE连接器优势明显

电子设备的性能要求日益提高&#xff0c;尤其是在数据传输和连接稳定性方面。中国星坤推出的MINI PCIE连接器&#xff0c;以其出色的性能和显著的优势&#xff0c;迅速成为行业内的佼佼者&#xff0c;为现代电子设备提供了高效、稳定的连接解决方案。 在性能方面&#xff0c;中…

限制登录Linux服务器的几种方式

一.第一种方法 通过修改TCP Wrappers服务访问控制来实现限制登录Linux 1.这里以sshd服务为例&#xff0c;配置完成后&#xff0c;只允许配置允许的IP才能ssh连接本机服务器&#xff0c;其他IP拒绝判断某一个基于tcp协议的服务是否支持tcp_wrapper&#xff0c;要先判断它是否支…

登录页界面设计详细教程:打造令人印象深刻的用户登录体验

我们日常使用的软件都会有注册和登录页&#xff0c;为什么注册登录页面是必不可少的呢&#xff1f;对于企业&#xff0c;目的是将访客转化为产品用户&#xff0c;有助于获得用户画像和各种数据&#xff0c;针对用户的个性化服务进行产品的迭代更新。对于个人&#xff0c;则根据…

鸿蒙应用开发之下拉菜单选择组件

前面学习了搜索框组件,接着下来学习下位菜单选择组件。它实现了一个下拉式的菜单,当用户点击下拉菜单中某一项,就会被选中。 这个组件的界面大体如下: 这个组件可以设置每一项的图标,以及文本显示。由于它是下拉式的菜单,所以候选的内容建议不要太多,否则会滚动比较麻烦…

Redis-键值设计

Redis-键值设计 1.设置key的规范 遵循基本格式&#xff1a;【业务名称】&#xff1a;【数据名】&#xff1a;【id】 可读性强&#xff0c;在客户端的情况下使用:如果前缀相同会分目录层级长度不超过44字节 string数据结构的三种类型&#xff0c;在44字节之内是embstring 内存…

【零基础学数据结构】链表

目录 1.链表的概念 ​编辑 2.链表的雏形 ​编辑 3.链表的组成 ​编辑 4.链表代码 4.1创建节点 4.2链表的打印 4.3链表的尾插 4.4链表的头插 4.5链表的尾删 4.6链表的头删 4.7链表的查找 4.8链表在指定位置之前插⼊数据 4.9链表在指定位置之后插⼊数据 4.9-1删除pos节点 4.9…

做抖音小店保证金可以不交吗?不交保证金,会有什么后果?

哈喽~我是电商月月 说到最赚钱的软件&#xff0c;大家第一个想的就是抖音了&#xff0c;很多不想直播&#xff0c;但又想在抖音上赚钱的人就选择了抖音小店 但普通人创业&#xff0c;开店遇到的第一个困难就是类目保证金的缴纳 几千块钱虽然能拿的出来&#xff0c;但怕就怕在…