小菜鸟

java菜鸟号正在起航

POI 读取 Excel 合并单元格内容:完整解决方案

在处理包含合并单元格的 Excel 时,直接读取非左上角的单元格会返回空值(因为合并单元格仅左上角单元格保存数据)。本文将详细讲解如何识别合并单元格、读取合并区域的内容,并提供通用工具类,确保在各种合并场景下准确获取数据。

合并单元格的特性与读取难点

合并单元格的存储规则

Excel 中合并单元格(如 CellRangeAddress 定义的区域)仅在左上角第一个单元格中存储数据,其他被合并的单元格均为空。例如:

  • 合并 A1:A3(3 行 1 列)后,仅 A1 有值,A2A3 为空;
  • 合并 B2:D4(3 行 3 列)后,仅 B2 有值,B3C2 等均为空。

直接读取的问题

若直接通过 row.getCell(column) 读取合并区域内的非左上角单元格,会返回 null 或空值,导致数据丢失。因此,需要:

  • 先判断单元格是否属于合并区域;
  • 若属于,则读取合并区域左上角单元格的值。

核心工具类实现

以下工具类提供合并单元格的判断、内容读取等功能,兼容 .xls.xlsx 格式。

阅读全文 »

POI读取excel报错解决方案:兼容 xls 与 xlsx 格式

在使用 POI 读取 Excel 文件时,因文件格式(.xls.xlsx)不同常出现格式不匹配的错误。本文将详细解析错误原因,提供通用解决方案,并介绍 WorkbookFactory 的使用技巧,帮助你无缝兼容两种格式。

错误原因分析

错误信息解读

当使用 POIFSFileSystem 读取 .xlsx 文件时,会出现以下错误:

The supplied data appears to be in the Office 2007+ XML. You are calling the part of POI that deals with OLE2 Office Documents. You need to call a different part of POI to process this data (eg XSSF instead of HSSF)
  • 原因:
    • .xls(Excel 2003 及以前)基于 OLE2 格式,需用 HSSF 相关类(如 POIFSFileSystemHSSFWorkbook)处理;
    • .xlsx(Excel 2007 及以后)基于 XML 格式,需用 XSSF 相关类(如 XSSFWorkbook)处理。
    • 若用 HSSF 类读取 .xlsx 文件(或反之),会因格式不匹配报错。

常见错误用法

手动判断文件格式并选择对应类,容易因判断逻辑错误导致兼容问题:

阅读全文 »

Spark 性能优化:代码写法先调,参数其次,最后加资源

Spark 任务慢,三个方向排查:

排查顺序 优化方向 典型手段
第一 代码层面 换算子、加缓存、广播变量
第二 参数层面 调并行度、调内存分配
第三 资源层面 加 Executor、加内存

不要一上来就加资源。先检查代码有没有写错,再看参数有没有配错,最后才考虑加机器

代码层面:90% 的性能问题出在这

1. 能用 reduceByKey,就别用 groupByKey

算子 行为 Shuffle 数据量
groupByKey 所有数据 Shuffle 到 Reduce 端,再聚合 全量数据
reduceByKey Map 端先预聚合,再 Shuffle 聚合后的数据
//  低效:所有数据都 Shuffle
rdd.groupByKey().mapValues(_.sum)

//  高效:Map 端预聚合
rdd.reduceByKey(_ + _)

2. 能用 mapPartitions,就别用 map

map 每条数据调用一次函数,mapPartitions 每个分区调用一次。

//  错误:每条数据创建一次数据库连接
rdd.map(record => {
  val conn = getConnection()   // 重复创建
  process(record, conn)
})

//  正确:每个分区创建一次连接
rdd.mapPartitions(iter => {
  val conn = getConnection()   // 一个分区一次
  iter.map(record => process(record, conn))
})

3. 重复使用的 RDD 要缓存

同一个 RDD 被多次使用(比如多个 Action),每次都会重算。

// 错误: 两次 action 都重算
val rdd = sc.textFile("hdfs://data.txt").flatMap(...).map(...)
rdd.count()
rdd.collect()

// 正确: 中间结果缓存
val rdd = sc.textFile("hdfs://data.txt").flatMap(...).map(...).cache()
rdd.count()
rdd.collect()

4. 广播大变量,不要每个 Task 传一份

如果每个 Task 都要用同一个大对象(比如字典表),用广播变量。

// 错误: 每个 Task 都发一份(100 个 Task = 100 份)
val dict = Map(...)   // 大对象
rdd.map(record => dict.get(record.key))

// 正确: 广播到每个 Executor(10 个 Executor = 10 份)
val bcDict = sc.broadcast(dict)
rdd.map(record => bcDict.value.get(record.key))
阅读全文 »

AES 对称加密算法详解

AES(Advanced Encryption Standard,高级加密标准)是目前应用最广泛的对称加密算法之一,作为 DES 的替代者,它具有更高的安全性和效率,被广泛用于金融、通信、数据存储等领域。

AES 的核心特性

  • 对称加密:加密和解密使用相同的密钥,这意味着通信双方需要共享同一个密钥。
  • 安全性高:相比 DES 算法,AES 的密钥长度更长(支持 128 位、192 位、256 位),抗暴力破解能力更强。
  • 效率优异:在硬件和软件环境中都能高效实现,适合处理大量数据的加密。
  • 应用广泛:是美国联邦政府采用的加密标准,也是众多行业的首选加密方案。

代码解析:AES 加密与解密过程

加密流程(aesEncode方法)

  1. 初始化随机数生成器

    SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
    random.setSeed("PASSWORD_CRYPT_KEY".getBytes());

    通过指定种子(这里使用 “PASSWORD_CRYPT_KEY”)确保密钥生成的一致性,相同的种子会生成相同的密钥。

  2. 生成密钥

阅读全文 »

消息摘要算法详解

消息摘要算法是密码学中的重要组成部分,主要用于验证数据的完整性,确保数据在传输或存储过程中未被篡改。这类算法通过对原始数据进行处理,生成一个固定长度的 “摘要”(也称为哈希值或散列值),该摘要具有唯一性和不可逆性,成为数据的 “数字指纹”。

消息摘要算法的核心特性

  • 单向性:从原始数据生成摘要很容易,但无法从摘要反推原始数据。
  • 唯一性:不同的原始数据几乎不可能生成相同的摘要(哈希碰撞概率极低)。
  • 固定长度:无论原始数据大小如何,生成的摘要长度固定。
  • 敏感性:原始数据的微小变化会导致摘要发生巨大改变。

三大系列消息摘要算法

MD 算法(Message Digest)

MD 算法是早期的消息摘要算法系列,由 Ronald L. Rivest 设计,主要代表为MD5

MD5 的特点
  • 生成128 位的信息摘要(二进制),转换为十六进制后为 32 位字符串。
  • 运算速度快,但安全性已被攻破(存在实际的碰撞案例)。
  • 目前更多用于非安全性要求极高的场景,如文件校验、数据一致性验证等。
MD5 的 Java 实现示例
阅读全文 »
0%