Java 项目部署之 Docker工具快速入门: 使用 Docker 镜像命令练习

概述

从 Docker Hub 拉一个 Redis 镜像,导出成 tar,删掉本地镜像,再从 tar 灌回来。命令本身没难度,坑都藏在细节里------怎么命名归档文件、save 和重定向有什么区别、删镜像为什么会被容器拦住。

纲要

  • 练习题目的六步目标与拆解
  • 每步的命令、预期输出与设计原因
  • 镜像命令速查表
  • docker save/load 与 docker export/import 的边界
  • 四个高频陷阱:-o 与 >、.tar.gz 后缀、load 混用 import、删镜像前先删容器
  • 一条可复制的完整练习脚本

练习要解决什么

讲义给的题目是:

需求:去 Docker Hub 搜索并拉取一个 Redis 镜像

目标是六步,顺序不能乱:

序号 目标 对应命令
1 去 Docker Hub 搜索 Redis 镜像 浏览器搜索 / docker search redis
2 查看 Redis 镜像的名称和版本 Docker Hub 的 Tags 页
3 拉取镜像 docker pull redis
4 打包成 tar 归档 docker save -o redis.tar redis:latest
5 删除本地镜像 docker rmi redis:latest
6 从 tar 重新加载 docker load -i redis.tar

练习题里没有第 7 步,但少了它整道题就没有验收标准------加载回来之后必须再 docker images 看一眼,确认 REPOSITORY、TAG、IMAGE ID 都对得上。题目的真正考点不是"记得住命令",而是理解 save/load 这条链路里到底搬运了什么。

课前资料目录长这样,练习用到的归档文件以后会和其他素材放在一起:

tree 复制代码
day03-Docker/
├── 讲义/
│   ├── Docker实用篇.md            # 2.1.5 就是本文的练习题
│   ├── Docker实用篇.pptx
│   └── assets/
└── 资料/
    ├── Centos7安装Docker.md
    ├── Dockerfile
    ├── docker-compose
    ├── docker-demo.jar
    ├── hmy.cnf
    ├── jdk8.tar.gz
    ├── mysql.tar
    ├── nginx.conf
    ├── redis.tar.gz               # 注意:只有 22 字节,不是 save 的产物
    ├── assets/
    └── cloud-demo/

后面讲命名陷阱时会回到 redis.tar.gz 这个文件上。

确认镜像名称

镜像名是 [repository]:[tag] 两段式,不写 tag 时默认就是 latest。Redis 的官方镜像在 Docker Hub 上叫 redis,完整身份是 docker.io/library/redis:latest,library 是官方镜像的命名空间,日常拉取时可以全省掉。

不加 latest 而写具体版本是有意义的。docker pull redis:6.2.1 拉的是写死的版本,docker pull redis 拉的是当下最新的。生产环境的镜像必须钉版本,否则今天构建的和下个月重建的可能不是同一个东西。

命令行也能搜,但结果只是 Docker Hub 上前若干个仓库的简要列表,看不出 tags,正式确认版本还是去 Docker Hub 的 Tags 页面看。

bash 复制代码
docker search redis
text 复制代码
NAME                      DESCRIPTION                                      STARS     OFFICIAL
redis                     Redis is an open source key-value store that...  12000     [OK]
bitnami/redis             Bitnami Redis Docker Image                       ...

OFFICIAL 那列是 [OK] 的才是官方镜像,library/redis 排在第一行。

拉取镜像

bash 复制代码
docker pull redis

等价于 docker pull redis:latest,看到的输出是分层下载的过程:

text 复制代码
Using default tag: latest
latest: Pulling from library/redis
a2abf6c4d29d: Pull complete
c7a4e4382001: Pull complete
4044b9ba67c9: Pull complete
Digest: sha256:b3f4a3db5e1c1a0d0f...
Status: Downloaded newer image for redis:latest
docker.io/library/redis:latest

Pulling from 后面那一串就是镜像层(layer)。Docker 按层下载并做内容去重,所以第二次拉同一个镜像会直接提示 Image is up to date for redis:latest,不会重新下载------层已经在本地缓存里。这也是后面 docker save 导出的东西一定会比单个 tar 包大的原因,它把每一层都装进去了。

查看本地镜像

bash 复制代码
docker images

当前 Docker 版本的列头是固定的五列:

text 复制代码
REPOSITORY   TAG       IMAGE ID       CREATED         SIZE
redis        latest    7614ae9453d1   2 weeks ago     113MB
nginx        latest    605c77e624dd   3 weeks ago     141MB

想只看某个镜像,把名字当过滤参数传进去:

bash 复制代码
docker images redis

IMAGE ID 是这一列的短 ID。这条命令不只是"看看有没有",它还是第 6 步验收时的比对基准:回灌之后 IMAGE ID 应该和删除前完全一致,如果变了,说明你灌进来的不是同一个镜像。

导出为 tar 归档

bash 复制代码
docker save -o redis.tar redis:latest

讲义原文这里就是 redis.tar,不带 .gz。命令跑完没有回显,靠 ll 或 ls -lh 确认:

bash 复制代码
ls -lh redis.tar
text 复制代码
-rw-r--r-- 1 root root 138M Feb 28 10:12 redis.tar

-o 是 --output,意思是让 Docker 自己把归档写到指定文件。不带 -o 时 save 会把 tar 流打到标准输出------这个默认行为后面会和 shell 重定向放在一起讲。

删除本地镜像

bash 复制代码
docker rmi redis:latest
text 复制代码
Untagged: redis:latest
Untagged: redis@sha256:b3f4a3db5e1c1a0d0f...
Deleted: sha256:7614ae9453d1...
Deleted: sha256:c7a4e4382001...
...

注意两件事。第一,删镜像用 rmi 不是 rm,docker rm 删的是容器,名字差一个字母,结果天差地别。第二,输出是先把 Untagged 把 tag 摘掉,再逐层 Deleted,顺序和拉取时正好相反。

删完复查,列表里就没有它了:

bash 复制代码
docker images redis
text 复制代码
REPOSITORY   TAG       IMAGE ID   CREATED   SIZE

从归档回灌

bash 复制代码
docker load -i redis.tar
text 复制代码
Loaded image: redis:latest

-i 是 --input,从文件读。这一步之所以能"还原",是因为 save 出来的 tar 里有完整的 manifest.json 和全部层目录,tag、镜像配置(CMD、ENV、暴露的端口)都在里面。load 只是把档案解回本地镜像库,不涉及任何网络请求,所以离线机器上同样能跑通。

如果只需要确认归档里有什么,不用加载,直接看 tar 的目录结构:

bash 复制代码
tar -tf redis.tar | head -n 8
text 复制代码
7614ae9453d1e1...
7614ae9453d1e1.../VERSION
7614ae9453d1e1.../json
7614ae9453d1e1.../layer.tar
c7a4e4382001...
...
manifest.json
repositories

manifest.json 就是元数据,一堆十六进制目录名就是各层。

复查与比对

bash 复制代码
docker images
docker images -q redis

