CodeGym /课程 /JAVA 25 SELF /从互联网下载图片

从互联网下载图片

JAVA 25 SELF
第 12 级 , 课程 4
可用

1. 引言

文件与网络相关的工作是编程中至关重要的主题之一。几乎任何现代应用都会以某种方式与互联网交互:下载数据、发送请求、获取图片或文档。即便是简单的程序,比如聊天客户端或音乐播放器,也一定会处理资源的下载。

今天我们就以最直观、最“讨喜”的载荷——图片——为例,来看看在 Java 中如何完成这些操作。为什么选它们?

  • 立竿见影:如果一切正常,你只需打开下载的文件就能看到图片。结果是即时且易于理解的。
  • 通用技能:图片不只是图片,它们是二进制数据,即“原始字节”。学会处理它们,你就能下载任何东西:文档、视频、音乐、压缩包。原理完全相同!
  • 贴近实战:从网络下载资源是构建任何现代应用的基础,从简单的壁纸下载器到与社交网络 API 交互的复杂系统都离不开它。

我们将用两种思路来理解:快速的“邮政服务”——通过 URL,以及“专业运输公司”——通过 HttpClient,并配合对响应头、状态码与超时的控制。

2. 最简单的方法:URL 类

Java 中有一个 URL 类,用来表示网络地址。它可以打开连接并获得数据流(类型为 InputStream)。你可以像处理文件一样处理这个流:读取字节、复制到其他流、按文本处理等。

本质上,使用 URL 是从浏览器中的链接到磁盘上的文件的最短路径。

第一个最小示例

假设我们有一个图片链接:

URL url = new URL("https://example.com/image.jpg");
Files.copy(url.openStream(), Path.of("a.jpg"));

就两行代码。这里发生了什么?

  1. 创建一个 URL 对象,传入图片地址的字符串。
  2. 调用 openStream() 方法,它会打开网络连接并返回一个 InputStream
  3. 通过 Files.copy 方法将该流的内容复制到文件 "a.jpg" 中。

结果:来自互联网的图片被保存到我们的工作目录中。

使用 transferTo 的变体

还有一种把流中的数据导入文件的方法:

InputStream in = new URL("https://example.com/image.jpg").openStream();
in.transferTo(Files.newOutputStream(Path.of("b.jpg")));

这里我们明确地说:拿到流 in,并用 transferTo 方法把所有数据“输送”到写入文件 "b.jpg" 的输出流中。

结果是一样的:图片会出现在磁盘上。

重要细节

这种方式非常简单,但也有一些细节。首先,我们并没有校验链接返回的到底是什么。可能是图片,也可能是带有 404 错误的 HTML 页面。两种情况下文件都会被保存。只有当你用图片查看器打开它时,差异才会显现。

3. 现代方法:HttpClient

从 Java 11 开始,标准库引入了一个新工具——HttpClient。它可以以现代方式发起 HTTP 请求:支持 GET、POST 等方法,管理超时,检查响应头与状态码,处理重定向。

下载图片时这尤其有用:我们可以确保文件确实成功下载,而不是收到了一个错误页面。

HttpClient 的最小示例

URI uri = URI.create("https://example.com/image.jpg");   // URI —— URL 的更现代版本

HttpClient client = HttpClient.newHttpClient();          // 创建 HttpClient 对象
HttpRequest request = HttpRequest.newBuilder(uri).build(); // 创建“请求”对象

HttpResponse<byte[]> response = client.send(request, HttpResponse.BodyHandlers.ofByteArray());
Files.write(Path.of("c.jpg"), response.body());

现在一步步来:

  1. 创建 HttpClient 客户端。
  2. 为指定地址构造 HttpRequest 请求。
  3. 发送请求,并以字节数组形式获取响应:HttpResponse.BodyHandlers.ofByteArray()
  4. 使用 Files.write 将这些字节写入文件 "c.jpg"

结果——还是同一张图片,但现在我们还掌握了服务器如何响应的更多信息。

检查响应状态

response 对象有一个 statusCode() 方法。借助它可以确认服务器是否返回了成功的响应:

if (response.statusCode() == 200)
{
    Files.write(Path.of("ok.jpg"), response.body());
}
else
{
    System.out.println("错误:代码 " + response.statusCode());
}

现在我们就能确定,保存下来的正是期望的内容,而不是一个错误页面。

获取响应头

假设我们想检查内容类型:

String type = response.headers().firstValue("Content-Type").orElse("未知");
System.out.println("内容类型: " + type);

如果服务器返回了 "image/png""image/jpeg",那它确实是图片;如果是 "text/html",那就是一个危险信号。

超时设置

为避免服务器长时间无响应导致程序“卡死”,可以设置超时:

URI uri = URI.create("https://example.com/image.jpg");
HttpRequest req = HttpRequest.newBuilder(uri)
    .timeout(Duration.ofSeconds(5))
    .build();

如果服务器在 5 秒内没有响应,请求将以错误结束。

处理重定向

有时链接并不直接指向文件,而是先到一个重定向页面。你可以这样开启自动跟随重定向:

HttpClient client = HttpClient.newBuilder()
    .followRedirects(HttpClient.Redirect.NORMAL)
    .build();

4. 实用场景

连续下载多张图片

经常需要下载的不止一张,而是几十甚至上百张图片。比如,你要编写一个程序来保存用户头像或商品照片。用 HttpClient 可以通过循环来完成:

var client = HttpClient.newHttpClient();

String[] urls = {
    "https://example.com/img1.jpg",
    "https://example.com/img2.jpg"
};

for (int i = 0; i < urls.length; i++)
{
    var uri = URI.create(urls[i]);
    var request = HttpRequest.newBuilder(uri).build();
    var response = client.send(request, HttpResponse.BodyHandlers.ofByteArray());
    if (response.statusCode() == 200) {
        Files.write(Path.of("img" + i + ".jpg"), response.body());
    }
}

检查文件大小

有时了解图片的字节数很有用。服务器可能会在 "Content-Length" 头中给出:

String length = response.headers().firstValue("Content-Length").orElse("?");
System.out.println("大小: " + length + " 字节");

如果没有该响应头,也可以直接取 response.body().length

在程序中下载并显示图片

有时不仅要下载图片,还要立即显示。可以使用 javax.imageio.ImageIO 库:

InputStream in = new URL("https://example.com/pic.png").openStream();
BufferedImage img = ImageIO.read(in);
System.out.println("宽度: " + img.getWidth() + ", 高度: " + img.getHeight());

这样,我们可以直接在内存中检查图片,甚至无需将其保存到磁盘。

评论 (3)
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION
告不离水 级别 19,Jiangmen,China
13 七月 2026
这节课和前面循序渐进的风格差距太大了,一股脑塞了很多东西
Anonymous #11750648 级别 27,Golden,United States
2 二月 2026
这节难度大了,给了很多东西没说具体原理
ncksllpo 级别 43,Cherkasy,Ukraine
25 一月 2026
太难了,感觉掌握不了