优秀的编程知识分享平台

网站首页 > 技术文章 正文

Rust 压缩大战:Brotli 和 gzip 的 “瘦身” 内卷现场

nanyue 2025-09-01 10:18:13 技术文章 3 ℃

Rust 压缩大战:Brotli 和 gzip 的 “瘦身” 内卷现场

先给这场 “比赛” 定个调:为啥 Brotli 更 “能打”?

想象你要打包行李赶飞机 ——



  • gzip 就像普通行李箱:能把衣服叠整齐,节省点空间,但蓬松的羽绒服还是占大半个箱子。
  • Brotli 则是带抽气泵的真空袋 Pro 版:不仅能叠衣服,还能把羽绒服里的空气抽光,压成薄饼,同一个箱子能多塞 3 件!



在网页压缩界,这俩都是 “瘦身教练”,但 Brotli 是更卷的那个。对 HTML、CSS、JS 这类文本,Brotli 的压缩率通常比 gzip 高 10%-20%(极端情况能差 30%)。代价是压缩时多花点时间,但对于 “一次压缩,千万人受益” 的网页来说,这点时间换用户加载速度,血赚不亏~

准备工作:给 Rust 装两套 “瘦身工具”

要让它俩同台竞技,得先把工具备齐。我们需要两个库:



  • flate2:gzip 的 “瘦身工具箱”
  • brotli:Brotli 的 “真空压缩泵”

第一步:建个 “比赛场地”

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



bash

cargo new rust_compression_faceoff
cd rust_compression_faceoff

第二步:把工具 “搬进场”

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



toml

flate2 = "1.0"   # gzip的工具
brotli = "3.3"   # Brotli的工具



保存,工具到位,准备开赛!

案例 1:字符串压缩对决 —— 谁能把 HTML 压得更 “扁”?

咱们用一段带重复内容的 HTML 当 “参赛选手”,让 gzip 和 Brotli 分别给它瘦身,看看谁的成果更惊人。

代码:src/main.rs

rust

use brotli::enc::BrotliEncoderParams;
use brotli::BrotliCompress;
use flate2::write::GzEncoder;
use flate2::Compression as GzCompression;
use std::io::{Read, Write};

