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")));

這裡我們明確地說:把資料流 intransferTo 方法「傳輸」給負責寫入檔案 "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());

如此一來,我們可以直接在記憶體中檢查圖片,甚至不必把它存到磁碟。

留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION