分布式文件系统Minio

由于本人目前在公司中主要负责的业务之一就是文件图片的上传和下载系统,所以为了提升自我能力,我在业余的时间对分布式文件系统Minio源码进行了学习,希望对之后的工作和学习有所帮助。下面对Minio进行介绍。
使用场景:
用于互联网海量非结构化数据的存储需求。

  • 电商网站:海量电商图片
  • 视频网站:海量视频资源
  • 网盘:海量文件
  • 社交网站:海量社交图片

为什么不使用数据库来存储海量的图片等非结构化数据呢,而是使用分布式文件系统呢?

从成本上来说,数据库的存储成本,比如MySQL或者PostgreSQL,每个字段都要存储,对大文件来说,存储效率低,而且备份和恢复可能麻烦。另外,数据库的索引机制不适合大块数据,读取速度可能慢。

分布式文件系统比如HDFS、Ceph或者对象存储如AWS S3,它们设计用来处理大量非结构化数据,提供高吞吐量,数据分片存储,容易扩展。还有CDN加速下载,这对图片这类需要快速访问的数据很有利。另外,分布式文件系统具有高吞吐I/O的优点,可以针对大规模数据的并行读写优化,适合批量处理场景。
Minio介绍
MinIO 是一个开源的高性能分布式对象存储系统,专为云原生和大规模非结构化数据存储而设计。它兼容 Amazon S3 API,支持海量数据的存储与管理,适用于图片、视频、日志文件、备份数据等场景。另外,Minio使用go语言开发的,go语言天然就有跨平台特性和性能高的特点.
Minio的核心特性

  1. 高性能

MinIO 在标准硬件上可实现高达 183 GB/s 的读取速度和 171 GB/s 的写入速度,适合作为主存储层处理 Spark、TensorFlow 等高性能计算任务28。

  1. 兼容性

完全兼容 Amazon S3 API,支持无缝集成现有 S3 生态工具(如 AWS CLI、SDK),并支持 S3 Select 功能,可直接查询存储的数据18。

  1. 可扩展性

采用分布式架构,支持通过添加节点线性扩展存储容量和吞吐量,同时支持纠删码技术(Reed-Solomon Code),允许在丢失半数存储节点的情况下恢复数据34。

  1. 安全性

提供数据加密(传输加密和静态加密)、访问控制(IAM 策略、RBAC)及身份验证(JWT、OAuth2)功能,确保数据安全13。

  1. 轻量易用

单二进制文件部署,支持多种操作系统(Windows、Linux、macOS)和容器化平台(Docker、Kubernetes),并提供可视化控制台管理

与HDFS相比

​HDFS 的局限:架构复杂、扩展性差、小文件性能差,逐渐被云原生方案取代。
​MinIO 的崛起:凭借轻量、S3 兼容性和纠删码技术,成为现代分布式存储的首选。
​混合架构趋势:部分企业将冷数据存至 HDFS,热数据迁移至 MinIO,兼顾成本与性能
企业通过冷热分层存储实现成本与性能的最优解:

  • HDFS可以无缝衔接Hdoop生态,它可以保证保障冷数据低成本存储:借助 EC 和压缩技术,在 Hadoop 生态内完成批量分析。
  • MinIO 赋能热数据高性能访问:通过云原生架构和 S3 协议兼容性,支撑实时业务需求。

此策略尤其适合需同时处理历史数据分析与实时交互的场景(如金融风控、物联网平台),是传统大数据架构向云原生演进的关键过渡方案。

SpringBoot使用minio实现文件上传下载
本代码以minio的单服务器模式举例,对于单服务器模式,如果上传大文件(>500M),为了提升稳定性和效率,文件的分片合并需要由我们自己来完成。
为什么单机模式下minio没有提供分片呢?
如果是分布式部署minio,minio会将数据分片,提升存储扩展性和并行处理能力,通过将数据分散到多个节点或磁盘,实现负载均衡和容错。而单机模式中,所有数据存储在单一服务器或本地磁盘上,无需跨节点协作,因此分片的分布式优势(如并行读写、冗余恢复)失去意义。
​一、客户端分片生成(dataShard 的获取)​

File file = new File("/path/to/large-file.zip");
long chunkSize = 5 * 1024 * 1024; // 5MB
long totalSize = file.length();
int totalShards = (int) Math.ceil((double) totalSize / chunkSize);List<byte[]> dataShard = new ArrayList<>();
try (InputStream is = new FileInputStream(file)) {for (int i = 0; i < totalShards; i++) {int bytesRead;byte[] buffer = new byte[(int) chunkSize];bytesRead = is.read(buffer);// 去除末尾空字节(最后一片可能不足 chunkSize)byte[] trimmedBuffer = Arrays.copyOf(buffer, bytesRead);dataShard.add(trimmedBuffer);}
}

二、分片上传流程

//1、初始化上传任务
// 创建上传 ID
String uploadId = minioClient.initMultipartUpload("my-bucket", "large-file.zip");
//2、并发上传分片
List<Part> parts = new ArrayList<>();
for (int i = 0; i < dataShard.size(); i++) {byte[] chunk = dataShard.get(i);int partNumber = i + 1; // 分片编号从 1 开始// 上传分片并获取 ETagString etag = minioClient.uploadPart("my-bucket", "large-file.zip", uploadId, partNumber,new ByteArrayInputStream(chunk), chunk.length);parts.add(new Part(partNumber, etag));
}
//3、合并分片
minioClient.completeMultipartUpload("my-bucket", "large-file.zip", uploadId, parts
);

分片唯一标识

  • ​ETag 校验:每个分片上传后,MinIO 返回分片的唯一标识 ETag(即 MD5 哈希值)。
  • ​客户端缓存:需本地记录分片顺序和 ETag,用于断点续传和合并校验。
    三、分片下载与组装
//1、直接下载完整文件
InputStream stream = minioClient.getObject(GetObjectArgs.builder().bucket("my-bucket").object("large-file.zip").build()
);
//2、断点续传下载
// 从第 5MB 开始下载剩余部分
GetObjectArgs args = GetObjectArgs.builder().bucket("my-bucket").object("large-file.zip").offset(5 * 1024 * 1024L).build();
InputStream stream = minioClient.getObject(args);
//3、客户端手动组装
#列出所有分片
ListPartsResponse partsResponse = minioClient.listParts("my-bucket", "large-file.zip", uploadId
);
List<Part> parts = partsResponse.parts();
#按顺序下载分片
List<byte[]> downloadedShards = new ArrayList<>();
for (Part part : parts) {InputStream stream = minioClient.getPart("my-bucket", "large-file.zip", uploadId, part.partNumber());downloadedShards.add(IOUtils.toByteArray(stream));
}
#本地合成分片
try (FileOutputStream fos = new FileOutputStream("merged-file.zip")) {for (byte[] shard : downloadedShards) {fos.write(shard);}
}

Minio上传下载文件的核心流程和代码执行路径

一、上传文件流程(PUT请求)
1、请求入口(HTTP路由)

  • 触发代码internal/api/router.go
    • 路由 GET /{bucket}/{object...} 映射到 objectHandlers.GetObjectHandler

关键函数

// internal/api/object-handlers.go
func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Request) {// 处理上传逻辑
}

2、认证与权限校验

  • ​触发代码:触发代码:internal/auth/ 和中间件
    • 请求会通过中间件链(如 internal/handler/auth-handler.go)进行身份验证(IAM、STS)和桶权限校验(Bucket Policy)。
    • 校验客户端是否有 s3:PutObject 权限。

关键函数

// internal/handler/auth-handler.go
func AuthMiddleware(next http.Handler) http.Handler {// 校验请求签名和权限
}

3、数据分片读取与解码

  • 触发代码:internal/erasure/encode.go
    • 如果是分布式模式(Erasure Coding),对象会被分片为 N+M 块(数据块 + 校验块)。

    • 使用 Reed-Solomon 算法进行编码(依赖库 klauspost/reedsolomon)。

关键函数

// internal/erasure/encode.go
func (e *Erasure) Encode(ctx context.Context, data []byte) ([][]byte, error) {// 数据分片和纠删码编码
}

4、数据持久化到磁盘

  • 触发代码:internal/storage/posix.go
    • 将分片后的数据块写入磁盘(每个分片存储为单独的文件)。
    • 路径格式:<disk>/<bucket>/<object>/part.<shard-id>

关键函数

// internal/storage/posix.go
func (s *posix) WriteFile(ctx context.Context, volume, path string, buf []byte) error {// 写入分片文件
}

5、元数据更新

  • ​触发代码:internal/metacache/
    • 更新对象的元数据(如 xl.meta 文件),记录分片位置、哈希值、时间戳等。
    • 元数据存储在 .minio.sys/buckets/<bucket>/<object>/xl.meta