回灌后 redis:latest 应该原样出现,IMAGE ID 与删除前一致。整个往返过程可以画成这样:
#mermaid-svg-VoLSt8DGrCGVrxZD{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-VoLSt8DGrCGVrxZD .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-VoLSt8DGrCGVrxZD .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-VoLSt8DGrCGVrxZD .error-icon{fill:#552222;}#mermaid-svg-VoLSt8DGrCGVrxZD .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-VoLSt8DGrCGVrxZD .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-VoLSt8DGrCGVrxZD .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-VoLSt8DGrCGVrxZD .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-VoLSt8DGrCGVrxZD .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-VoLSt8DGrCGVrxZD .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-VoLSt8DGrCGVrxZD .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-VoLSt8DGrCGVrxZD .marker{fill:#333333;stroke:#333333;}#mermaid-svg-VoLSt8DGrCGVrxZD .marker.cross{stroke:#333333;}#mermaid-svg-VoLSt8DGrCGVrxZD svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-VoLSt8DGrCGVrxZD p{margin:0;}#mermaid-svg-VoLSt8DGrCGVrxZD .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-VoLSt8DGrCGVrxZD .cluster-label text{fill:#333;}#mermaid-svg-VoLSt8DGrCGVrxZD .cluster-label span{color:#333;}#mermaid-svg-VoLSt8DGrCGVrxZD .cluster-label span p{background-color:transparent;}#mermaid-svg-VoLSt8DGrCGVrxZD .label text,#mermaid-svg-VoLSt8DGrCGVrxZD span{fill:#333;color:#333;}#mermaid-svg-VoLSt8DGrCGVrxZD .node rect,#mermaid-svg-VoLSt8DGrCGVrxZD .node circle,#mermaid-svg-VoLSt8DGrCGVrxZD .node ellipse,#mermaid-svg-VoLSt8DGrCGVrxZD .node polygon,#mermaid-svg-VoLSt8DGrCGVrxZD .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-VoLSt8DGrCGVrxZD .rough-node .label text,#mermaid-svg-VoLSt8DGrCGVrxZD .node .label text,#mermaid-svg-VoLSt8DGrCGVrxZD .image-shape .label,#mermaid-svg-VoLSt8DGrCGVrxZD .icon-shape .label{text-anchor:middle;}#mermaid-svg-VoLSt8DGrCGVrxZD .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-VoLSt8DGrCGVrxZD .rough-node .label,#mermaid-svg-VoLSt8DGrCGVrxZD .node .label,#mermaid-svg-VoLSt8DGrCGVrxZD .image-shape .label,#mermaid-svg-VoLSt8DGrCGVrxZD .icon-shape .label{text-align:center;}#mermaid-svg-VoLSt8DGrCGVrxZD .node.clickable{cursor:pointer;}#mermaid-svg-VoLSt8DGrCGVrxZD .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-VoLSt8DGrCGVrxZD .arrowheadPath{fill:#333333;}#mermaid-svg-VoLSt8DGrCGVrxZD .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-VoLSt8DGrCGVrxZD .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-VoLSt8DGrCGVrxZD .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-VoLSt8DGrCGVrxZD .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-VoLSt8DGrCGVrxZD .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-VoLSt8DGrCGVrxZD .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-VoLSt8DGrCGVrxZD .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-VoLSt8DGrCGVrxZD .cluster text{fill:#333;}#mermaid-svg-VoLSt8DGrCGVrxZD .cluster span{color:#333;}#mermaid-svg-VoLSt8DGrCGVrxZD div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-VoLSt8DGrCGVrxZD .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-VoLSt8DGrCGVrxZD rect.text{fill:none;stroke-width:0;}#mermaid-svg-VoLSt8DGrCGVrxZD .icon-shape,#mermaid-svg-VoLSt8DGrCGVrxZD .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-VoLSt8DGrCGVrxZD .icon-shape p,#mermaid-svg-VoLSt8DGrCGVrxZD .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-VoLSt8DGrCGVrxZD .icon-shape .label rect,#mermaid-svg-VoLSt8DGrCGVrxZD .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-VoLSt8DGrCGVrxZD .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-VoLSt8DGrCGVrxZD .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-VoLSt8DGrCGVrxZD :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} docker pull redis
docker save -o redis.tar
docker rmi redis:latest
docker load -i redis.tar
确认恢复
Docker Hub

library/redis:latest
本地镜像

redis:latest
磁盘归档 redis.tar

未压缩 tar,含全部分层 + manifest.json
本地已无该镜像
docker images 复查

IMAGE ID 与删除前一致

图中 save 与 load 是两条方向相反的边,中间夹着一次删除。删掉镜像不会动磁盘上的 tar,所以这条链路本质上是"把镜像从 Docker 的存储目录里挪到普通文件系统上,再挪回去"。

命令速查表

阶段 命令 作用 常用参数
搜索 docker search redis 在 Docker Hub 上按关键字找仓库 --limit n、--filter is-official=true
拉取 docker pull redis:6.2.1 从仓库拉镜像到本地 不写 tag 即 latest
查看 docker images 列出本地镜像 -q 只输出 ID、-a 含中间层、--filter 过滤
导出 docker save -o redis.tar redis:latest 把镜像(含全部分层与元数据)存成 tar -o/--output,可一次跟多个镜像
加载 docker load -i redis.tar 从 tar 恢复镜像,保留 tag -i/--input、-q 静默
打标签 docker tag redis:latest myredis:1.0 给镜像加一个新引用 源镜像、目标名
删除 docker rmi redis:latest 删除本地镜像 -f 强制、可传 ID 或 名称:tag
清理 docker image prune -a 清理未被容器使用的镜像 -a 连无 tag 的一起清
看帮助 docker save --help 查某个子命令的语法 所有子命令通用

docker save --help 的输出值得存一份,本机实际是这样的:

text 复制代码
Usage:  docker save [OPTIONS] IMAGE [IMAGE...]

Save one or more images to a tar archive (streamed to STDOUT by default)

Options:
  -o, --output string     Write to a file, instead of STDOUT
      --platform string   Save only the given platform variant. Formatted
                          as "os[/arch[/variant]]" (e.g., "linux/amd64")

streamed to STDOUT by default 这句是后面所有命名问题的根源。

save/load 与 export/import 的边界

这两组命令长得像,作用对象完全不同。save/load 面向镜像,export/import 面向容器:

