顾乔芝士网

持续更新的前后端开发技术栈

Rust 网页压缩之 Zstd:比 gzip 快,比 Brotli 省时间的 “智能压缩袋”

Rust 网页压缩之 Zstd:比 gzip 快,比 Brotli 省时间的 “智能压缩袋”

先给 Zstd 整个自我介绍:这货是压缩界的 “全能选手”?

之前咱们聊过:



  • gzip 像 “手动真空袋”,速度快但压缩率一般;
  • Brotli 像 “液压真空机”,压缩率高但费时间。



那 Zstd(发音 “Z-standard”)呢?它就像 “智能真空袋”—— 自带感应装置,既能把棉花糖压得比 gzip 紧,又比 Brotli 省力气(压缩速度快)。这是 Facebook 家的 “压缩神器”,主打 “平衡”:在速度和压缩率之间找了个甜蜜点,特别适合既要速度又要省空间的场景(比如动态生成的网页)。



打个比方:同样压缩一件羽绒服,gzip 用 3 秒压到原来的 40%,Brotli 用 10 秒压到 30%,而 Zstd 可能用 5 秒压到 35%—— 不快不慢,不多不少,刚刚好~

准备工作:给 Rust 装个 “智能压缩袋”

Rust 生态里有个官方维护的zstd crate(压缩工具箱),专门处理 Zstd 压缩。先确保你有 Rust 环境(没的话用rustup安装,跟拧瓶盖一样简单)。

第一步:建个 “压缩实验室”

终端敲命令,让 Cargo(Rust 管家)给你搭个项目:



bash

cargo new rust_zstd_demo
cd rust_zstd_demo

第二步:请出 “智能压缩袋”

打开Cargo.toml(项目购物清单),在[dependencies]下面加一行:



toml

zstd = "0.13"  # 最新稳定版,就像买带智能模式的压缩袋



搞定!工具到位,准备测试 Zstd 的 “智能压缩术”~

案例 1:字符串压缩初体验 ——Zstd 的 “平衡术”

先用一段 HTML 字符串当 “试验品”,看看 Zstd 的压缩效果和速度(主观感受)。

代码:src/main.rs

rust

use std::time::Instant;
use zstd::bulk::Compressor;
use zstd::bulk::Decompressor;

fn main() {
    // 试验用的HTML内容(带重复的CSS,方便压缩)
    let html = r#"
<!DOCTYPE html>
<html>
  <head>
    <style>
      .card { padding: 20px; margin: 10px; border: 1px solid #eee; border-radius: 5px; }
      .card { padding: 20px; margin: 10px; border: 1px solid #eee; border-radius: 5px; }
      .title { font-size: 18px; font-weight: bold; color: #333; }
      .title { font-size: 18px; font-weight: bold; color: #333; }
    </style>
  </head>
  <body>
    <h1>Zstd压缩测试</h1>
    <div class="card"><div class="title">测试卡片1</div></div>
  </body>
</html>
    "#;

    // 原始大小(试验品初始体积)
    let original_size = html.as_bytes().len();
    println!("原始大小:{} 字节", original_size);

    // 配置Zstd压缩器(level 3:平衡模式,1-21可选,数字越大压缩率越高但越慢)
    let mut compressor = Compressor::new(3).expect("压缩器初始化失败,是不是买到假货了?");
    
    // 计时:看看压缩多快
    let start = Instant::now();
    let compressed = compressor.compress(html.as_bytes()).expect("压缩失败!");
    let compress_time = start.elapsed();

    // 压缩后大小
    let compressed_size = compressed.len();
    println!("Zstd压缩后:{} 字节", compressed_size);
    println!("压缩率:{:.2}%", (compressed_size as f64 / original_size as f64) * 100.0);
    println!("压缩耗时:{:?}", compress_time);

    // 验证解压(压缩后内容不能变样)
    let mut decompressor = Decompressor::new().expect("解压工具坏了!");
    let decompressed = decompressor.decompress(&compressed, original_size).expect("解压失败!");
    let decompressed_str = String::from_utf8(decompressed).expect("解压内容不是字符串!");

    println!("\n解压验证:{}(内容是否和原来一样)", 
        if decompressed_str == html { "" } else { "" }
    );
}

运行方法:

终端敲:



bash

cargo run

预期输出(类似这样):

plaintext

原始大小:634 字节
Zstd压缩后:225 字节
压缩率:35.49%
压缩耗时:34.2us  # 微秒级,比眨眼还快

解压验证:(内容是否和原来一样)



感受下:这个压缩率比 gzip(通常 40%+)好,耗时比 Brotli(可能上百微秒)少,果然是 “平衡大师”~

案例 2:文件压缩实战 —— 给 JS 文件 “智能瘦身”

实际开发中,我们常压缩 JS 文件。这次用一个真实的 JS 文件当 “试验品”,看看 Zstd 处理文件的能力。

步骤 1:准备 “试验品”

在项目根目录新建script.js,随便复制一段 JS 代码(比如带重复逻辑的函数):



javascript

运行

// 重复的工具函数,方便压缩
function formatNumber(n) { return n.toFixed(2); }
function formatNumber(n) { return n.toFixed(2); }
function log(msg) { console.log("[LOG] " + msg); }
function log(msg) { console.log("[LOG] " + msg); }
// 简单逻辑
let total = 0;
for (let i=0; i<10; i++) {
    total += i;
    log("当前值:" + formatNumber(total));
}

步骤 2:代码src/main.rs(修改版)

rust

use std::fs::File;
use std::io::{Read, Write};
use std::path::Path;
use std::time::Instant;
use zstd::bulk::{Compressor, Decompressor};

fn main() {
    // 文件路径(试验品存放处)
    let source_path = Path::new("script.js");
    let dest_path = Path::new("script.js.zst");  // Zstd压缩文件通常用.zst后缀

    // 读取源文件内容
    let mut source_content = Vec::new();
    File::open(source_path)
        .expect("找不到源文件!是不是忘创建script.js了?")
        .read_to_end(&mut source_content)
        .expect("读文件时手抖了!");
    let original_size = source_content.len();
    println!("JS文件原始大小:{} 字节", original_size);

    // 压缩并计时
    let mut compressor = Compressor::new(5).expect("压缩器罢工了!");  // 级别5,稍强一点
    let start = Instant::now();
    let compressed = compressor.compress(&source_content).expect("压缩文件失败!");
    let compress_time = start.elapsed();

    // 保存压缩文件
    let mut dest_file = File::create(dest_path).expect("创建压缩文件失败!");
    dest_file.write_all(&compressed).expect("写文件到硬盘失败!");
    let compressed_size = compressed.len();

    // 结果汇报
    println!("Zstd压缩后({}):{} 字节", dest_path.display(), compressed_size);
    println!("压缩率:{:.2}%", (compressed_size as f64 / original_size as f64) * 100.0);
    println!("压缩耗时:{:?}", compress_time);

    // 验证解压
    let mut decompressor = Decompressor::new().expect("解压工具坏了!");
    let decompressed = decompressor.decompress(&compressed, original_size).expect("解压失败!");
    println!("解压验证:{}(内容是否完整)", 
        if decompressed == source_content { "" } else { "" }
    );
}

运行方法:

bash

cargo run



项目根目录会生成script.js.zst压缩文件,终端输出类似:



plaintext

JS文件原始大小:488 字节
Zstd压缩后(script.js.zst):184 字节
压缩率:37.70%
压缩耗时:42.1us
解压验证:(内容是否完整)



如果对比 gzip 和 Brotli,你会发现 Zstd 的压缩率接近 Brotli,速度接近 gzip—— 这 “平衡感” 绝了~

案例 3:HTTP 服务器实时压缩 —— 给动态网页 “秒速瘦身”

Zstd 的一大优势是 “压缩速度快”,适合动态生成的网页(比如用户每次请求内容都不一样)。咱们用hyper搭个服务器,实时用 Zstd 压缩响应内容。

步骤 1:更新 “购物清单”(Cargo.toml)

加 HTTP 服务器和异步运行时:



toml

[dependencies]
zstd = "0.13"
hyper = { version = "1.1", features = ["full"] }  # HTTP服务器
tokio = { version = "1.0", features = ["full"] }  # 异步运行时,让服务器“不偷懒”

步骤 2:服务器代码src/main.rs

rust

use hyper::{
    header::{ACCEPT_ENCODING, CONTENT_ENCODING, CONTENT_TYPE},
    service::{make_service_fn, service_fn},
    Body, Request, Response, Server,
};
use std::convert::Infallible;
use std::net::SocketAddr;
use zstd::bulk::Compressor;

// 压缩内容:如果浏览器支持Zstd就用它
fn compress_content(content: &str) -> Vec<u8> {
    // 用级别3,平衡速度和压缩率(服务器别太耗CPU)
    let mut compressor = Compressor::new(3).unwrap();
    compressor.compress(content.as_bytes()).unwrap()
}

// 处理HTTP请求
async fn handle_request(req: Request<Body>) -> Result<Response<Body>, Infallible> {
    // 动态生成的网页内容(比如从数据库查出来的)
    let dynamic_html = format!(
        r#"
<!DOCTYPE html>
<html>
  <head>
    <title>Zstd实时压缩</title>
  </head>
  <body>
    <h1>这是动态生成的网页</h1>
    <p>当前时间:{}(每次请求都不一样哦)</p>
  </body>
</html>
        "#,
        chrono::Local::now().format("%Y-%m-%d %H:%M:%S")  // 用当前时间模拟动态内容
    );

    // 检查浏览器是否支持Zstd(看请求头Accept-Encoding)
    let accept_encoding = req.headers()
        .get(ACCEPT_ENCODING)
        .and_then(|h| h.to_str().ok())
        .unwrap_or("");
    let supports_zstd = accept_encoding.contains("zstd");

    // 压缩内容(支持就用Zstd,否则发原始内容)
    let (body, encoding) = if supports_zstd {
        let compressed = compress_content(&dynamic_html);
        (Body::from(compressed), "zstd")
    } else {
        (Body::from(dynamic_html), "")
    };

    // 构建响应
    let mut response = Response::builder()
        .header(CONTENT_TYPE, "text/html; charset=utf-8");

    // 告诉浏览器用了什么压缩
    let response = if !encoding.is_empty() {
        response.header(CONTENT_ENCODING, encoding)
    } else {
        response
    }.body(body).unwrap();

    Ok(response)
}

#[tokio::main]
async fn main() {
    let addr = SocketAddr::from(([127, 0, 0, 1], 8080));
    let make_svc = make_service_fn(|_conn| async {
        Ok::<_, Infallible>(service_fn(handle_request))
    });
    let server = Server::bind(&addr).serve(make_svc);

    println!("服务器启动!访问 http://127.0.0.1:8080");
    if let Err(e) = server.await {
        eprintln!("服务器出错:{}", e);
    }
}

步骤 3:补个小依赖(动态时间需要)

在Cargo.toml里再加个获取当前时间的库:



toml

chrono = "0.4"  # 时间处理库,方便生成动态内容

运行方法:

bash

cargo run



打开浏览器访问http://127.0.0.1:8080,按 F12 看 “网络” 标签:



  • 现代浏览器(Chrome 113+、Firefox 112+)支持 Zstd,响应头会有Content-Encoding: zstd。
  • 每次刷新页面,时间都会变(模拟动态内容),但 Zstd 压缩依然能 “秒速” 完成。

总结:Zstd—— 压缩界的 “实用主义者”

如果说 gzip 是 “快但糙”,Brotli 是 “好但慢”,那 Zstd 就是 “又快又好还不贵” 的实用主义者。在 Rust 里,zstd库把它封装得简单易用,无论是压缩字符串、文件,还是服务器实时处理,都能轻松应对。尤其适合动态网页、API 响应这类需要 “速度与压缩率兼顾” 的场景 —— 毕竟用户可不想等太久,服务器也不想太累~

标题:

  1. Rust+Zstd:网页压缩界的 “平衡大师” 实战指南
  2. 从字符串到服务器:Rust 实现 Zstd 压缩全案例

简介:

本文用 “智能真空袋” 的幽默类比,详解 Rust 中使用 zstd 库实现 Zstd 压缩的方法,通过字符串压缩、文件压缩及 HTTP 服务器实时压缩三个案例,展示 Zstd 在速度和压缩率间的平衡优势,步骤清晰可实操,助你掌握高效的网页压缩技巧。

关键词:

#Rust #Zstd #网页压缩 #zstd 库 #性能优化

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言