关键代码

// internal/metacache/metacache.go
func (m *metaCache) UpdateMetadata(oi ObjectInfo) error {// 更新元数据
}

6、响应客户端

  • 触发代码:internal/api/object-handlers.go
    • 返回 HTTP 200 OK 或错误码(如 403 Forbidden 或 500 Internal Server Error)。

​二、下载文件(GET 请求)流程
​1. 请求入口(HTTP 路由)​

  • ​触发代码:internal/api/router.go
    • 路由 GET /{bucket}/{object…} 映射到 objectHandlers.GetObjectHandler。
      ​关键函数:
// internal/api/object-handlers.go
func (api objectAPIHandlers) GetObjectHandler(w http.ResponseWriter, r *http.Request) {// 处理下载逻辑
}

​2. 认证与权限校验

  • ​触发代码:同上传流程,校验 s3:GetObject 权限。

​3. 数据分片读取与解码

  • ​触发代码:internal/erasure/decode.go
    • 从多个磁盘读取分片数据(至少需要 N 个分片才能解码)。
    • 如果某些分片损坏或丢失,通过纠删码算法恢复数据。
      ​关键函数:
// internal/erasure/decode.go
func (e *Erasure) Decode(ctx context.Context, shards [][]byte) ([]byte, error) {// 解码并合并数据
}

​4. Bitrot 校验(可选)​

  • ​触发代码:internal/hashicorp/bitrot.go
    • 校验数据块的哈希值,检测磁盘静默错误(Bitrot)。
    • 若数据损坏,触发自动修复(通过后台 heal 服务)。

关键函数:

// internal/hashicorp/bitrot.go
func VerifyShard(algorithm BitrotAlgorithm, checksum []byte, data []byte) bool {// 校验数据完整性
}

5、数据流式返回

  • ​触发代码:internal/http/response-writer.go
    • 将解码后的数据通过 HTTP 分块传输(Chunked Encoding)流式返回。

​关键逻辑

// internal/http/response-writer.go
func (w *responseWriter) Write(p []byte) (int, error) {// 流式写入响应体
}

​三、关键设计细节
​1. 并发控制

  • ​上传:通过 internal/sync/ 中的互斥锁(Mutex)或分布式锁(Dsync)保证并发写入的一致性。
  • ​下载:无锁设计,允许多个客户端并发读取。

​2. 错误处理

  • 上传失败:若部分分片写入失败,客户端会收到错误码,未完成的分片会被垃圾回收(internal/background/gc.go)。
  • ​下载失败:若无法读取足够分片(如节点宕机),返回 404 Not Found 或 503 Service Unavailable。

​3. 分布式模式下的流程

  • ​节点选择:使用一致性哈希算法选择存储节点。
  • ​数据修复:后台服务 internal/background/heal.go 自动检测并修复丢失的分片。

源码分析

在Minio中发起上传请求时,核心流程是数据的分片和编码、分布式写入部分。
​一、分片与编码阶段
​1、纠删码分片逻辑
MinIO 使用 Reed-Solomon 算法将原始数据切分为 ​数据块(Data Shards)​ 和 ​校验块(Parity Shards)​。例如,若配置为 4+2 纠删码,一个 100MB 的文件会被切分为 4 个 25MB 的数据块,并生成 2 个 25MB 的校验块。

​代码实现:核心逻辑位于 cmd/erasure-code.goErasure.Encode() 方法,通过 splitData 方法分片,再调用纠删码编码器生成校验块。

//数据通过 EncodeData() 方法被分片为数据块和校验块,调用 Reed-Solomon 算法完成编码
func (e *Encoder) EncodeData(data []byte) ([][]byte, error) {shards, err := e.Split(data)  // 数据分片if err != nil {return nil, err}return e.Encode(shards)       // 生成校验块
}

2、​哈希定位存储节点
分片后的每个块通过 ​一致性哈希算法 确定目标节点和磁盘。例如,使用 hashKey() 函数(位于 cmd/hasher.go)对对象键(Key)进行哈希计算,再通过取模运算分配到具体磁盘。

//每个分片通过一致性哈希算法(hashKey())确定存储位置,源码位于 cmd/hasher.go
func hashKey(key string, totalDisks int) int {hash := fnv.New32a()hash.Write([]byte(key))return int(hash.Sum32()) % totalDisks
}

