Files.newInputStream()仅返回懒加载流,不加载文件到内存;关键在于分块读取(如8KB缓冲区)、禁用readAllBytes、配合BufferedInputStream或直接传递流给MessageDigest/Files.copy等下游处理器。

<p>Files.newInputStream() 本身不会将整个文件加载到堆内存,它只返回一个 <strong>懒加载的 InputStream</strong>,读取行为完全由你后续调用 read()、readFully() 或封装的工具(如 BufferedInputStream)控制。关键不在于如何“创建流”,而在于**如何使用这个流避免一次性读入全部内容**。</p>
<H3>用流式分块读取,不缓存全量数据</H3>
<p>直接调用 <code>inputStream.read(byte[])</code> 并指定合理缓冲区大小(如 8KB–64KB),每次只读一小块,处理完立即丢弃引用:</p>
<ul>
<li>避免用 <code>Files.readAllBytes()</code> 或 <code>Files.readString()</code> —— 它们会把整个文件读进 byte[]/String,必然 OOM</li>
<li>推荐缓冲区大小:8192(8KB)或 65536(64KB),兼顾 I/O 效率与内存占用,无需更大</li>
<li>示例:<font color="gray">// 不要这样</font><br><code>byte[] all = Files.readAllBytes(path); // ❌ 全部进堆</code><br><font color="gray">// 要这样</font><br><code>try (var is = Files.newInputStream(path)) {<br> byte[] buf = new byte[8192];<br> int n;<br> while ((n = is.read(buf)) != -1) {<br> // 处理 buf[0..n),不保留 buf 全局引用<br> }<br>}</code></li>
</ul>
<H3>配合 BufferedInputStream 提升效率(可选但推荐)</H3>
<p>底层 FileInputStream 的单字节 read() 性能差,加一层 BufferedInputStream 可减少系统调用次数,且<strong>不增加整体内存压力</strong>:</p>
<ul>
<li>BufferedInputStream 内部仅维护一个固定大小的缓冲区(默认 8KB),不是为整个文件分配空间</li>
<li>仍需你自己循环 read(),它只是让多次小 read() 合并成一次底层读取</li>
<li>示例:<code>try (var is = new BufferedInputStream(Files.newInputStream(path), 32768)) { ... }</code></li>
</ul>
<H3>传递流给下游处理器,避免中间 byte[]</H3>
<p>如果目标是解析、校验、转存或上传,优先让下游组件直接消费 InputStream,跳过“读到内存再传”这一步:</p>
<ul>
<li>例如用 <code>MessageDigest.update(InputStream)</code> 计算 SHA256,无需先读全文件</li>
<li>用 <code>Files.copy(is, targetPath)</code> 直接管道复制,JDK 内部自动分块</li>
<li>HTTP 客户端(如 OkHttp、Apache HttpClient)支持直接传 InputStream 上传大文件</li>
</ul>
<H3>注意 close() 和 try-with-resources</H3>
<p>InputStream 持有操作系统文件句柄,不关闭会导致资源泄漏,尤其在高并发读大文件时:</p>
<ul>
<li>必须用 try-with-resources(推荐)或显式 finally 关闭</li>
<li>不要依赖 finalize 或 GC —— 文件句柄不会被及时释放</li>
<li>即使只读了几 KB 就异常退出,也要确保流关闭</li>
</ul>