本质是服务器校验User-Agent,JDK默认为空被拦截;需用HttpURLConnection手动设User-Agent、Referer等头,并设置超时;Files.copy不建父目录,须先Files.createDirectories();BufferedInputStream对小图多连发提速明显;文件后缀须与实际Content-Type一致,不可信URL后缀。

用 URL 打开连接后,为什么 getInputStream() 报 IOException: Server returned HTTP response code: 403
很多图片链接看似能浏览器打开,但 Java 直接 new URL(url).openStream() 就 403,本质是服务器校验了 User-Agent。默认 JDK 的 URL 请求头里 User-Agent 是空或极简字符串,被当成爬虫拦截。
解决方法不是换库,而是手动构造 HttpURLConnection 并设置请求头:
- 调用
url.openConnection()得到HttpURLConnection实例,不能直接openStream() - 必须在
connect()前设置:conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36") - 部分站点还校验
Referer,可追加:conn.setRequestProperty("Referer", "https://example.com/") - 别漏掉
conn.setConnectTimeout(5000)和conn.setReadTimeout(5000),否则卡死没提示
Files.copy() 拷贝输入流时抛 NoSuchFileException:目标目录不存在怎么办
Files.copy(inputStream, path, StandardCopyOption.REPLACE_EXISTING) 只负责拷贝文件内容,**完全不创建父目录**。如果 path 是 ./images/cat.jpg,而 ./images 不存在,就直接炸。
正确做法分两步,且顺序不能错:
立即学习“Java免费学习笔记(深入)”;
- 先用
Files.createDirectories(path.getParent())确保父目录存在(它会递归建多级) - 再调用
Files.copy(),传入StandardCopyOption.REPLACE_EXISTING防止同名文件报错 - 注意:不要用
path.toFile().mkdirs()—— 它对路径含中文或特殊字符时可能失败,FilesAPI 更健壮
用 BufferedInputStream 包一层 URL 输入流,真能提升下载速度吗
能,但只在「小图多连发」场景下明显。JDK 默认的 URL 输入流底层没有缓冲,每次 read() 都可能触发一次系统调用,尤其下载几十个 10KB 左右的图标时,I/O 开销翻倍。
实操建议很直接:
- 一律包一层:
new BufferedInputStream(conn.getInputStream(), 8192)(8KB 缓冲区足够,不必盲目调大) - 别用
BufferedInputStream去包FileInputStream这类本地流——那是画蛇添足 - 如果后续还要解析图片(比如用
ImageIO.read()),它内部已自带缓冲,外层再套一层意义不大
下载后的图片打不开?检查 Content-Type 和文件扩展名是否匹配
服务器返回的 Content-Type(如 image/jpeg)只是提示,不代表实际二进制数据就是该格式。更常见的是:链接是 .png 后缀,但服务器实际返回了 webp 数据,你硬存成 .png,双击就打不开。
稳妥做法是「以实际内容为准」:
- 读取响应头:
conn.getContentType(),提取主类型(如image/webp→webp) - 若为空或不可信,用
URLConnection.guessContentTypeFromStream()猜(需前 128 字节) - 最终文件名后缀必须和真实类型一致,例如:
Paths.get("img", "a.jpg").resolveSibling("a.webp") - 别依赖 URL 路径里的后缀——它可能是伪造的,或者服务器做了重定向
网络 I/O 不像本地文件操作那么“老实”,每个环节都可能悄悄变形。最危险的不是代码写错,而是把「看起来能跑通」当成「逻辑没问题」。