​示例:若集群有 6 个节点,哈希值对 6 取模后,确定数据块应写入的节点索引。
并行写入阶段
二、​多线程并行传输
1、客户端或服务端通过 ​并发协程(Goroutine)​ 将分片后的数据块和校验块同时发送到目标节点。例如,4 个数据块和 2 个校验块会被 6 个独立的线程并行传输。

​性能优化:通过 Go 语言的并发模型,充分利用网络带宽和磁盘 I/O,避免单线程瓶颈。

//使用 Go 的 errgroup 包启动多个协程,每个协程负责一个分片的传输,并行写入不同节点
g := new(errgroup.Group)
for _, shard := range shards {g.Go(func() error {return node.Write(shard)  // 并行写入分片})
}
if err := g.Wait(); err != nil {// 错误处理
}

2、​分布式存储协议
节点间通过 ​HTTP/HTTPS 协议 进行通信,每个分片作为独立对象写入目标节点的磁盘。写入时遵循以下步骤:

  • ​临时目录写入:分片先被写入节点的临时目录(如 tmp),完成校验后通过原子操作重命名为正式存储路径。
  • ​元数据一致性:每个分片的元数据(如哈希值、分片索引)同步记录到所有相关节点,确保后续读取时能快速定位。
分片先写入临时目录(如 .minio/tmp),通过文件系统级 rename 操作原子提交,避免写入过程中断导致数据不一致

3、​冗余与一致性校验

  • ​冗余写入:每个分片根据纠删码规则分布在多个节点,例如 4+2 配置下,每个数据块至少存在于 2 个节点(通过哈希副本策略)。
  • 校验机制:写入完成后,MinIO 会验证所有分片的哈希值(如 SHA-256),确保数据完整性。若某节点写入失败,立即触发重试或切换备用节点。

总结
MinIO 的并行写入通过 ​分片编码、一致性哈希定位、协程并发传输 实现,其源码设计特点包括:

  • 高效并发:利用 Go 协程和 errgroup 实现无锁并行;
  • ​原子性保障:临时文件 + rename 操作确保写入完整性; ​
  • 智能容错:动态重试、负载均衡与自动修复机制。

这种设计使其在分布式场景下能充分发挥硬件性能,同时保障数据可靠性。

个人实现

我对要MinIO 并行写入的核心逻辑(分片、哈希定位、多节点并发写入),使用Java进行了简单实现。以下是关键实现步骤及代码示例
​一、核心架构设计
​1. 模块划分

// 模块职责说明
public class MinIOParallelWriter {// 分片编码器(模拟纠删码)private ErasureEncoder encoder; // 哈希定位器private NodeHasher nodeHasher;// 线程池管理并发写入private ExecutorService executor; 
}

二、分片与编码实现
​1. 数据分片(模拟纠删码)​

public class ErasureEncoder {// 数据分片(示例:4+2 配置)public List<byte[]> splitAndEncode(byte[] data) {int dataShards = 4, parityShards = 2;List<byte[]> shards = new ArrayList<>();// 模拟分片(实际需实现 Reed-Solomon 算法)for (int i = 0; i < dataShards + parityShards; i++) {shards.add(Arrays.copyOfRange(data, i * shardSize, (i+1)*shardSize));}return shards;}
}

三、并发写入实现

  1. 哈希定位存储节点
public class NodeHasher {// 一致性哈希计算(类似 MinIO 的 hashKey 逻辑)public int hashKey(String objectKey, int nodeCount) {return objectKey.hashCode() % nodeCount;}
}
  1. 多线程写入控制
public class MinIOParallelWriter {public void writeParallel(byte[] data, String objectKey) {List<byte[]> shards = encoder.splitAndEncode(data);List<CompletableFuture<Void>> futures = new ArrayList<>();for (int i = 0; i < shards.size(); i++) {int nodeIndex = nodeHasher.hashKey(objectKey + i, nodeCount);futures.add(CompletableFuture.runAsync(() -> {uploadShardToNode(shards.get(i), nodeIndex, objectKey);}, executor));}CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();}private void uploadShardToNode(byte[] shard, int nodeIndex, String objectKey) {// 调用 MinIO SDK 写入特定节点(需自定义节点路由)minioClient.putObject(PutObjectArgs.builder().bucket("bucket").object(objectKey + "_shard_" + nodeIndex).stream(new ByteArrayInputStream(shard), shard.length, -1).build());}
}

关键点:

  • 使用 CompletableFuture 实现非阻塞并发
  • 通过 objectKey + “shard” + nodeIndex 模拟分片存储路径

​四、优化方向
​1. 动态节点发现

// 结合服务发现机制(如 Consul)
public class DynamicNodeManager {public List<String> getActiveNodes() {// 从注册中心获取在线节点}
}
  1. 原子性写入保障
// 写入临时路径后原子重命名
private void atomicCommit(String tempPath, String finalPath) {Path source = Paths.get(tempPath);Files.move(source, source.resolveSibling(finalPath), StandardCopyOption.ATOMIC_MOVE);
}
  1. 故障重试机制
// 指数退避重试(参考 Resilience4j)
RetryConfig config = RetryConfig.custom().maxAttempts(3).waitDuration(Duration.ofMillis(500)).build();
Retry retry = Retry.of("uploadRetry", config);

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

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

相关文章

如何高效解决 Java 内存泄漏问题方法论

目录 一、系统化的诊断与优化方法论 二、获取内存快照&#xff1a;内存泄漏的第一步 &#xff08;一&#xff09;自动生成 Heap Dump &#xff08;二&#xff09;手动生成 Heap Dump 三、导入分析工具&#xff1a;MAT 和 JProfiler &#xff08;一&#xff09;MAT (Memor…

新手村:数据预处理-异常值检测方法

机器学习中异常值检测方法 一、前置条件 知识领域要求编程基础Python基础&#xff08;变量、循环、函数&#xff09;、Jupyter Notebook或PyCharm使用。统计学基础理解均值、中位数、标准差、四分位数、正态分布、Z-score等概念。机器学习基础熟悉监督/无监督学习、分类、聚类…

大模型-提示词调优

什么是提示词 提示词&#xff08;Prompt&#xff09;在大模型应用中扮演着关键角色&#xff0c;它是用户输入给模型的一段文本指令 。简单来说&#xff0c;就是我们向大模型提出问题、请求或描述任务时所使用的文字内容。例如&#xff0c;当我们想让模型写一篇关于春天的散文&a…

VS2022输入 scanf 报错解决方法

1.第一种解决办法&#xff08;不推荐&#xff09; •将 scanf 替换为 scanf_s •scanf_s 是VS提供的一个函数&#xff0c;scanf_s函数的使用和scanf是有区别的 •scanf_s 是VS提供的一个函数&#xff0c;其他的编译器可能不认识这个函数&#xff0c;那么我们所写的代码就存在跨…

鸿蒙开发-一多开发之媒体查询功能

在HarmonyOS中&#xff0c;使用ArkTS语法实现响应式布局的媒体查询是一个强大的功能&#xff0c;它允许开发者根据不同的设备特征&#xff08;如屏幕尺寸、屏幕方向等&#xff09;动态地调整UI布局和样式。以下是一个使用媒体查询实现响应式布局的实例&#xff1a; 1. 导入必要…

火语言RPA--列表项内容获取

【组件功能】&#xff1a;获取列表中某项数据内容 配置预览 配置说明 获取 获取数据方式 首项&#xff1a;列表第一条数据 末项&#xff1a;列表最后一条数据 随机项&#xff1a;随机获取列表中一条数据 指定索引项&#xff1a;根据索引获取列表对象中数据。 索引项目位置 …

基于Python+Flask+MySQL+HTML的爬取豆瓣电影top-250数据并进行可视化的数据可视化平台

FlaskMySQLHTML 项目采用前后端分离技术&#xff0c;包含完整的前端&#xff0c;以flask作为后端 Pyecharts、jieba进行前端图表展示 通过MySQL收集格列数据 通过Pyecharts制作数据图表 这是博主b站发布的详细讲解&#xff0c;感兴趣的可以去观看&#xff1a;【Python爬虫可…

解锁MySQL 8.0.14源码调试:Mac 11.6+CLion 2024.3.4实战指南

文章目录 解锁MySQL 8.0.41源码调试&#xff1a;Mac 11.6CLion 2024.3.4实战指南前期准备环境搭建详细步骤安装 CLion安装 CMake 3.30.5准备 MySQL 8.0.14 源码配置 CMake 选项构建 MySQL 项目 调试环境配置与验证配置 LLDB 调试器启动调试验证调试环境 总结与拓展 解锁MySQL 8…

81.HarmonyOS NEXT 状态管理与响应式编程:@Observed深度解析

温馨提示&#xff1a;本篇博客的详细代码已发布到 git : https://gitcode.com/nutpi/HarmonyosNext 可以下载运行哦&#xff01; HarmonyOS NEXT 状态管理与响应式编程&#xff1a;Observed深度解析 文章目录 HarmonyOS NEXT 状态管理与响应式编程&#xff1a;Observed深度解析…

【快速入门】MyBatis

一.基础操作 1.准备工作 1&#xff09;引入依赖 一个是mysql驱动包&#xff0c;一个是mybatis的依赖包&#xff1a; <dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><vers…

RabbitMQ可靠性进制

文章目录 1.生产者可靠性生产者重连生产者确认小结 2. MQ的可靠性数据持久化LazyQueue小结 3. 消费者的可靠性消费者确认机制消费者失败处理方案业务幂等性唯一消息ID业务判断 兜底方案业务判断 兜底方案 1.生产者可靠性 生产者重连 在某些场景下由于网络波动&#xff0c;可能…

【专项测试】限流测试

简介 限流的目的是防止恶意请求、恶意攻击&#xff0c;或者防止流量超出系统峰值时保护系统免受灭顶之灾。 限流的具体做法是是通过对并发访问/请求进行限速或者对一个时间窗口的请求进行限速在保护系统&#xff0c;一旦达到限制速率则可以拒绝服务&#xff08;定向到错误页&a…

Qt-D指针与Q指针的设计哲学

文章目录 前言PIMLP与二进制兼容性D指针Q指针优化d指针继承Q_D和Q_Q 前言 在探索Qt源码的过程中会看到类的成员有一个d指针&#xff0c;d指针类型是一个private的类&#xff0c;这种设计模式称为PIMPL&#xff08;pointer to implementation&#xff09;&#xff0c;本文根据Q…

ctf web入门知识合集

文章目录 01做题思路02信息泄露及利用robots.txt.git文件泄露dirsearch ctfshow做题记录信息搜集web1web2web3web4web5web6web7web8SVN泄露与 Git泄露的区别web9web10 php的基础概念php的基础语法1. PHP 基本语法结构2. PHP 变量3.输出数据4.数组5.超全局变量6.文件操作 php的命…

LangChain 工作流编排

文章目录 LCEL流式调用案例invoke的异步调用异步流中的事件 LCEL LangChain Expression Language&#xff0c;是一种强大的工作流编排工具&#xff0c;可以从基本组件构建复杂的任务链&#xff08;Chain&#xff09;&#xff0c;有如下亮点&#xff1a; 流式支持&#xff1b;…

PyTorch 深度学习实战(14):Deep Deterministic Policy Gradient (DDPG) 算法

在上一篇文章中&#xff0c;我们介绍了 Proximal Policy Optimization (PPO) 算法&#xff0c;并使用它解决了 CartPole 问题。本文将深入探讨 Deep Deterministic Policy Gradient (DDPG) 算法&#xff0c;这是一种用于连续动作空间的强化学习算法。我们将使用 PyTorch 实现 D…

3.14-1列表

列表 一.列表的介绍和定义 1 .列表 类型: <class list> 2.符号:[] 3.定义列表: 方式1:[] 通过[] 来定义 list[1,2,3,4,6] print(type(list)) #<class list> 方式2: 通过list 转换 str2"12345" print(type(str2)) #<class str> list2lis…

Java集合 - HashMap

HashMap 是 Java 集合框架中的一个重要类&#xff0c;位于 java.util 包中。它实现了 Map 接口&#xff0c;基于哈希表的数据结构来存储键值对&#xff08;key-value pairs&#xff09;。HashMap 允许使用 null 作为键和值&#xff0c;并且是非同步的&#xff08;非线程安全的&…

有效的山脉数组 力扣941

一、题目 给定一个整数数组 arr&#xff0c;如果它是有效的山脉数组就返回 true&#xff0c;否则返回 false。 让我们回顾一下&#xff0c;如果 arr 满足下述条件&#xff0c;那么它是一个山脉数组&#xff1a; arr.length > 3在 0 < i < arr.length - 1 条件下&am…

本地部署Spark集群

部署Spark集群大体上分为两种模式&#xff1a;单机模式与集群模式 大多数分布式框架都支持单机模式&#xff0c;方便开发者调试框架的运行环境。但是在生产环境中&#xff0c;并不会使用单机模式。 下面详细列举了Spark目前支持的部署模式。 &#xff08;1&#xff09;Local…