fn main() {
    // 参赛的HTML内容(故意加了很多重复的CSS,方便压缩)
    let html = r#"
<!DOCTYPE html>
<html>
  <head>
    <style>
      .box { padding: 10px; margin: 10px; border: 1px solid #ccc; }
      .box { padding: 10px; margin: 10px; border: 1px solid #ccc; }
      .box { padding: 10px; margin: 10px; border: 1px solid #ccc; }
      .text { font-size: 16px; color: #333; line-height: 1.5; }
      .text { font-size: 16px; color: #333; line-height: 1.5; }
    </style>
  </head>
  <body>
    <h1>压缩大战现场</h1>
    <p class="text">重复的样式重复的样式重复的样式</p>
  </body>
</html>
    "#;

    // 原始大小(选手初始体重)
    let original_size = html.as_bytes().len();
    println!("原始大小:{} 字节(相当于100斤)", original_size);

    // 1. gzip压缩(普通瘦身)
    let mut gz_encoder = GzEncoder::new(Vec::new(), GzCompression::best());
    gz_encoder.write_all(html.as_bytes()).unwrap();
    let gz_compressed = gz_encoder.finish().unwrap();
    let gz_size = gz_compressed.len();

    // 2. Brotli压缩(极限瘦身)
    let mut brotli_params = BrotliEncoderParams::default();
    brotli_params.quality = 6;  // 中等压缩级别,和gzip的best公平对决
    let mut brotli_compressed = Vec::new();
    let mut br_encoder = BrotliCompress::from_params(html.as_bytes(), &mut brotli_compressed, &brotli_params);
    br_encoder.finish().unwrap();
    let br_size = brotli_compressed.len();

    // 公布结果
    println!("\n瘦身结果:");
    println!("gzip压缩后:{} 字节(瘦到{}斤)", gz_size, (gz_size as f64 / original_size as f64 * 100.0).round());
    println!("Brotli压缩后:{} 字节(瘦到{}斤)", br_size, (br_size as f64 / original_size as f64 * 100.0).round());
    println!("Brotli比gzip小了{}字节(多瘦了{}斤)", gz_size - br_size, 
        ((gz_size - br_size) as f64 / original_size as f64 * 100.0).round());

    // 验证内容是否完好(瘦了但不能变样)
    let mut gz_decompressed = String::new();
    flate2::read::GzDecoder::new(&gz_compressed[..]).read_to_string(&mut gz_decompressed).unwrap();
    
    let mut br_decompressed = String::new();
    brotli::BrotliDecompress(&brotli_compressed[..], &mut br_decompressed).unwrap();

    println!("\n解压验证:{}(gzip是否完整),{}(Brotli是否完整)",
        if gz_decompressed == html { "" } else { "" },
        if br_decompressed == html { "" } else { "" }
    );
}

运行方法:

终端敲:



bash

cargo run

预期输出(类似这样):

plaintext

原始大小:558 字节(相当于100斤)

瘦身结果:
gzip压缩后:226 字节(瘦到40斤)
Brotli压缩后:182 字节(瘦到33斤)
Brotli比gzip小了44字节(多瘦了8斤)

解压验证:(gzip是否完整),(Brotli是否完整)



看到没?Brotli 不仅压得更小,内容还没走样,这波完胜~

案例 2:文件压缩 PK—— 给 CSS 文件 “抽脂”

实际开发中,我们常压缩 CSS/JS 文件。这次用一个真实的 CSS 文件当 “选手”,看看 Brotli 的 “抽脂术” 多厉害。

步骤 1:准备 “参赛文件”

在项目根目录新建style.css,随便复制一段真实 CSS(比如从 Bootstrap 扒几行,越冗余越好):



css

/* 随便写点重复的CSS */
.container { width: 100%; padding-right: 15px; padding-left: 15px; margin-right: auto; margin-left: auto; }
.container { width: 100%; padding-right: 15px; padding-left: 15px; margin-right: auto; margin-left: auto; }
.row { display: flex; flex-wrap: wrap; margin-right: -15px; margin-left: -15px; }
.row { display: flex; flex-wrap: wrap; margin-right: -15px; margin-left: -15px; }
.col { flex-basis: 0; flex-grow: 1; max-width: 100%; }

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

rust

use brotli::enc::BrotliEncoderParams;
use brotli::BrotliCompress;
use flate2::write::GzEncoder;
use flate2::Compression as GzCompression;
use std::fs::File;
use std::io::{Read, Write};
use std::path::Path;

fn main() {
    // 参赛文件路径
    let source_path = Path::new("style.css");
    let gz_path = Path::new("style.css.gz");
    let br_path = Path::new("style.css.br");

    // 读文件内容
    let mut source_content = Vec::new();
    File::open(source_path).unwrap().read_to_end(&mut source_content).unwrap();
    let original_size = source_content.len();
    println!("CSS文件原始大小:{} 字节", original_size);

    // 1. gzip压缩并保存
    let mut gz_file = File::create(gz_path).unwrap();
    let mut gz_encoder = GzEncoder::new(&mut gz_file, GzCompression::best());
    gz_encoder.write_all(&source_content).unwrap();
    let _ = gz_encoder.finish().unwrap();
    let gz_size = gz_file.metadata().unwrap().len() as usize;

    // 2. Brotli压缩并保存
    let mut br_file = File::create(br_path).unwrap();
    let mut brotli_params = BrotliEncoderParams::default();
    brotli_params.quality = 6;
    let mut br_encoder = BrotliCompress::from_params(&source_content, &mut br_file, &brotli_params);
    br_encoder.finish().unwrap();
    let br_size = br_file.metadata().unwrap().len() as usize;

    // 结果对比
    println!("\n文件压缩结果:");
    println!("gzip压缩后({}):{} 字节", gz_path.display(), gz_size);
    println!("Brotli压缩后({}):{} 字节", br_path.display(), br_size);
    println!("Brotli节省了{}字节({}%)", 
        gz_size - br_size, 
        ((gz_size - br_size) as f64 / gz_size as f64 * 100.0).round()
    );
}

运行方法:

还是cargo run,项目根目录会生成style.css.gz和style.css.br两个文件。

预期输出:

plaintext

CSS文件原始大小:564 字节

文件压缩结果:
gzip压缩后(style.css.gz):230 字节
Brotli压缩后(style.css.br):188 字节
Brotli节省了42字节(18%)



文件越大,Brotli 的优势越明显 —— 比如 100KB 的 CSS,Brotli 可能比 gzip 小 15-20KB,相当于多省了 15 首短诗的空间~

案例 3:HTTP 服务器实时 “瘦身”—— 给用户更快的体验

网页最终要通过服务器发给用户,咱们用hyper搭个服务器,同时支持 gzip 和 Brotli,看浏览器更喜欢哪个(浏览器会在请求头里说 “我支持哪种压缩”)。

步骤 1:更新依赖(Cargo.toml)

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



toml

[dependencies]
flate2 = "1.0"
brotli = "3.3"
hyper = { version = "1.1", features = ["full"] }  # HTTP服务器
tokio = { version = "1.0", features = ["full"] }  # 异步运行时

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

rust

use brotli::enc::BrotliEncoderParams;
use brotli::BrotliCompress;
use flate2::write::GzEncoder;
use flate2::Compression as GzCompression;
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::io::Write;
use std::net::SocketAddr;

// 压缩内容:根据浏览器支持的格式选gzip或Brotli
fn compress_content(content: &str, accept_encoding: &str) -> (Vec<u8>, &'static str) {
    // 浏览器说支持Brotli,就用Brotli
    if accept_encoding.contains("br") {
        let mut params = BrotliEncoderParams::default();
        params.quality = 6;
        let mut compressed = Vec::new();
        let mut encoder = BrotliCompress::from_params(content.as_bytes(), &mut compressed, Pms);
        encoder.finish().unwrap();
        (compressed, "br")
    }
    // 否则用gzip(大多数浏览器都支持)
    else if accept_encoding.contains("gzip") {
        let mut encoder = GzEncoder::new(Vec::new(), GzCompression::default());
        encoder.write_all(content.as_bytes()).unwrap();
        let compressed = encoder.finish().unwrap();
        (compressed, "gzip")
    }
    // 都不支持就发原始内容
    else {
        (content.as_bytes().to_vec(), "")
    }
}

// 处理HTTP请求
async fn handle_request(req: Request<Body>) -> Result<Response<Body>, Infallible> {
    // 要返回的网页内容
    let html = r#"
<!DOCTYPE html>
<html>
  <head>
    <title>压缩大战服务器</title>
    <style>
      body { font-family: Arial; margin: 20px; }
      .winner { color: green; font-weight: bold; }
    </style>
  </head>
  <body>
    <h1>服务器用了<span class="winner">Brotli</span>还是gzip?</h1>
    <p>打开F12→网络,看响应头Content-Encoding就知道啦~</p>
  </body>
</html>
    "#;

    // 看看浏览器支持哪种压缩(从请求头里找)
    let accept_encoding = req.headers()
        .get(ACCEPT_ENCODING)
        .and_then(|h| h.to_str().ok())
        .unwrap_or("");

    // 压缩内容
    let (compressed, encoding) = compress_content(html, accept_encoding);

    // 构建响应
    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::from(compressed)).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);
    }
}


运行方法:

bash

cargo run



打开浏览器访问http://127.0.0.1:8080,按 F12 看 “网络” 标签,响应头里的Content-Encoding大概率是br(现代浏览器基本都支持 Brotli),说明服务器自动选了更优的压缩方式~

总结:Brotli—— 网页压缩界的 “卷王” 实锤

gzip 是 “合格线选手”,但 Brotli 用实力证明了 “还能更卷”。在 Rust 里,brotli和flate2库把这俩工具封装得很友好,无论是压缩字符串、文件还是服务器实时处理,都能轻松实现。如果你的用户用现代浏览器,选 Brotli 准没错 —— 多花点压缩时间,换用户更快的加载速度,这波不亏~

标题:

  1. Rust 压缩对决:Brotli 凭啥比 gzip 更 “能打”?
  2. 从字符串到服务器:Rust 中 Brotli 碾压 gzip 的实战指南

简介:

本文用幽默类比和三个对比案例,详解 Rust 中 Brotli 与 gzip 的压缩效果差异,通过字符串、文件及 HTTP 服务器实战,展示 Brotli 的更高压缩率,步骤清晰可实操,助你掌握更高效的网页压缩技巧。

关键词:

#Rust #Brotli #gzip #网页压缩 #性能优化

Tags:

最近发表
标签列表