InputStreamReader 不会自动识别 <meta> 标签编码,必须显式指定 charset;正确流程是先读取字节流、从响应头或 HTML meta 中提取编码,再用该编码构造 InputStreamReader。

InputStreamReader 本身不会自动识别 <meta> 标签里的编码,它只是一个基于字节流构造字符流的桥梁,必须显式传入 charset 名称(如 "UTF-8"),否则会使用平台默认编码,极易导致乱码。
真正实现“从 <meta> 标签中识别编码”的逻辑,需要你先读取原始字节流 → 解析 HTML 片段 → 提取 charset= 值 → 再用该值构造 InputStreamReader。这是一个两步过程,不能跳过。
以下是实用、可落地的关键步骤:
先按字节读取,不急于转成字符串
避免一上来就用 InputStreamReader 指定错误编码。应先获取原始 InputStream,并限制读取范围(比如前 2KB),用于分析编码线索:
立即学习“Java免费学习笔记(深入)”;
URLConnection conn = new URL(url).openConnection();
conn.setConnectTimeout(5000);
conn.setReadTimeout(10000);
conn.setRequestProperty("User-Agent", "Mozilla/5.0");
InputStream is = conn.getInputStream();
byte[] headBytes = new byte[2048];
int len = is.read(headBytes);
is.close(); // 后续需重新打开或用 ByteArrayInputStream 复用从响应头优先提取 charset
HTTP Content-Type 头部最权威,解析它比解析 <meta> 更快、更可靠:
String contentType = conn.getHeaderField("Content-Type");
if (contentType != null) {
int charsetIndex = contentType.indexOf("charset=");
if (charsetIndex != -1) {
String charset = contentType.substring(charsetIndex + 8).trim().split(";")[0].replaceAll("["'\s]", "");
return charset; // 如 "utf-8"、"gbk"、"ISO-8859-1"
}
}再从 HTML 字节中查找 <meta charset="..."> 或 <meta ... content="text/html; charset=...">
将前面读到的 headBytes 转为字符串(用 ISO-8859-1 安全解码,因它能一对一映射字节,不会丢数据):
String headStr = new String(headBytes, 0, len, StandardCharsets.ISO_8859_1);
// 匹配 <meta charset="utf-8">
Pattern p1 = Pattern.compile("<meta[^>]+charset[\s]*=[\s]*["']([^"']*)["']", Pattern.CASE_INSENSITIVE);
// 匹配 <meta http-equiv="Content-Type" content="text/html; charset=gbk">
Pattern p2 = Pattern.compile("<meta[^>]+http-equiv[\s]*=[\s]*["']Content-Type["'][^>]*content[\s]*=[\s]*["'][^"']*charset=([^"';]*)", Pattern.CASE_INSENSITIVE);
Matcher m1 = p1.matcher(headStr);
if (m1.find()) return m1.group(1).trim();
Matcher m2 = p2.matcher(headStr);
if (m2.find()) return m2.group(1).trim();最后才用识别出的编码构造 InputStreamReader
确认编码后,重新打开连接(或用 ByteArrayInputStream 复用字节),再包装 InputStreamReader:
InputStream freshIs = conn.getInputStream(); // 或 new ByteArrayInputStream(headBytes)
InputStreamReader reader = new InputStreamReader(freshIs, detectedCharset);
BufferedReader br = new BufferedReader(reader);
String line;
while ((line = br.readLine()) != null) {
// 安全读取,无乱码
}补充建议:遇到无法识别时设安全兜底
- 若 header 和 meta 都没找到,可尝试用 cpdetector 或 juniversalchardet 库做内容特征检测
- 绝对不要直接用
new InputStreamReader(is)(无参构造),那等价于System.getProperty("file.encoding"),Windows 上常是GBK,在 UTF-8 页面上必然乱码
整个流程本质是:先猜编码,再读内容。InputStreamReader 只负责“执行”,不负责“猜测”。



















