0%

Java 压缩与解压缩操作详解:ZIP 与 GZIP 实践

在 Java 中,处理压缩文件是常见需求,尤其是在文件传输、存储优化等场景。JDK 内置了对 ZIP 和 GZIP 格式的支持,通过 java.util.zip 包中的类可实现压缩和解压缩操作。本文将详细介绍 ZIP 和 GZIP 的使用方法,包括单文件 / 多文件压缩、文件夹递归压缩及解压缩的具体实现。

ZIP 压缩与解压缩

ZIP 是一种广泛使用的归档格式,支持将多个文件或文件夹压缩到一个 .zip 文件中,且可保留目录结构。Java 中通过 ZipInputStream(解压缩)和 ZipOutputStream(压缩)实现 ZIP 操作。

ZIP 解压缩(ZipInputStream

ZipInputStream 用于读取 ZIP 文件中的条目(文件或文件夹),通过 getNextEntry() 遍历所有条目,再通过输入流读取具体内容。

核心步骤:
  1. 创建 ZipInputStream 关联 ZIP 文件输入流。
  2. 循环调用 getNextEntry() 获取每个 ZipEntry(条目)。
  3. 对每个条目,通过 ZipInputStream 读取数据(若为文件夹则跳过内容读取)。
  4. 处理完成后调用 closeEntry() 关闭当前条目,最终关闭流。
示例:解压缩 ZIP 文件到指定目录
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

public class ZipUncompressor {
/**
* 解压缩 ZIP 文件到目标目录
* @param zipFilePath ZIP 文件路径
* @param destDir 目标目录
*/
public static void unzip(String zipFilePath, String destDir) throws IOException {
// 创建目标目录(不存在则创建)
File destDirFile = new File(destDir);
if (!destDirFile.exists()) {
destDirFile.mkdirs();
}

try (ZipInputStream zis = new ZipInputStream(
new BufferedInputStream(new FileInputStream(zipFilePath)),
StandardCharsets.UTF_8)) { // 指定编码(解决中文乱码)

ZipEntry entry;
// 遍历所有条目
while ((entry = zis.getNextEntry()) != null) {
String entryName = entry.getName();
File entryFile = new File(destDirFile, entryName);

if (entry.isDirectory()) {
// 若为文件夹,创建目录
entryFile.mkdirs();
} else {
// 若为文件,读取内容并写入目标文件
// 确保父目录存在
File parentDir = entryFile.getParentFile();
if (!parentDir.exists()) {
parentDir.mkdirs();
}

try (BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream(entryFile))) {
byte[] buffer = new byte[1024];
int len;
while ((len = zis.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
}
}
zis.closeEntry(); // 关闭当前条目
}
}
}

public static void main(String[] args) throws IOException {
unzip("test.zip", "unzip_result");
System.out.println("解压缩完成!");
}
}
注意事项:
  • 中文乱码ZipInputStream 构造器需指定编码(如 StandardCharsets.UTF_8),避免条目名称中文乱码。
  • 目录处理:通过 entry.isDirectory() 判断是否为文件夹,需创建对应目录结构。
阅读全文 »

计算机中的浮点数:二进制表示与存储机制

浮点数是计算机中表示小数的核心方式,其设计巧妙但也因二进制特性存在独特的精度问题。以下从二进制小数表示、浮点数结构、IEEE 754 标准等方面详细解析:

二进制表示小数:精度问题的根源

计算机以二进制存储数据,十进制小数需转换为二进制的 “负指数幂之和”,这是浮点数精度问题的核心原因。

二进制小数的表示规则

二进制小数的整数部分与整数的二进制表示一致(如十进制5→二进制101),小数部分则通过 “2 的负指数幂累加” 表示:

  • 二进制小数 b₀.b₁b₂...bₙ 对应十进制的值为:
    b₀×2⁰ + b₁×2⁻¹ + b₂×2⁻² + ... + bₙ×2⁻ⁿ
    (其中b₀是整数位,b₁~bₙ是小数位,取值为 0 或 1)

示例

  • 二进制 1.011 转换为十进制:
    1×2⁰ + 0×2⁻¹ + 1×2⁻² + 1×2⁻³ = 1 + 0 + 0.25 + 0.125 = 1.375(精确表示)。
  • 十进制 0.1 转换为二进制:
    结果是无限循环小数 0.0001100110011...,计算机无法用有限位存储,只能近似表示(这就是0.1 + 0.2 ≠ 0.3的原因)。

浮点数的组成:符号、尾数与指数

浮点数通过 “科学计数法” 的二进制形式表示,核心是用符号、尾数、指数三部分描述一个数,格式为:

1
2
3
4
5
// 指数也叫做阶码,阶码一般是用移码表示,尾数一般是补码表示(IEEE754标准中尾数也可以用原码表示),阶码的正负叫阶符,尾数的符号位叫数符
// 阶码决定数的表示范围
// 尾数决定数的有效精度
// 对阶时,小数向大数看齐,通过较小的尾数右移实现的
符号 尾数 * 基数 ^ (指数)

如果浮点数的阶码(包括一位阶符)用R位的移码表示,尾数(包括一位数符)用M位的补码表示,则浮点数表示的数值范围时

最大正数为 $+(1-2^{-M+1})*2^{2^{R-1}-1}$

最小负数为$-1*2^{(2^{R-1}-1)}$

(计算机中基数固定为 2,因此无需存储,只需记录符号、尾数和指数)

阅读全文 »

CPU 的工作过程:从指令接收到底层执行

CPU 作为计算机的 “大脑”,其工作过程本质是通过二进制信号接收、解析并执行指令的循环流程。从引脚接收电信号到完成数据处理,每一步都依赖硬件逻辑与二进制编码的紧密配合。以下是 CPU 工作过程的详细解析:

二进制信号的传递:引脚与电信号的编码

CPU 通过外部引脚与主板、内存等部件连接,这些引脚是信号传递的 “通道”,其工作原理基于最基础的二进制逻辑:

  • 信号表示:引脚中有电流通过时,代表二进制 “1”;无电流通过时,代表二进制 “0”。
  • 信号类型:
    • 地址信号:通过引脚发送内存地址(如 “00101100” 表示某块内存的位置),用于定位要读取的指令或数据。
    • 数据信号:双向传递二进制数据(如 “11001010” 可能代表一个整数或指令代码)。
    • 控制信号:传递 “读 / 写”“中断” 等控制指令(如某引脚高电平表示 “读取内存”,低电平表示 “写入内存”)。

这些二进制信号通过总线(地址总线、数据总线、控制总线)有序传输,构成 CPU 与外部交互的基础。

CPU 工作的核心循环:取指→译码→执行→写回

CPU 的工作过程可概括为 “取指令→指令译码→执行指令→结果写回” 的循环(Fetch-Decode-Execute-Writeback),每一步都由内部组件协同完成:

1. 取指令(Fetch)

  • 目标:从内存中读取下一条要执行的指令。
  • 过程:
    1. 程序计数器(PC)提供指令在内存中的地址(如二进制地址 “10001000”),通过地址总线发送到内存。
    2. 控制总线发出 “读” 信号,通知内存准备数据。
    3. 内存根据地址找到对应的指令(如二进制 “00000101”,代表某条加法指令),通过数据总线传输到 CPU。
    4. 指令被暂存到指令寄存器(IR),同时 PC 自动递增(指向相邻的下一条指令地址),为下一次取指做准备。
阅读全文 »

BlockingQueue 阻塞队列:线程同步与并发协作的核心组件

BlockingQueue(阻塞队列)是 Java 并发包(JUC)中用于线程间安全通信的核心组件,其核心特性是当队列满时阻塞生产者线程,当队列空时阻塞消费者线程,完美适配生产者 - 消费者模型。本文将系统解析 BlockingQueue 接口及其主要实现类,深入探讨其设计原理、核心方法及适用场景。

BlockingQueue 接口核心定义

BlockingQueue 继承自 Queue 接口,新增了阻塞式插入 / 移除方法,确保线程安全的同时简化了并发协作。其核心方法可分为三类:非阻塞方法阻塞方法超时方法

核心方法分类

操作类型 非阻塞方法(满 / 空时抛异常) 非阻塞方法(满 / 空时返回特殊值) 阻塞方法(满 / 空时阻塞) 超时方法(满 / 空时等待超时)
插入 add(E e) offer(E e) put(E e) offer(E e, long timeout, TimeUnit unit)
移除 remove() poll() take() poll(long timeout, TimeUnit unit)
查看 element() peek() - -

关键特性

  • 不允许插入 null 元素(会抛出 NullPointerException);
  • 支持线程中断(阻塞方法声明了 throws InterruptedException);
  • 提供批量操作(如 drainTo(Collection) 一次性取出所有元素)。

主要实现类详解

阻塞队列

BlockingQueue 有多个实现类,适用于不同场景。以下是最常用的 5 种实现:

ArrayBlockingQueue:基于数组的有界阻塞队列

核心特性
  • 底层结构:数组(容量固定,创建时需指定);
  • 排序方式:FIFO(先进先出);
  • 并发控制:单把全局锁(ReentrantLock)+ 两个条件变量(notEmpty/notFull);
  • 公平性:支持公平锁(按线程等待顺序访问)和非公平锁(默认)。
源码解析
阅读全文 »

Linux perf 命令:性能分析的瑞士军刀

perf 是 Linux 系统自带的性能分析工具,基于内核性能事件机制,可用于监控 CPU 使用率、缓存命中、函数调用开销、上下文切换等关键性能指标。它是排查程序性能瓶颈(如 CPU 密集、缓存失效、函数耗时过长)的核心工具,尤其适合分析 Java 等应用的底层运行情况。

perf 基本用法

命令格式

1
perf [子命令] [选项] [目标程序/进程ID]

perf 包含多个子命令,分别用于不同场景的性能分析,最常用的包括 stat(统计事件)、record(记录事件)、report(生成报告)等。

核心子命令

子命令 功能描述
perf stat 统计指定事件(如 CPU 周期、缓存命中)的发生次数和频率,适合快速定位性能瓶颈。
perf record 记录程序运行时的性能事件数据,生成二进制文件(默认 perf.data),用于后续分析。
perf report 解析 perf record 生成的文件,展示函数级别的性能开销分布(如 CPU 占用率)。
perf top 实时显示当前系统中消耗 CPU 最多的函数或指令,类似 top 但更深入。

常用场景与示例

perf stat:快速统计性能指标

阅读全文 »