维度 docker save / docker load docker export / docker import
操作对象 镜像 容器
归档命令 docker save -o a.tar IMAGE docker export -o a.tar CONTAINER
归档内容 全部分层 + manifest.json 等元数据 容器根文件系统的合并快照,只有一层
是否保留 tag 保留,load 后 tag 原样恢复 丢失,import 时必须自己指定 名称:tag
是否保留历史与配置 保留(CMD、ENTRYPOINT、ENV、构建历史) 全部丢失
回灌命令 docker load -i a.tar docker import a.tar myapp:1.0
归档体积 大,等于所有层之和 小,只有一份合并后的文件系统
典型用途 离线机器之间搬运镜像 导出容器现场做 rootfs、做瘦身基镜像

一句话记:save 存的是"镜像本身",export 存的是"容器里的文件长什么样"。练习里用的是 save 路径,所以回灌之后 redis:latest 这个 tag 才会自动回来。

两条链路不能交叉:

  • 把 export 出来的快照丢给 docker load,会直接报归档格式错误。
  • 把 save 出来的 tar 丢给 docker import,要么报 tar 解析错误,要么"成功"导入出一个没有入口命令的垃圾镜像,docker run 起来立刻退出。

几个容易踩的坑

docker save -o 和重定向 >

两种写法看起来等价,实际不是:

bash 复制代码
docker save -o redis.tar redis:latest     # Docker 自己打开文件写入
docker save redis:latest > redis.tar       # shell 先创建文件,再让 Docker 往标准输出写

单纯的二选一都能用,真正的坑是混着写:

bash 复制代码
docker save -o a.tar redis:latest > b.tar

-o a.tar 把归档写进了 a.tar,标准输出没有任何内容,于是 shell 创建并清空的 b.tar 永远是 0 字节。这种命令不会报错,等到别人拿着 b.tar 去 docker load 才炸。

重定向还有第二个副作用:shell 是先 创建、截断目标文件,再 执行命令。如果镜像名敲错了,docker save 失败退出,但 redis.tar 已经被清空了------你以为有个归档,其实是个 0 字节文件,而 -o 写法在镜像不存在时根本不会去动那个文件。所以凡是"生成产物"的场景,优先用命令自带的 -o/-i,不要用重定向。

.tar.gz 后缀不等于真压缩

docker save 的输出永远是未压缩 tar。把文件命名成 redis.tar.gz 不会让它小一个字节:

bash 复制代码
docker save -o redis.tar.gz redis:latest    # 内容是 tar,名字骗人
ls -lh redis.tar.gz                          # 大小和 redis.tar 一模一样

要真压缩,得自己过一遍 gzip:

bash 复制代码
docker save redis:latest | gzip > redis.tar.gz

docker load 能自动识别 gzip 归档,所以上面这条压缩版 load -i redis.tar.gz 也能正常加载。问题是"文件叫 .tar.gz 但内容是裸 tar"这种情况,用 tar -xzf 解压会直接报 gzip: stdin: not in gzip format,排查时容易被名字带偏。

回到 资料/ 目录里那个 redis.tar.gz------它只有 22 字节,显然不是任何一次 save 的产物,就是个占位文件。判断一个归档是不是真货,第一件事是 ls -lh 看体积,Redis 镜像导出来是百兆级别,不可能 22 字节。

load 和 import 不能混用

load 只认 save 生成的归档,import 只认文件系统快照。名字里都带个"载入"的意思,参数格式却完全不同:load 用 -i 指定归档,import 直接把 tar 当位置参数,还要额外给个 名称:tag。记法是:save 配 load,export 配 import。

删镜像前必须先删依赖它的容器

练习里 docker rmi 一次就成功了,是因为从头到尾只 pull 了镜像,没 run 过容器。一旦镜像被容器引用过(哪怕容器已经停止),删除就会失败:

text 复制代码
Error response from daemon: conflict: unable to delete 7614ae9453d1 (must be forced)
 - image is being used by stopped container 3f2b1c9d8e70

Docker 拦这一下是故意的:容器只是停止,没有删除,它的可写层还挂在原镜像上,直接删镜像会让这个容器再也起不来。处理顺序是先清容器再清镜像:

bash 复制代码
docker rm -f 3f2b1c9d8e70       # 先删容器
docker rmi redis:latest         # 再删镜像

要批量清场:

bash 复制代码
docker rm -f $(docker ps -aq)   # 删除所有容器(含运行中的)
docker rmi redis:latest

用 docker rmi -f 硬删也能过,但那是强制解引用,容器会变成悬空状态,除了掩盖问题没有别的好处。

完整练习脚本

把六步串成一个可以整段复制的脚本,带 IMAGE ID 比对做自动验收:

bash 复制代码
#!/usr/bin/env bash
# Docker 镜像命令练习:Redis 镜像的拉取 / 导出 / 删除 / 回灌
# 需要有 docker 权限,非 root 用户确认已在 docker 组里(否则每条命令前面加 sudo)
set -euo pipefail

IMAGE="redis:latest"
TAR="redis.tar"

echo "===== 0. 环境自检 ====="
docker version --format '{{.Server.Version}}'

echo "===== 1. 拉取镜像(不写 tag 等价于 ${IMAGE})====="
docker pull redis

echo "===== 2. 查看本地镜像 ====="
docker images
docker images redis

echo "===== 3. 导出为 tar 归档 ====="
rm -f "${TAR}"
docker save -o "${TAR}" "${IMAGE}"
ls -lh "${TAR}"
# 确认归档里确实有 manifest.json 和各层目录
tar -tf "${TAR}" | head -n 8

echo "===== 4. 记录删除前的 IMAGE ID ====="
OLD_ID="$(docker images -q "${IMAGE}")"
echo "before: ${OLD_ID}"

echo "===== 5. 删除本地镜像 ====="
# 先确认没有容器在引用这个镜像
echo "引用该镜像的容器(应为空):"
docker ps -a --filter "ancestor=${IMAGE}" --format '{{.ID}} {{.Names}}'
docker rmi "${IMAGE}"
# 复查:查不到任何记录即删除成功
docker images "${IMAGE}"

echo "===== 6. 从 tar 重新加载 ====="
docker load -i "${TAR}"

echo "===== 7. 复查并比对 IMAGE ID ====="
NEW_ID="$(docker images -q "${IMAGE}")"
echo "after : ${NEW_ID}"
if [ -n "${NEW_ID}" ] && [ "${OLD_ID}" = "${NEW_ID}" ]; then
    echo "通过:IMAGE ID 与删除前一致,镜像完整恢复"
else
    echo "失败:镜像未恢复或 ID 不一致" >&2
    exit 1
fi

脚本里有两处是练习题原文没写、但实际必须做的:记录删除前的 IMAGE ID、回灌后再比一次。少了这两步,"恢复成功"只能靠肉眼感觉。

官方文档

总结

  • 练习的六步是一条完整闭环:pull 拿到镜像 → save 落成 tar → rmi 删本地 → load 灌回来。归档文件放在普通文件系统上,镜像放在 Docker 的存储目录里,两边互不影响,删了镜像不会动 tar。
  • save 搬运的是镜像本身,带着全部分层和 manifest.json,所以 load 之后 tag 和 IMAGE ID 都能原样回来;export 搬运的是容器文件系统的合并快照,元数据全丢,必须配 import 且要自己补名字。
  • 归档一律用 -o/-i,别用重定向。-o a.tar > b.tar 这种写法会静默产出一个 0 字节的 b.tar,而且重定向会在命令执行前就先清空目标文件。
  • save 的输出是未压缩 tar,叫 .tar.gz 不会变小。想真压一遍就 docker save redis:latest | gzip > redis.tar.gz。判断归档真伪先看体积,别信后缀。
  • docker rmi 失败多半是被容器占着,先 docker rm -f 清容器再删镜像,不要习惯性加 -f 硬删。
相关推荐
企业数字化笔记1 小时前
多租户系统从哪里开始设计?租户身份、数据隔离、权限与套餐的整体方案
java·spring boot·后端·junit
tiger从容淡定是人生1 小时前
从 Gemini 的任务示例出发:AI 能否从知识中推导工作流?
java·人工智能·aigc·知识图谱·创业创新·零知识证明
Nebula_g1 小时前
JavaSE高级:动态代理
java·开发语言·算法·javase
高级程序源1 小时前
springboot校园外卖平台13676-计算机课程设计、毕业设计
java·vue.js·spring boot·后端·tomcat·maven·课程设计
怦然丶心动1 小时前
用 Java 把干支历法算清楚:从节气切年到可读摘要
java·算法·vue·独立开发·spring boot / spring
青春逝如流水2 小时前
Java 线程与操作系统、硬件的关系:状态、映射与差异
java·开发语言·多线程·ai文章
深入云栈2 小时前
Netty 4.2.x 源码深度解析 (十八):ChannelOutboundBuffer 写缓冲区 —— 发送队列与流量控制
java·后端·架构
我是人✓2 小时前
Java核心术语与内存机制深度解析
java