一、URL 基础概念
URL 的定义
URL(Uniform Resource Locator,统一资源定位符)是互联网上用于标识和定位资源的字符串。它提供了访问资源的标准方式,包括协议、主机名、路径等组成部分。
URL 的组成结构
一个标准的 URL 通常包含以下部分:
协议://主机名:端口号/路径?查询参数#片段标识
例如:
https://www.example.com:443/path/to/resource?param1=value1¶m2=value2#section1
URL 的作用
- 资源定位:唯一标识互联网上的资源位置
- 协议指定:明确访问资源使用的协议(HTTP、HTTPS、FTP等)
- 参数传递:通过查询字符串传递额外参数
- 片段导航:定位到文档的特定部分
URL 在 Java 中的表示
Java 中通过 java.net.URL
类表示 URL 对象:
URL url = new URL("https://www.example.com/path");
注意事项
- URL 需要正确编码特殊字符(如空格编码为 %20)
- 不同协议可能有不同的 URL 格式要求
- URL 长度可能受浏览器和服务器的限制
- 区分大小写(取决于服务器实现)
URL 的组成部分
URL(Uniform Resource Locator)是统一资源定位符的缩写,用于标识互联网上的资源位置。一个标准的 URL 由多个部分组成,每个部分都有特定的含义和作用。
1. 协议(Scheme)
- 定义:URL 的开头部分,表示访问资源所使用的协议。
- 常见协议:
http
:超文本传输协议(未加密)https
:加密的 HTTP 协议ftp
:文件传输协议file
:本地文件协议
- 示例:
https://www.example.com
https
是协议部分。
2. 主机(Host)
- 定义:资源所在的服务器的域名或 IP 地址。
- 示例:
https://www.example.com
www.example.com
是主机部分。
3. 端口(Port)
- 定义:服务器上用于访问资源的端口号。如果未指定,则使用协议的默认端口(如 HTTP 默认 80,HTTPS 默认 443)。
- 示例:
https://www.example.com:8080
8080
是端口部分。
4. 路径(Path)
- 定义:服务器上资源的具体位置,通常对应文件或目录。
- 示例:
https://www.example.com/blog/post1
/blog/post1
是路径部分。
5. 查询参数(Query)
- 定义:以
?
开头,包含键值对(key=value
),多个参数用&
分隔。 - 示例:
https://www.example.com/search?q=java&page=1
q=java&page=1
是查询部分。
6. 片段(Fragment)
- 定义:以
#
开头,通常用于标识页面内的锚点(如 HTML 的id
)。 - 示例:
https://www.example.com/page#section1
section1
是片段部分。
完整 URL 示例
https://www.example.com:8080/blog/post1?q=java#comments
- 协议:
https
- 主机:
www.example.com
- 端口:
8080
- 路径:
/blog/post1
- 查询:
q=java
- 片段:
comments
注意事项
- 编码问题:URL 中不允许直接包含空格或特殊字符(如
?
、#
),需使用 URL 编码(如空格为%20
)。 - 默认端口:通常省略默认端口(如 HTTP 的 80),显式指定端口仅用于非标准情况。
- 区分大小写:主机名不区分大小写,但路径和查询参数可能区分(取决于服务器实现)。
URL 与 URI 的区别与联系
概念定义
-
URI (Uniform Resource Identifier)
统一资源标识符,用于唯一标识某个资源。URI 是一个更广泛的概念,包含 URL 和 URN。 -
URL (Uniform Resource Locator)
统一资源定位符,是 URI 的子集,不仅标识资源,还提供访问资源的具体路径(如协议、主机名、路径等)。
核心区别
-
范围不同
- URI 是抽象概念(标识资源)
- URL 是具体实现(定位资源)
-
功能差异
- URI 只需保证唯一性(如
ISBN:978-7-04-049487-6
是 URI 但不是 URL) - URL 必须包含访问方式(如
https://example.com/page
)
- URI 只需保证唯一性(如
联系
- 包含关系
所有 URL 都是 URI,但并非所有 URI 都是 URL。
关系式:URL ⊂ URI
示例对比
类型 | 示例 | 说明 |
---|---|---|
URI 非 URL | urn:isbn:0451450523 | 标识书籍但无访问路径 |
URL | https://example.com/api | 包含协议和访问路径 |
常见误区
-
错误等价
认为 URI 和 URL 完全等同(实际是包含关系)。 -
混淆 URN
忽略 URI 的另一种形式 URN(永久资源名称),如urn:uuid:6e8bc430-9c3a-11d9-9669-0800200c9a66
。
代码示例(Java)
import java.net.URI;
import java.net.URL;
public class UriVsUrl {
public static void main(String[] args) throws Exception {
// URI 示例(可以是 URL 或 URN)
URI uri = new URI("https://example.com");
System.out.println("URI: " + uri.getScheme()); // 输出 https
// URL 示例(必须是可定位的)
URL url = new URL("https://example.com/api");
System.out.println("URL: " + url.getPath()); // 输出 /api
}
}
使用场景
- 用 URI 时:需要资源标识但无需访问(如 XML 命名空间)
- 用 URL 时:必须通过网络获取资源(如下载文件、调用 API)
URL 编码与解码
概念定义
URL 编码(也称为百分号编码)是将 URL 中的特殊字符转换为 %
后跟两位十六进制数的形式,以确保 URL 的合法性和安全性。解码则是将编码后的 URL 还原为原始字符串。
使用场景
- 传递特殊字符:当 URL 中包含空格、中文等非 ASCII 字符时,需编码。
- 表单提交:GET 请求的表单数据会附加在 URL 中,需编码。
- 防止注入攻击:避免恶意字符破坏 URL 结构。
常见字符编码示例
- 空格 →
%20
/
→%2F
?
→%3F
- 中文“你” →
%E4%BD%A0
Java 实现
编码
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
String original = "你好 world!";
String encoded = URLEncoder.encode(original, StandardCharsets.UTF_8);
System.out.println(encoded); // 输出:%E4%BD%A0%E5%A5%BD+world%21
解码
import java.net.URLDecoder;
String encoded = "%E4%BD%A0%E5%A5%BD+world%21";
String decoded = URLDecoder.decode(encoded, StandardCharsets.UTF_8);
System.out.println(decoded); // 输出:你好 world!
注意事项
- 字符集一致性:编码和解码必须使用相同字符集(如 UTF-8)。
- 无需编码的字符:字母、数字和
-_.~
通常无需编码。 - 完整 URL 编码:仅对参数部分编码,而非整个 URL(如
?key=value
部分)。
URL 构造方法
URL 类提供了多个构造方法用于创建 URL 对象,以下是常见的几种:
URL(String spec)
通过完整的 URL 字符串创建 URL 对象。
URL url = new URL("https://www.example.com:8080/path/to/resource?query=value#fragment");
URL(String protocol, String host, int port, String file)
通过指定协议、主机、端口和文件路径创建 URL 对象。
URL url = new URL("https", "www.example.com", 8080, "/path/to/resource");
URL(String protocol, String host, String file)
通过指定协议、主机和文件路径创建 URL 对象(使用默认端口)。
URL url = new URL("https", "www.example.com", "/path/to/resource");
URL(URL context, String spec)
基于一个已有的 URL(context)和一个相对路径创建新的 URL 对象。
URL baseUrl = new URL("https://www.example.com/base/");
URL relativeUrl = new URL(baseUrl, "relative/path");
注意事项
- 构造方法会抛出
MalformedURLException
,需要进行异常处理 - 如果端口号为 -1,将使用协议的默认端口
- 相对 URL 解析基于 context URL 的路径
- 特殊字符需要进行 URL 编码处理
示例代码
try {
// 绝对URL
URL absUrl = new URL("https://www.example.com/api/data");
// 相对URL
URL baseUrl = new URL("https://www.example.com/api/");
URL relUrl = new URL(baseUrl, "users");
System.out.println("Absolute URL: " + absUrl);
System.out.println("Relative URL: " + relUrl);
} catch (MalformedURLException e) {
e.printStackTrace();
}
URL 常见协议
URL(统一资源定位符)中的协议(scheme)指定了访问资源的方式。以下是 Java 网络编程中常见的 URL 协议:
HTTP
- 定义:超文本传输协议,用于传输网页数据
- 特点:
- 默认端口 80
- 明文传输,不安全
- 无状态协议
- 示例:
http://example.com
HTTPS
- 定义:HTTP 的安全版本
- 特点:
- 默认端口 443
- 使用 SSL/TLS 加密
- 需要证书验证
- 示例:
https://example.com
FTP
- 定义:文件传输协议
- 特点:
- 默认端口 21(控制端口)
- 支持文件上传和下载
- 可匿名访问或需要认证
- 示例:
ftp://ftp.example.com
File
- 定义:本地文件协议
- 特点:
- 访问本地文件系统
- 路径格式取决于操作系统
- 示例:
file:/C:/path/to/file.txt
其他协议
- Jar:访问 JAR 文件中的资源
- Mailto:发送电子邮件
- Data:直接在 URL 中包含数据(Base64 编码)
协议选择注意事项
- 安全性:优先使用 HTTPS
- 功能需求:文件传输用 FTP,网页访问用 HTTP/HTTPS
- 协议支持:确保 Java 运行时支持所需协议
URL 合法性验证
概念定义
URL 合法性验证是指检查一个字符串是否符合 URL(统一资源定位符)的标准格式。合法的 URL 应包含协议(如 http
、https
)、域名(或 IP 地址)、端口(可选)、路径(可选)、查询参数(可选)和片段(可选)等部分。
使用场景
- 用户输入校验:确保用户输入的 URL 格式正确,避免无效请求。
- 爬虫程序:过滤非法 URL,防止程序处理无效链接。
- API 开发:验证客户端传递的 URL 参数是否合法。
常见误区
- 忽略协议:认为
www.example.com
是合法 URL(实际缺少协议部分)。 - 过度依赖正则:复杂正则可能无法覆盖所有合法 URL(如国际化域名)。
- 忽略编码问题:未对特殊字符(如空格、中文)进行 URL 编码处理。
验证方法
1. 正则表达式验证
import java.util.regex.Pattern;
public class URLValidator {
private static final String URL_REGEX = "^(https?|ftp)://[\\w-]+(\\.[\\w-]+)+([\\w-.,@?^=%&:/~+#]*[\\w@?^=%&/~+#])?$";
private static final Pattern URL_PATTERN = Pattern.compile(URL_REGEX);
public static boolean isValid(String url) {
return URL_PATTERN.matcher(url).matches();
}
}
2. 使用 java.net.URL
类
import java.net.MalformedURLException;
import java.net.URL;
public class URLValidator {
public static boolean isValid(String url) {
try {
new URL(url);
return true;
} catch (MalformedURLException e) {
return false;
}
}
}
3. Apache Commons Validator
import org.apache.commons.validator.routines.UrlValidator;
public class URLValidator {
public static boolean isValid(String url) {
String[] schemes = {"http", "https"};
UrlValidator validator = new UrlValidator(schemes);
return validator.isValid(url);
}
}
注意事项
- 协议限制:明确是否需要支持
http
、https
、ftp
等特定协议。 - 国际化域名:处理包含非 ASCII 字符的域名(如中文域名)。
- 编码处理:验证前先对 URL 进行编码(使用
URLEncoder
)。 - 性能考量:高频验证场景建议预编译正则表达式。
完整示例(包含编码处理)
import java.net.URL;
import java.net.URLEncoder;
public class URLValidator {
public static boolean isValid(String url) {
try {
// 先尝试直接验证
new URL(url);
return true;
} catch (Exception e1) {
try {
// 失败后尝试编码再验证
new URL(URLEncoder.encode(url, "UTF-8"));
return true;
} catch (Exception e2) {
return false;
}
}
}
}
相对 URL 与绝对 URL
概念定义
- 绝对 URL:包含完整路径的 URL,包括协议(如
http
/https
)、域名、端口(可选)、路径和查询参数。例如:
https://www.example.com:8080/api/data?id=123
- 相对 URL:基于当前页面或资源的路径,省略了协议和域名等部分。例如:
/images/logo.png
或../data/list
。
使用场景
- 绝对 URL:
- 跨域请求(如从
https://siteA.com
访问https://siteB.com
)。 - 需要明确指定完整路径的场景(如邮件中的链接)。
- 跨域请求(如从
- 相对 URL:
- 同一网站内的资源引用(如 CSS、JS 文件)。
- 简化代码,避免硬编码域名(便于迁移或切换环境)。
示例代码(Java 中解析)
import java.net.URI;
import java.net.URISyntaxException;
public class URLExample {
public static void main(String[] args) throws URISyntaxException {
// 绝对 URL
URI absoluteUrl = new URI("https://example.com/api/data");
System.out.println("Host: " + absoluteUrl.getHost()); // 输出: example.com
// 相对 URL 解析(需结合基础 URL)
URI baseUri = new URI("https://example.com/docs/");
URI relativeUri = new URI("../images/logo.png");
URI resolvedUri = baseUri.resolve(relativeUri);
System.out.println(resolvedUri); // 输出: https://example.com/images/logo.png
}
}
注意事项
- 相对 URL 的基准路径:
浏览器或代码中解析相对 URL 时,依赖当前页面的基础 URL(通过<base>
标签或代码上下文指定)。 - 路径符号含义:
/
开头:从根目录开始(如/api
→https://domain.com/api
)。../
:向上跳转一级目录。
- 编码问题:
特殊字符(如空格、中文)需编码处理(如%20
)。 - 性能考虑:
相对 URL 可能减少请求头大小,但需注意缓存策略的影响。
URL 标准化处理
概念定义
URL 标准化(URL Normalization)是指将不同形式的 URL 转换为统一的标准格式的过程。由于同一资源可能对应多种 URL 形式(如大小写差异、端口默认省略、路径冗余等),标准化可消除这些差异,确保 URL 的唯一性和一致性。
常见标准化场景
-
协议和主机名处理
- 将
http://example.com
和HTTP://EXAMPLE.COM
统一为http://example.com
(协议小写,主机名小写)。 - 默认端口省略:
http://example.com:80/
→http://example.com/
。
- 将
-
路径规范化
- 移除冗余路径:
/a/b/../c
→/a/c
。 - 补充末尾斜杠:
http://example.com/dir
→http://example.com/dir/
(若为目录)。
- 移除冗余路径:
-
查询参数排序
- 参数键值对按字母排序:
?b=2&a=1
→?a=1&b=2
(可选,取决于业务需求)。
- 参数键值对按字母排序:
-
编码解码处理
- 保留字符编码:如空格转为
%20
,中文转为 UTF-8 编码。 - 解码冗余编码:
%7E
→~
(若原始字符无需编码)。
- 保留字符编码:如空格转为
注意事项
-
区分大小写
- 部分服务器对路径大小写敏感(如 Linux),需根据实际场景处理。
-
锚点(Fragment)保留
#section
不参与标准化,因其仅用于客户端定位。
-
敏感信息保护
- 避免标准化时泄露敏感信息(如
https
→http
)。
- 避免标准化时泄露敏感信息(如
Java 示例代码
import java.net.URI;
import java.net.URISyntaxException;
public class URLNormalizer {
public static String normalize(String url) throws URISyntaxException {
URI uri = new URI(url).normalize();
return uri.toASCIIString(); // 返回标准化后的字符串
}
public static void main(String[] args) throws URISyntaxException {
String rawUrl = "HTTP://Example.com:80/a/../b?c=2&a=1";
System.out.println(normalize(rawUrl));
// 输出:http://example.com/b?a=1&c=2
}
}
从 URL 获取输入流的方法
1. 使用 URL.openStream()
URL.openStream()
是最简单的方法,直接返回一个 InputStream
,用于读取 URL 指向的资源内容。
示例代码:
try {
URL url = new URL("https://example.com");
try (InputStream inputStream = url.openStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
}
} catch (IOException e) {
e.printStackTrace();
}
特点:
- 适用于简单的 HTTP GET 请求。
- 自动处理连接建立和关闭。
2. 使用 URL.openConnection()
获取 HttpURLConnection
URL.openConnection()
返回一个 URLConnection
对象(通常是 HttpURLConnection
),可以更灵活地控制请求(如设置请求方法、超时、请求头等)。
示例代码:
try {
URL url = new URL("https://example.com");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(5000);
connection.setReadTimeout(5000);
try (InputStream inputStream = connection.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
}
} catch (IOException e) {
e.printStackTrace();
}
特点:
- 支持设置请求方法(GET、POST 等)。
- 可以添加请求头(如
setRequestProperty
)。 - 支持超时设置。
3. 处理错误流
如果请求失败(如 HTTP 404 或 500),服务器可能返回错误流(通过 HttpURLConnection.getErrorStream()
获取)。
示例代码:
try {
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
int responseCode = connection.getResponseCode();
InputStream inputStream = responseCode >= 400 ?
connection.getErrorStream() : connection.getInputStream();
// 读取 inputStream
} catch (IOException e) {
e.printStackTrace();
}
注意事项
- 资源释放:确保关闭
InputStream
和HttpURLConnection
(使用try-with-resources
或手动关闭)。 - 异常处理:网络请求可能抛出
IOException
,需捕获处理。 - 性能优化:对于大文件下载,建议使用缓冲流(如
BufferedInputStream
)。 - HTTPS 支持:直接支持 HTTPS,无需额外配置。
二、HttpURLConnection 基本使用
HttpURLConnection 类概述
概念定义
HttpURLConnection
是 Java 标准库中用于发送 HTTP 请求和接收 HTTP 响应的类,继承自 URLConnection
。它支持 HTTP 协议的基本功能,如 GET、POST、PUT、DELETE 等请求方法,以及处理响应状态码、响应头等。
使用场景
- 发送 HTTP 请求:如从服务器获取数据(GET)或提交数据(POST)。
- 处理 RESTful API:与后端服务交互,如调用 Web API。
- 文件下载/上传:通过 HTTP 协议传输文件。
核心特点
- 基于 URL:通过
URL
对象创建连接。 - 支持多种 HTTP 方法:通过
setRequestMethod()
设置。 - 可配置请求头:如
setRequestProperty()
设置Content-Type
。 - 支持重定向:默认自动处理 3xx 重定向(可通过
setInstanceFollowRedirects()
禁用)。 - 超时控制:通过
setConnectTimeout()
和setReadTimeout()
设置。
示例代码(GET 请求)
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class HttpExample {
public static void main(String[] args) throws Exception {
URL url = new URL("https://example.com/api/data");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
int responseCode = conn.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String inputLine;
StringBuilder response = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
System.out.println(response.toString());
} else {
System.out.println("GET request failed: " + responseCode);
}
conn.disconnect();
}
}
注意事项
- 资源释放:必须调用
disconnect()
关闭连接(或在 try-with-resources 中管理)。 - 异常处理:需捕获
IOException
,处理网络或协议错误。 - 线程安全:每个
HttpURLConnection
实例仅用于一次请求,不可复用。 - 性能考虑:频繁创建连接开销较大,建议使用连接池(如
HttpClient
)。 - HTTPS 支持:需配置
HttpsURLConnection
或信任证书(生产环境建议使用 CA 证书)。
获取 HttpURLConnection 实例
基本步骤
- 通过
URL
类创建 URL 对象 - 调用
openConnection()
方法获取URLConnection
实例 - 将
URLConnection
强制转换为HttpURLConnection
示例代码
URL url = new URL("https://example.com/api");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
注意事项
- 协议支持:URL 必须以
http://
或https://
开头 - 类型转换:如果 URL 不是 HTTP 协议,强制转换会抛出
ClassCastException
- 资源释放:使用后应调用
disconnect()
方法释放连接
高级用法
// 设置请求超时
connection.setConnectTimeout(5000);
connection.setReadTimeout(10000);
// 设置请求方法
connection.setRequestMethod("GET");
// 添加请求头
connection.setRequestProperty("Content-Type", "application/json");
异常处理
需要处理以下异常:
MalformedURLException
:URL格式错误IOException
:网络连接问题ProtocolException
:请求方法设置错误
设置请求方法(GET/POST/PUT/DELETE)
概念定义
请求方法是 HTTP 协议中定义的操作类型,用于指定客户端希望服务器执行的动作。常见方法包括:
- GET:请求获取资源(如查询数据)。
- POST:提交数据到服务器(如创建资源)。
- PUT:更新服务器上的资源(全量替换)。
- DELETE:删除服务器上的资源。
在 HttpURLConnection 中设置方法
通过 setRequestMethod(String method)
方法设置,需在 connect()
前调用:
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST"); // 设置为 POST 请求
注意事项
- 方法名必须大写(如
"GET"
而非"get"
),否则抛出ProtocolException
。 - 默认方法为 GET,若不显式设置,则自动使用 GET。
- 部分方法受服务器支持限制(如 PUT/DELETE 需后端接口支持)。
- POST/PUT 需配合输出流:若需传递请求体,需先调用
setDoOutput(true)
:connection.setDoOutput(true); try (OutputStream os = connection.getOutputStream()) { os.write("data=example".getBytes()); }
示例代码(GET 和 POST 对比)
// GET 请求示例
URL getUrl = new URL("https://api.example.com/data?id=123");
HttpURLConnection getConn = (HttpURLConnection) getUrl.openConnection();
getConn.setRequestMethod("GET");
// POST 请求示例
URL postUrl = new URL("https://api.example.com/create");
HttpURLConnection postConn = (HttpURLConnection) postUrl.openConnection();
postConn.setRequestMethod("POST");
postConn.setDoOutput(true);
try (OutputStream os = postConn.getOutputStream()) {
os.write("name=test&age=25".getBytes());
}
常见误区
- 混淆 POST 和 PUT:POST 用于创建,PUT 用于更新(需明确资源 ID)。
- 忘记设置输出流:POST/PUT 未调用
setDoOutput(true)
导致数据无法发送。 - 方法拼写错误:如
"get"
或"Post"
会触发异常。
设置请求头参数
概念定义
请求头(Request Headers)是HTTP请求的一部分,用于向服务器传递额外的信息。通过HttpURLConnection
可以设置自定义请求头参数,常见的如User-Agent
、Content-Type
、Authorization
等。
使用场景
- 身份验证:通过
Authorization
头传递令牌(如Bearer Token)。 - 内容协商:通过
Accept
头指定客户端期望的响应格式(如JSON/XML)。 - 跨域请求:设置
Origin
或CORS
相关头。 - 自定义元数据:传递业务相关的自定义头(如
X-Request-ID
)。
核心方法
connection.setRequestProperty(String key, String value);
- key:请求头字段名(如
Content-Type
)。 - value:对应字段值(如
application/json
)。
示例代码
URL url = new URL("https://api.example.com/data");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
// 设置常用请求头
connection.setRequestProperty("User-Agent", "MyApp/1.0");
connection.setRequestProperty("Content-Type", "application/json");
connection.setRequestProperty("Authorization", "Bearer abc123");
// 自定义头
connection.setRequestProperty("X-Custom-Header", "value");
// 发送请求
connection.setRequestMethod("GET");
int responseCode = connection.getResponseCode();
注意事项
- 时机问题:需在
connect()
或getResponseCode()
之前设置请求头。 - 覆盖行为:重复调用
setRequestProperty
会覆盖同名头字段。 - 无效字符:避免在头字段名/值中使用换行符等非法字符。
- 敏感信息:不要通过请求头明文传输密码等敏感数据。
- 只读头字段:如
Host
、Content-Length
等由系统自动管理,手动设置无效。
常见误区
- 错误认为
addRequestProperty
可追加多个值(实际应使用逗号分隔的字符串)。 - 忽略
Content-Type
对请求体格式的影响(如表单需用application/x-www-form-urlencoded
)。
获取响应状态码
概念定义
响应状态码是HTTP协议中服务器返回的一个3位数字代码,用于表示请求的处理结果。常见的状态码包括:
- 200:请求成功
- 404:资源未找到
- 500:服务器内部错误
使用场景
- 判断请求是否成功
- 根据不同的状态码执行不同的业务逻辑
- 错误处理和调试
获取方法
通过HttpURLConnection的getResponseCode()
方法获取:
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
int responseCode = connection.getResponseCode();
注意事项
- 调用
getResponseCode()
会自动发送请求(如果尚未发送) - 对于HTTP错误状态码(如404、500),该方法不会抛出异常
- 建议先检查状态码再读取响应内容
示例代码
try {
URL url = new URL("https://example.com/api");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
int responseCode = connection.getResponseCode();
System.out.println("Response Code: " + responseCode);
if (responseCode == HttpURLConnection.HTTP_OK) {
// 处理成功响应
} else {
// 处理错误响应
}
} catch (IOException e) {
e.printStackTrace();
}
常见状态码常量
HttpURLConnection类中定义了常用的HTTP状态码常量:
HTTP_OK = 200
HTTP_NOT_FOUND = 404
HTTP_INTERNAL_ERROR = 500
读取响应头信息
概念定义
响应头信息(Response Headers)是HTTP响应中包含的元数据,用于描述服务器返回的资源属性、控制缓存行为、设置Cookie等。通过HttpURLConnection
可以获取这些头信息,以便客户端正确处理服务器响应。
使用场景
- 内容类型检查:通过
Content-Type
头判断返回数据的格式(如application/json
)。 - 缓存控制:解析
Cache-Control
或Expires
头管理本地缓存。 - 重定向处理:根据
Location
头处理3xx重定向响应。 - 认证与Cookie:读取
Set-Cookie
头或认证相关头信息。
关键方法
// 获取单个头字段的值(如"Content-Type")
String contentType = connection.getHeaderField("Content-Type");
// 获取所有头字段的Map(键不区分大小写)
Map<String, List<String>> headers = connection.getHeaderFields();
// 获取特定索引的头字段值(从0开始)
String firstHeader = connection.getHeaderField(0); // 如"HTTP/1.1 200 OK"
示例代码
HttpURLConnection connection = (HttpURLConnection) new URL("https://example.com").openConnection();
connection.setRequestMethod("GET");
// 读取响应头
System.out.println("Content-Type: " + connection.getHeaderField("Content-Type"));
System.out.println("Response Code: " + connection.getResponseCode());
// 遍历所有头字段
connection.getHeaderFields().forEach((key, values) -> {
System.out.println(key + ": " + values);
});
注意事项
- 大小写不敏感:头字段名(如
Content-Type
与content-type
)视为相同。 - 多值头字段:某些头(如
Set-Cookie
)可能返回多个值,需通过getHeaderFields()
获取List<String>
。 - 连接状态:需先调用
getResponseCode()
或connect()
确保连接已建立,否则可能返回null
。 - 性能考虑:频繁读取头信息时,建议缓存结果而非重复调用。
常见误区
- 错误认为
getHeaderField(0)
返回状态码(实际返回完整状态行,需用getResponseCode()
获取状态码)。 - 忽略多值头字段(如多个
Set-Cookie
),仅读取第一个值。
获取响应内容
概念定义
在通过 HttpURLConnection
发起 HTTP 请求后,服务器会返回响应数据。获取响应内容是指从服务器返回的输入流中读取数据,并将其转换为可处理的格式(如字符串、字节数组等)。
使用场景
- 从 REST API 获取 JSON 或 XML 数据。
- 下载文件或图片。
- 读取网页 HTML 内容。
常见误区或注意事项
- 未检查响应码:应先检查
getResponseCode()
,确保请求成功(如 200)后再读取响应内容。 - 忽略字符编码:读取文本数据时需明确字符编码(如 UTF-8),否则可能乱码。
- 未关闭流:必须关闭
InputStream
和HttpURLConnection
,避免资源泄漏。 - 大文件处理:直接读取大文件可能导致内存溢出,应使用缓冲流分块读取。
示例代码
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class HttpExample {
public static String getResponseContent(String urlString) throws Exception {
URL url = new URL(urlString);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
try (InputStream inputStream = connection.getInputStream();
BufferedReader reader = new BufferedReader(
new InputStreamReader(inputStream, "UTF-8"))) {
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
return response.toString();
}
} else {
throw new RuntimeException("HTTP请求失败,响应码: " + responseCode);
}
}
public static void main(String[] args) throws Exception {
String content = getResponseContent("https://example.com/api/data");
System.out.println(content);
}
}
关键方法说明
getInputStream()
:获取成功响应(如 200)的输入流。getErrorStream()
:获取错误响应(如 404、500)的输入流。BufferedReader
:按行读取文本数据,提高效率。StringBuilder
:高效拼接字符串,避免频繁创建对象。
HTTP 重定向
HTTP 重定向是指服务器返回一个特殊的响应状态码(如 301、302、307 等),告诉客户端请求的资源已被移动到新的 URL,客户端需要重新向新 URL 发起请求。
常见重定向状态码
- 301 Moved Permanently:永久重定向,资源已永久移动到新 URL。
- 302 Found(或 307 Temporary Redirect):临时重定向,资源暂时移动到新 URL。
- 303 See Other:通常用于 POST 请求后的重定向,要求客户端使用 GET 方法请求新 URL。
HttpURLConnection 处理重定向
默认情况下,HttpURLConnection
会自动处理重定向(跟随重定向)。可以通过 setInstanceFollowRedirects()
方法控制是否自动跟随重定向:
// 禁用自动重定向
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setInstanceFollowRedirects(false);
// 获取响应码
int responseCode = connection.getResponseCode();
// 如果是重定向响应(如 301、302),获取 Location 头
if (responseCode == HttpURLConnection.HTTP_MOVED_PERM
|| responseCode == HttpURLConnection.HTTP_MOVED_TEMP) {
String newUrl = connection.getHeaderField("Location");
// 手动处理重定向
connection = (HttpURLConnection) new URL(newUrl).openConnection();
}
注意事项
-
自动重定向的局限性:
- 默认只支持 GET 和 HEAD 方法的自动重定向。
- POST 请求的重定向可能会被转换为 GET 请求(取决于 HTTP 版本和状态码)。
-
手动处理重定向:
- 禁用自动重定向后,需要手动检查
Location
头并重新发起请求。 - 注意处理重定向循环(通过限制最大重定向次数避免无限循环)。
- 禁用自动重定向后,需要手动检查
-
安全性:
- 重定向可能被恶意利用(如钓鱼攻击),验证重定向目标 URL 的合法性。
示例代码(手动处理重定向)
public static String followRedirects(String originalUrl, int maxRedirects) throws IOException {
String currentUrl = originalUrl;
int redirectCount = 0;
while (redirectCount <= maxRedirects) {
HttpURLConnection connection = (HttpURLConnection) new URL(currentUrl).openConnection();
connection.setInstanceFollowRedirects(false); // 禁用自动重定向
int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_MOVED_PERM
|| responseCode == HttpURLConnection.HTTP_MOVED_TEMP) {
// 获取重定向目标
String location = connection.getHeaderField("Location");
if (location == null) break;
currentUrl = location;
redirectCount++;
} else {
// 非重定向响应,返回最终内容
try (BufferedReader in = new BufferedReader(
new InputStreamReader(connection.getInputStream()))) {
StringBuilder content = new StringBuilder();
String line;
while ((line = in.readLine()) != null) {
content.append(line);
}
return content.toString();
}
}
}
throw new IOException("Too many redirects (> " + maxRedirects + ")");
}
最佳实践
- 对于简单场景,使用默认的自动重定向即可。
- 需要精细控制时(如记录重定向链或处理 POST 请求),禁用自动重定向并手动处理。
- 始终限制最大重定向次数(通常 5-10 次)。
设置连接超时和读取超时
概念定义
-
连接超时(Connection Timeout)
指客户端尝试与服务器建立连接的最大等待时间。如果超过此时间仍未建立连接,则抛出SocketTimeoutException
。 -
读取超时(Read Timeout)
指客户端从服务器读取数据的最大等待时间。如果超过此时间仍未收到数据,则抛出SocketTimeoutException
。
使用场景
- 网络不稳定时,避免程序长时间阻塞。
- 需要控制请求的响应时间,保证用户体验。
- 防止因服务器无响应导致资源浪费。
常见误区
-
混淆两种超时
- 连接超时针对的是建立连接的过程。
- 读取超时针对的是数据传输的过程。
-
设置过短或过长
- 过短:可能导致正常请求被误判为超时。
- 过长:失去超时设置的意义。
-
未处理超时异常
必须捕获SocketTimeoutException
,否则程序可能崩溃。
示例代码
import java.net.HttpURLConnection;
import java.net.URL;
public class TimeoutExample {
public static void main(String[] args) {
try {
URL url = new URL("https://example.com");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
// 设置连接超时为5秒
connection.setConnectTimeout(5000);
// 设置读取超时为10秒
connection.setReadTimeout(10000);
// 发起请求
connection.connect();
// 处理响应...
} catch (Exception e) {
e.printStackTrace();
}
}
}
注意事项
-
单位是毫秒
超时参数的单位是毫秒(ms),例如5000
表示5秒。 -
默认值
- 未设置时,超时为0,表示无限等待(不推荐)。
-
影响范围
- 仅对当前
HttpURLConnection
实例生效。
- 仅对当前
-
代理设置
如果使用代理,超时设置同样适用于代理连接。
关闭连接的正确方式
概念定义
在 Java 中,使用 HttpURLConnection
或 URLConnection
进行网络请求后,必须正确关闭连接以释放系统资源。如果不关闭连接,可能会导致资源泄漏(如文件描述符耗尽)或连接池阻塞等问题。
关闭步骤
- 关闭输入流/输出流:如果打开了
InputStream
或OutputStream
,必须先关闭它们。 - 断开连接:调用
disconnect()
方法(仅对HttpURLConnection
有效)。 - 异常处理:确保在
finally
块中关闭资源,即使发生异常。
示例代码
HttpURLConnection connection = null;
InputStream inputStream = null;
try {
URL url = new URL("https://example.com");
connection = (HttpURLConnection) url.openConnection();
inputStream = connection.getInputStream();
// 处理输入流数据
// ...
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭输入流
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
// 断开连接
if (connection != null) {
connection.disconnect();
}
}
注意事项
- 关闭顺序:必须先关闭流,再断开连接。如果先调用
disconnect()
,可能导致流无法正常关闭。 - Java 7+ 的 try-with-resources:推荐使用
try-with-resources
自动关闭资源:try (InputStream inputStream = connection.getInputStream()) { // 处理输入流 } catch (IOException e) { e.printStackTrace(); } finally { if (connection != null) { connection.disconnect(); } }
- 连接复用:
HttpURLConnection
可能被底层连接池复用,但显式调用disconnect()
会强制关闭连接。 - 输出流的关闭:如果使用了
getOutputStream()
,也需要显式关闭:OutputStream outputStream = null; try { outputStream = connection.getOutputStream(); // 写入数据 } finally { if (outputStream != null) { outputStream.close(); } }
三、高级功能与配置
启用输出流发送请求体
概念定义
通过 HttpURLConnection
的 getOutputStream()
方法获取输出流,可以向服务器发送请求体数据(如 POST 请求的表单数据或 JSON 内容)。需在获取输出流前设置 setDoOutput(true)
,表示需要向连接写入数据。
使用场景
- 提交表单数据(如登录、注册)
- 上传文件或 JSON/XML 数据
- 调用 RESTful API 的 POST/PUT 方法
注意事项
- 顺序敏感:必须先设置
setDoOutput(true)
再获取输出流 - 自动切换方法:调用
getOutputStream()
会将请求方法自动改为 POST(即使显式设置了 GET) - 超时设置:建议配置
setConnectTimeout()
和setReadTimeout()
- 流关闭:必须关闭输出流,否则可能导致资源泄漏
示例代码
// 创建连接
URL url = new URL("http://example.com/api");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setDoOutput(true); // 关键设置
conn.setRequestProperty("Content-Type", "application/json");
// 写入请求体
try (OutputStream os = conn.getOutputStream()) {
String jsonInput = "{\"username\":\"test\", \"password\":\"123\"}";
os.write(jsonInput.getBytes(StandardCharsets.UTF_8));
os.flush();
}
// 处理响应(示例)
int responseCode = conn.getResponseCode();
HTTPS 连接概述
HTTPS(Hypertext Transfer Protocol Secure)是 HTTP 的安全版本,通过 SSL/TLS 协议对传输的数据进行加密,确保通信安全。在 Java 中,HttpsURLConnection
是 HttpURLConnection
的子类,专门用于处理 HTTPS 连接。
核心概念
SSL/TLS 协议
- SSL(Secure Sockets Layer) 和 TLS(Transport Layer Security) 是加密协议,用于在网络通信中提供安全性。
- TLS 是 SSL 的继任者,但通常仍统称为 SSL。
证书验证
- HTTPS 依赖数字证书验证服务器身份。
- Java 使用 信任库(TrustStore) 存储受信任的证书(如 CA 证书)。
- 若服务器证书不在信任库中,或证书无效(如过期、域名不匹配),连接会失败。
使用 HttpsURLConnection
基本流程
- 创建
URL
对象并打开连接。 - 配置请求方法(如
GET
/POST
)和请求头。 - 处理输入/输出流(如读取响应或发送数据)。
- 关闭连接。
示例代码
import javax.net.ssl.HttpsURLConnection;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
public class HttpsExample {
public static void main(String[] args) throws Exception {
URL url = new URL("https://example.com");
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
// 设置请求方法
connection.setRequestMethod("GET");
// 读取响应
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(connection.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
}
connection.disconnect();
}
}
常见问题与注意事项
1. 证书验证失败
- 问题:自签名证书或非权威 CA 签发的证书会导致
SSLHandshakeException
。 - 解决:
- 临时方案:自定义
TrustManager
跳过验证(不推荐生产环境使用)。TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() { public void checkClientTrusted(X509Certificate[] chain, String authType) {} public void checkServerTrusted(X509Certificate[] chain, String authType) {} public X509Certificate[] getAcceptedIssuers() { return null; } } }; SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, trustAllCerts, new SecureRandom()); HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
- 推荐方案:将服务器证书导入 Java 信任库(
cacerts
)或自定义TrustStore
。
- 临时方案:自定义
2. 协议与算法限制
- 明确指定支持的 TLS 版本(如
TLSv1.2
),避免使用不安全的协议(如SSLv3
)。SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
3. 主机名验证
- 默认会验证服务器主机名与证书匹配。如需禁用(仅测试环境):
connection.setHostnameVerifier((hostname, session) -> true);
高级配置
自定义 SSLSocketFactory
- 用于指定密钥库(
KeyStore
)或自定义 SSL 参数:KeyStore keyStore = KeyStore.getInstance("PKCS12"); try (InputStream is = new FileInputStream("client.p12")) { keyStore.load(is, "password".toCharArray()); } SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init( new KeyManager[] { /* 自定义 KeyManager */ }, new TrustManager[] { /* 自定义 TrustManager */ }, new SecureRandom() ); connection.setSSLSocketFactory(sslContext.getSocketFactory());
超时设置
- 避免无限等待:
connection.setConnectTimeout(5000); // 5秒连接超时 connection.setReadTimeout(10000); // 10秒读取超时
总结
- 优先使用
HttpsURLConnection
而非第三方库(如 Apache HttpClient)。 - 生产环境必须严格处理证书验证,避免安全漏洞。
- 通过
SSLContext
和TrustManager
可灵活控制加密行为。
代理服务器(Proxy Server)
代理服务器是位于客户端和目标服务器之间的中间服务器,用于转发客户端的请求和响应。它可以用于匿名访问、内容过滤、缓存加速等场景。
工作原理
- 客户端向代理服务器发送请求。
- 代理服务器将请求转发给目标服务器。
- 目标服务器将响应返回给代理服务器。
- 代理服务器将响应返回给客户端。
使用场景
- 匿名访问:隐藏客户端的真实IP地址。
- 内容过滤:限制访问某些网站或内容。
- 缓存加速:缓存常用资源,提高访问速度。
- 访问控制:限制特定IP或用户的访问权限。
在Java中使用代理
Java中可以通过Proxy
类和HttpURLConnection
设置代理服务器。
示例代码
import java.net.*;
import java.io.*;
public class ProxyExample {
public static void main(String[] args) throws Exception {
// 设置代理服务器地址和端口
Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("proxy.example.com", 8080));
// 创建URL对象
URL url = new URL("http://example.com");
// 打开连接并设置代理
HttpURLConnection conn = (HttpURLConnection) url.openConnection(proxy);
// 设置请求方法
conn.setRequestMethod("GET");
// 获取响应代码
int responseCode = conn.getResponseCode();
System.out.println("Response Code: " + responseCode);
// 读取响应内容
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String inputLine;
StringBuilder response = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
// 输出响应内容
System.out.println(response.toString());
}
}
注意事项
- 代理类型:
Proxy.Type
可以是HTTP
、SOCKS
等,需根据代理服务器类型选择。 - 认证:如果代理服务器需要认证,需设置
Authenticator
。Authenticator.setDefault(new Authenticator() { protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication("username", "password".toCharArray()); } });
- 性能:代理服务器可能会增加网络延迟,需权衡使用。
- 安全性:确保代理服务器可信,避免数据泄露。
Cookie 和 Session 的概念
Cookie
- 定义:Cookie 是服务器发送到用户浏览器并保存在本地的一小块数据(通常为键值对)。
- 特点:
- 存储在客户端(浏览器)。
- 大小有限(通常为 4KB)。
- 可以设置过期时间(会话 Cookie 或持久 Cookie)。
- 每次请求会自动携带同域名的 Cookie。
Session
- 定义:Session 是服务器端存储用户会话数据的机制,通常通过唯一的 Session ID 标识。
- 特点:
- 存储在服务器端(如内存、数据库)。
- 依赖 Cookie 或 URL 重写传递 Session ID。
- 安全性较高(敏感数据不直接暴露给客户端)。
使用场景
Cookie 的典型场景
- 记住登录状态(如
token
)。 - 保存用户偏好(如语言、主题)。
- 跟踪用户行为(如购物车商品)。
Session 的典型场景
- 存储敏感信息(如用户权限、临时验证码)。
- 实现登录会话管理。
- 需要服务器端控制的临时数据(如表单提交防重放)。
在 Java 中操作 Cookie 和 Session
1. 操作 Cookie(示例代码)
// 创建 Cookie
Cookie cookie = new Cookie("username", "user123");
cookie.setMaxAge(60 * 60 * 24); // 设置有效期(秒)
response.addCookie(cookie); // 添加到响应
// 读取 Cookie
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie c : cookies) {
if ("username".equals(c.getName())) {
String value = c.getValue();
break;
}
}
}
2. 操作 Session(示例代码)
// 获取或创建 Session
HttpSession session = request.getSession(true);
// 存储数据
session.setAttribute("cartItems", itemList);
// 读取数据
List<String> items = (List<String>) session.getAttribute("cartItems");
// 销毁 Session
session.invalidate();
常见误区与注意事项
Cookie
- 安全性:
- 敏感数据不应存于 Cookie(如密码)。
- 使用
HttpOnly
和Secure
标志防 XSS 和嗅探。
- 跨域限制:
- 浏览器禁止跨域访问 Cookie(遵循同源策略)。
- 编码问题:
- 值需进行 URL 编码(如
URLEncoder.encode()
)。
- 值需进行 URL 编码(如
Session
- 性能问题:
- 大量 Session 会占用服务器内存(可改用 Redis 存储)。
- 分布式问题:
- 集群环境下需同步 Session(如 Spring Session + Redis)。
- 过期时间:
- 默认依赖服务器配置(如 Tomcat 的
session-timeout
)。
- 默认依赖服务器配置(如 Tomcat 的
扩展:禁用 Cookie 时的 Session 处理
若浏览器禁用 Cookie,可通过 URL 重写传递 Session ID:
String url = response.encodeURL("/path"); // 自动追加 jsessionid
生成类似:/path;jsessionid=123456789
的 URL。
文件上传实现
基本概念
文件上传是指客户端通过HTTP协议将本地文件传输到服务器的过程。通常使用multipart/form-data
编码格式,允许在单个请求中发送二进制数据和表单字段。
核心实现步骤
客户端实现
- HTML表单设置
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="file">
<input type="submit" value="Upload">
</form>
- Java客户端实现(使用HttpURLConnection)
String boundary = "===" + System.currentTimeMillis() + "===";
URL url = new URL("http://example.com/upload");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setDoOutput(true);
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
try (OutputStream out = conn.getOutputStream();
PrintWriter writer = new PrintWriter(new OutputStreamWriter(out, "UTF-8"))) {
// 文件部分
writer.append("--" + boundary).append("\r\n");
writer.append("Content-Disposition: form-data; name=\"file\"; filename=\"test.txt\"\r\n");
writer.append("Content-Type: text/plain\r\n\r\n");
writer.flush();
Files.copy(Paths.get("test.txt"), out);
out.flush();
// 结束标记
writer.append("\r\n--" + boundary + "--\r\n");
}
服务端实现(Servlet示例)
@WebServlet("/upload")
@MultipartConfig
public class UploadServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) {
Part filePart = request.getPart("file");
String fileName = Paths.get(filePart.getSubmittedFileName()).getFileName().toString();
try (InputStream fileContent = filePart.getInputStream()) {
Files.copy(fileContent, Paths.get("/uploads/" + fileName));
}
}
}
关键注意事项
- 必须设置编码类型:
enctype="multipart/form-data"
- 服务器配置:Servlet 3.0+需要
@MultipartConfig
注解 - 文件大小限制:通过
@MultipartConfig(maxFileSize)
设置 - 安全考虑:
- 验证文件类型(不要仅依赖Content-Type)
- 限制上传目录权限
- 对文件名进行消毒处理
高级实现方案
- 断点续传:通过Content-Range头实现
- 分块上传:将大文件分块传输
- 进度监控:可通过监听InputStream的读取进度实现
常见问题解决
- 中文文件名乱码:确保请求和响应都使用UTF-8编码
- 大文件内存溢出:使用流式处理而非全量读取
- 跨域问题:配置CORS头部
Access-Control-Allow-Origin
文件下载实现
概念定义
文件下载是指通过网络从服务器获取文件并保存到本地存储的过程。在Java中,通常使用URL
和HttpURLConnection
类实现HTTP协议的文件下载功能。
使用场景
- 从网络资源下载图片、文档、压缩包等文件
- 实现断点续传功能
- 批量下载多个文件
- 需要进度显示的文件下载
核心实现步骤
- 创建URL对象指向目标文件
- 打开HTTP连接
- 获取输入流读取数据
- 创建输出流写入本地文件
- 关闭连接和流
示例代码
public static void downloadFile(String fileUrl, String savePath) throws IOException {
URL url = new URL(fileUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
try (InputStream in = connection.getInputStream();
FileOutputStream out = new FileOutputStream(savePath)) {
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
} finally {
connection.disconnect();
}
}
进阶功能实现
进度显示
int totalSize = connection.getContentLength();
int downloadedSize = 0;
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
downloadedSize += bytesRead;
System.out.printf("下载进度: %.2f%%%n",
(downloadedSize * 100.0 / totalSize));
}
断点续传
File file = new File(savePath);
long existingSize = file.exists() ? file.length() : 0;
connection.setRequestProperty("Range", "bytes=" + existingSize + "-");
try (RandomAccessFile raf = new RandomAccessFile(file, "rw")) {
raf.seek(existingSize);
// ...读取并写入数据
}
注意事项
- 需要处理各种HTTP响应码(如200、206、404等)
- 大文件下载应使用缓冲区避免内存溢出
- 需要正确处理连接超时和读取超时
- 注意文件路径的跨平台兼容性
- 考虑网络中断等异常情况的处理
- 下载完成后应验证文件完整性(如MD5校验)
性能优化建议
- 使用多线程分块下载大文件
- 合理设置缓冲区大小(通常4KB-8KB)
- 对频繁下载的资源实现本地缓存
- 考虑使用连接池管理HTTP连接
处理压缩响应(gzip/deflate)
概念定义
压缩响应是服务器通过 gzip
或 deflate
算法压缩 HTTP 响应体,以减少传输数据量。客户端需支持解压才能正确处理响应内容。
使用场景
- 传输大文本(如 JSON、HTML)时节省带宽。
- 移动端网络环境较差时优化性能。
- CDN 或 Web 服务器默认启用的优化手段。
核心实现步骤
-
请求头声明支持压缩:
connection.setRequestProperty("Accept-Encoding", "gzip, deflate");
-
检查响应是否压缩:
String encoding = connection.getContentEncoding(); boolean isCompressed = encoding != null && (encoding.equalsIgnoreCase("gzip") || encoding.equalsIgnoreCase("deflate"));
-
解压响应流:
InputStream inputStream = isCompressed ? new GZIPInputStream(connection.getInputStream()) : connection.getInputStream();
完整示例代码
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestProperty("Accept-Encoding", "gzip, deflate");
try (InputStream rawStream = connection.getInputStream();
InputStream finalStream = "gzip".equalsIgnoreCase(connection.getContentEncoding())
? new GZIPInputStream(rawStream)
: rawStream;
BufferedReader reader = new BufferedReader(new InputStreamReader(finalStream))) {
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
System.out.println(response.toString());
}
注意事项
- 资源释放:确保关闭
GZIPInputStream
和底层连接(Java 7+ try-with-resources 最佳)。 - 性能考量:压缩对小文件可能得不偿失,但现代网络库通常自动处理优化。
- 错误处理:捕获
ZipException
处理损坏的压缩数据。 - 内容编码:部分服务器可能返回
x-gzip
等变体,需兼容处理。
常见误区
- 忽略检查
Content-Encoding
直接解压,导致非压缩数据解析失败。 - 未正确处理分块传输编码(chunked transfer encoding)与压缩的叠加情况。
缓存控制机制
概念定义
缓存控制机制是 HTTP 协议中用于管理客户端和服务器之间缓存行为的机制。它通过 HTTP 头字段(如 Cache-Control
、Expires
、ETag
等)来控制资源的缓存策略,以提高性能并减少不必要的网络请求。
使用场景
- 静态资源缓存:如图片、CSS、JS 文件等不经常变化的资源。
- 动态内容缓存:如 API 响应,通过设置合适的缓存策略减少服务器负载。
- 离线访问:通过缓存实现部分功能的离线使用。
常见 HTTP 缓存头字段
-
Cache-Control
:最常用的缓存控制头,支持以下指令:max-age=3600
:资源有效期为 3600 秒。no-cache
:每次请求需向服务器验证缓存是否有效。no-store
:禁止缓存。public
/private
:指定资源是否可被代理服务器缓存。
-
Expires
:指定资源的过期时间(HTTP/1.0 兼容字段)。Expires: Wed, 21 Oct 2025 07:28:00 GMT
-
ETag
/Last-Modified
:用于缓存验证:ETag
是资源的唯一标识符(哈希值)。Last-Modified
记录资源最后修改时间。
缓存验证流程
- 首次请求:服务器返回资源及
Cache-Control: max-age=3600
。 - 再次请求:
- 若未过期(
max-age
内),直接使用缓存。 - 若过期,发送请求头
If-None-Match
(ETag)或If-Modified-Since
(Last-Modified)到服务器验证。 - 服务器返回
304 Not Modified
(缓存有效)或200 OK
(新资源)。
- 若未过期(
Java 示例:设置缓存控制
URL url = new URL("https://example.com/resource");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
// 设置缓存控制头
connection.setRequestProperty("Cache-Control", "max-age=3600");
// 发送请求
connection.connect();
注意事项
- 动态内容慎用缓存:如用户数据需实时性,应设置
no-cache
或短max-age
。 - 版本控制:静态资源建议通过文件名哈希(如
app.a1b2c3.js
)实现长期缓存。 - 移动端适配:注意移动网络下缓存策略可能不同。
分块传输编码(Chunked Transfer Encoding)
概念定义
分块传输编码是HTTP协议中的一种数据传输机制,允许服务器将响应体分成多个"块"(chunks)逐个发送,而不需要预先知道总数据大小。每个块包含一个十六进制表示的块大小和实际数据,最后以一个大小为0的块结束。
使用场景
- 动态生成内容:当服务器无法预先确定响应大小时(如实时生成的数据)
- 大文件传输:避免内存中缓冲整个响应
- 持久连接:在HTTP/1.1中保持连接活跃
核心格式
[chunk size]\r\n
[chunk data]\r\n
[chunk size]\r\n
[chunk data]\r\n
...
0\r\n\r\n
Java处理示例
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
try (InputStream in = connection.getInputStream()) {
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
String line;
while ((line = reader.readLine()) != null) {
// 第一行是块大小
int chunkSize = Integer.parseInt(line.trim(), 16);
if (chunkSize == 0) break;
// 读取块数据
char[] chunk = new char[chunkSize];
reader.read(chunk, 0, chunkSize);
System.out.print(new String(chunk));
// 跳过CRLF
reader.readLine();
}
}
注意事项
- 必须正确处理块大小行和CRLF分隔符
- 最后一个块是大小为0的块(
0\r\n\r\n
) - 可能包含尾部头字段(Trailer headers)
- 不要手动设置Content-Length头
常见误区
- 错误地计算块大小(十六进制值)
- 忽略块结束标记(可能导致连接挂起)
- 未正确处理非最后一块的大小为0的情况
- 假设所有服务器都正确实现分块编码
HTTP 认证基础
HTTP 认证是客户端与服务器之间进行身份验证的一种机制,主要用于保护 Web 资源。常见的认证方式包括:
- Basic 认证:用户名和密码以 Base64 编码传输
- Digest 认证:比 Basic 更安全,使用哈希算法
- Bearer 认证:常用于 OAuth 2.0,使用令牌
Java 中实现 HTTP 认证
使用 HttpURLConnection 设置 Basic 认证
import java.net.*;
import java.io.*;
import java.util.Base64;
public class BasicAuthExample {
public static void main(String[] args) throws Exception {
URL url = new URL("https://example.com/protected");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
String username = "user";
String password = "pass";
String authString = username + ":" + password;
String encodedAuth = Base64.getEncoder().encodeToString(authString.getBytes());
connection.setRequestProperty("Authorization", "Basic " + encodedAuth);
// 发送请求并处理响应
int responseCode = connection.getResponseCode();
System.out.println("Response Code: " + responseCode);
}
}
使用 Authenticator 类
Java 提供了更全局的认证解决方案:
import java.net.*;
import java.io.*;
public class GlobalAuthExample {
public static void main(String[] args) {
Authenticator.setDefault(new Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication("user", "pass".toCharArray());
}
});
try {
URL url = new URL("https://example.com/protected");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
// 自动处理认证
} catch (Exception e) {
e.printStackTrace();
}
}
}
注意事项
-
安全性:
- Basic 认证的凭证是 Base64 编码而非加密,必须配合 HTTPS 使用
- 生产环境建议使用更安全的认证方式如 OAuth
-
性能考虑:
- 频繁认证会增加请求开销
- 考虑使用会话机制减少认证次数
-
错误处理:
- 处理 401 (Unauthorized) 和 403 (Forbidden) 响应
- 实现重试逻辑时避免无限循环
-
凭证存储:
- 不要硬编码凭证在代码中
- 使用安全的方式存储和管理凭证
高级用法
处理 WWW-Authenticate 头
当服务器返回 401 时,通常会包含 WWW-Authenticate 头,指示支持的认证方式:
String authHeader = connection.getHeaderField("WWW-Authenticate");
if (authHeader != null && authHeader.startsWith("Basic")) {
// 实现 Basic 认证逻辑
}
自定义认证方案
可以继承 Authenticator 类实现更复杂的认证逻辑:
class CustomAuthenticator extends Authenticator {
protected PasswordAuthentication getPasswordAuthentication() {
// 根据请求属性决定使用哪个凭证
if (getRequestingHost().equals("api.example.com")) {
return new PasswordAuthentication("apiUser", "apiPass".toCharArray());
}
return null;
}
}
四、异常处理与调试
常见异常类型
MalformedURLException
定义:当尝试解析格式错误的URL时抛出,属于java.net
包中的受检异常(Checked Exception)。
触发场景:
- URL缺少协议(如
http://
或https://
) - 包含非法字符(如空格)
- 端口号超出范围(如
999999
)
示例代码:
try {
URL url = new URL("htp://example.com"); // 协议拼写错误
} catch (MalformedURLException e) {
System.err.println("URL格式错误: " + e.getMessage());
}
注意事项:
- 建议在构造URL前使用正则表达式预校验格式
- 避免硬编码URL,优先使用外部配置
IOException
定义:在HTTP通信过程中发生的通用I/O异常,如连接超时、读取中断等。
常见子类:
SocketTimeoutException
(连接/读取超时)UnknownHostException
(域名无法解析)
示例代码:
try (HttpURLConnection conn = (HttpURLConnection) new URL("http://example.com").openConnection()) {
conn.getResponseCode(); // 可能因网络问题失败
} catch (IOException e) {
System.err.println("网络通信异常: " + e.getClass().getSimpleName());
}
处理建议:
- 对连接和读取分别设置超时(
setConnectTimeout()
/setReadTimeout()
) - 使用重试机制应对临时性网络故障
- 资源释放必须放在
finally
块或try-with-resources中
关键区别
异常类型 | 触发阶段 | 典型原因 |
---|---|---|
MalformedURLException | URL构造时 | 语法错误 |
IOException | 网络通信过程中 | 物理层或协议层故障 |
HTTP 错误响应处理
概念定义
HTTP 错误响应是指服务器在处理请求时返回的状态码为 4xx(客户端错误)或 5xx(服务器错误)的响应。常见错误如 404(未找到)、500(服务器内部错误)等。
常见错误状态码
- 4xx 客户端错误
- 400 Bad Request:请求语法错误
- 401 Unauthorized:未授权
- 403 Forbidden:禁止访问
- 404 Not Found:资源不存在
- 5xx 服务器错误
- 500 Internal Server Error:服务器内部错误
- 503 Service Unavailable:服务不可用
处理方式
通过 HttpURLConnection
的 getResponseCode()
获取状态码,针对不同错误进行相应处理。
示例代码
try {
URL url = new URL("http://example.com/api");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
int responseCode = conn.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
// 处理成功响应
} else {
// 处理错误响应
switch (responseCode) {
case 404:
System.out.println("资源未找到");
break;
case 500:
System.out.println("服务器内部错误");
break;
default:
System.out.println("HTTP错误码: " + responseCode);
}
}
} catch (IOException e) {
e.printStackTrace();
}
注意事项
- 区分成功和错误响应:
getInputStream()
仅适用于 2xx 响应,错误时需使用getErrorStream()
读取错误信息。 - 资源释放:无论成功或失败,都应关闭连接和流。
- 重试机制:对 5xx 错误可考虑实现重试逻辑(需谨慎避免无限重试)。
- 用户友好提示:根据错误类型向用户提供清晰的反馈信息。
错误流读取示例
if (responseCode >= 400) {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(conn.getErrorStream()))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line); // 输出错误详情
}
}
}
连接超时处理
概念定义
连接超时(Connection Timeout)是指在建立网络连接时,客户端等待服务器响应的最大时间。如果在指定时间内未能建立连接,系统将抛出超时异常。
使用场景
- 当网络状况不佳或服务器不可达时
- 需要控制请求等待时间的应用场景
- 防止程序因网络问题长时间阻塞
常见设置方法
在HttpURLConnection中可以通过以下方式设置:
URL url = new URL("http://example.com");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
// 设置连接超时为5秒
connection.setConnectTimeout(5000);
// 设置读取超时为10秒
connection.setReadTimeout(10000);
注意事项
- 超时时间不宜过短(可能导致正常网络波动下的失败)
- 超时时间不宜过长(影响用户体验)
- 需要合理捕获和处理SocketTimeoutException
try {
// 连接操作
} catch (SocketTimeoutException e) {
// 处理超时情况
System.out.println("连接超时,请检查网络或稍后重试");
}
最佳实践
- 根据实际网络环境调整超时阈值
- 考虑实现重试机制
- 对不同的操作(连接/读取)设置不同的超时时间
- 在UI应用中提供超时反馈给用户
典型异常
- java.net.SocketTimeoutException:连接或读取超时时抛出
- java.net.ConnectException:连接被拒绝时抛出(服务器未响应)
读取超时处理
概念定义
读取超时(Read Timeout)是指在网络通信中,客户端等待服务器响应数据的最大时间限制。如果在设定的超时时间内没有收到任何数据,系统会抛出 SocketTimeoutException
异常。
使用场景
- 防止因服务器响应缓慢或无响应导致客户端长时间阻塞
- 在需要快速响应的应用中保证用户体验
- 处理不稳定的网络连接情况
设置方法(HttpURLConnection)
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
// 设置读取超时为5秒
connection.setReadTimeout(5000);
注意事项
- 超时时间单位是毫秒
- 值为0表示无限等待(不推荐)
- 合理的超时时间应根据实际网络环境和应用需求设置
- 需要捕获
SocketTimeoutException
进行处理
异常处理示例
try {
// 网络操作代码
} catch (SocketTimeoutException e) {
// 超时处理逻辑
System.out.println("读取超时,请检查网络或稍后重试");
} catch (IOException e) {
// 其他IO异常处理
}
最佳实践
- 对于移动应用,建议设置3-5秒的超时
- 对于后台服务,可根据业务需求适当延长
- 重要操作应实现重试机制
- 给用户提供友好的超时提示
网络不可用检测
概念定义
网络不可用检测是指在Java网络编程中,判断当前设备是否无法连接到互联网或目标服务器的过程。这是网络请求前的必要步骤,可避免因网络问题导致的程序异常。
常见检测方法
1. 使用InetAddress
try {
InetAddress address = InetAddress.getByName("www.baidu.com");
boolean reachable = address.isReachable(3000); // 3秒超时
System.out.println("网络状态: " + (reachable ? "可用" : "不可用"));
} catch (IOException e) {
System.out.println("网络不可用");
}
2. 使用HttpURLConnection
try {
URL url = new URL("http://www.baidu.com");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(3000);
conn.connect();
System.out.println("网络状态: 可用");
conn.disconnect();
} catch (Exception e) {
System.out.println("网络不可用");
}
注意事项
- 超时设置:必须设置合理的连接超时(建议3-5秒)
- DNS缓存:
InetAddress
可能有DNS缓存问题 - 代理影响:系统代理设置可能影响检测结果
- 移动网络:移动设备需考虑飞行模式等特殊状态
最佳实践
- 检测地址建议使用知名稳定域名(如baidu.com)
- 重要操作前应进行双重检测(如WiFi连接状态+实际网络请求测试)
- 在Android中需添加网络权限:
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
URL 与 HttpURLConnection:日志记录请求与响应
为什么需要记录请求与响应?
- 调试:帮助开发者快速定位网络请求问题
- 审计:记录系统间的交互行为
- 性能分析:统计请求耗时和响应大小
- 安全监控:检测异常请求模式
核心日志记录点
请求日志
// 记录请求方法
System.out.println("Request Method: " + connection.getRequestMethod());
// 记录请求头
Map<String, List<String>> requestHeaders = connection.getRequestProperties();
requestHeaders.forEach((k, v) -> System.out.println("Header: " + k + "=" + v));
// 记录请求体(POST/PUT时)
if ("POST".equals(connection.getRequestMethod())) {
// 需要先缓存请求体数据才能重复读取
}
响应日志
// 记录响应状态码
System.out.println("Response Code: " + connection.getResponseCode());
// 记录响应头
Map<String, List<String>> headers = connection.getHeaderFields();
headers.forEach((k, v) -> System.out.println("Header: " + k + "=" + v));
// 记录响应体
try (BufferedReader in = new BufferedReader(
new InputStreamReader(connection.getInputStream()))) {
String inputLine;
StringBuilder content = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
content.append(inputLine);
}
System.out.println("Response Body: " + content.toString());
}
注意事项
- 性能影响:高频请求场景应考虑异步日志
- 敏感信息:避免直接记录认证凭证等敏感数据
- 大响应体:限制日志记录的响应体大小
- 异常处理:确保网络异常时仍能记录已有信息
高级技巧
// 使用MDC实现请求链路追踪(需日志框架支持)
MDC.put("requestId", UUID.randomUUID().toString());
// 使用try-with-resources确保资源释放
try (HttpURLConnection conn = (HttpURLConnection) url.openConnection()) {
// 请求和日志记录代码
}
日志框架集成示例(SLF4J)
Logger logger = LoggerFactory.getLogger(MyHttpClient.class);
logger.debug("Request to {} with method {}", url, method);
logger.info("Received response: {}", responseCode);
调试技巧与工具
1. 调试的基本概念
调试是指通过工具和技术手段,发现并修复程序中的错误(Bug)。在 Java 中,调试通常涉及以下步骤:
- 设置断点:在代码的特定位置暂停程序执行。
- 单步执行:逐行或逐方法执行代码,观察变量变化。
- 查看变量值:检查程序运行时的数据状态。
- 分析调用栈:追踪方法调用的路径。
2. 常用调试工具
2.1 IDE 内置调试器
- Eclipse/IntelliJ IDEA:支持断点、条件断点、表达式求值等功能。
- Visual Studio Code:通过插件(如 Java Debugger)提供调试支持。
2.2 日志工具
- Log4j/SLF4J:通过日志级别(DEBUG、INFO等)输出运行时信息。
- System.out.println:简单但有效的临时调试手段。
2.3 命令行工具
- jdb:Java 自带的命令行调试工具。
- jstack/jmap:用于分析线程和内存问题。
3. 调试技巧
3.1 条件断点
在断点上设置条件,仅在满足条件时暂停程序。例如:
for (int i = 0; i < 100; i++) {
// 设置条件断点:i == 50
System.out.println(i);
}
3.2 异常断点
捕获特定异常时自动暂停,便于快速定位问题。
3.3 远程调试
通过 JVM 参数 -agentlib:jdwp
启动远程调试会话:
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 MyApp
3.4 日志调试
使用日志框架记录关键信息:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MyClass {
private static final Logger logger = LoggerFactory.getLogger(MyClass.class);
public void myMethod() {
logger.debug("Debug message: {}", someVariable);
}
}
4. 常见误区
- 过度依赖打印语句:可能导致代码混乱,建议使用日志框架。
- 忽略异常堆栈:完整的异常信息是调试的关键线索。
- 未复现问题:尽量在本地或测试环境复现问题后再调试。
5. 高级工具
- Arthas:阿里巴巴开源的 Java 诊断工具,支持动态跟踪方法调用。
- JProfiler/YourKit:性能分析工具,可检测内存泄漏和性能瓶颈。
性能优化建议
1. 使用连接池
- 概念:避免频繁创建和销毁
HttpURLConnection
对象,使用连接池(如 Apache HttpClient 或 OkHttp)复用连接。 - 场景:适用于高并发请求场景,减少 TCP 握手开销。
- 示例:
// 使用 Apache HttpClient 连接池 PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(); cm.setMaxTotal(100); // 最大连接数 CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm).build();
2. 启用缓存
- 概念:通过
URLConnection.setUseCaches(true)
启用响应缓存,减少重复请求。 - 场景:适用于静态资源或数据变化不频繁的请求。
- 注意:需确保服务器支持缓存头(如
Cache-Control
)。
3. 设置超时时间
- 概念:通过
setConnectTimeout()
和setReadTimeout()
避免长时间阻塞。 - 推荐值:
- 连接超时:2-5 秒
- 读取超时:10-30 秒
- 示例:
connection.setConnectTimeout(5000); connection.setReadTimeout(10000);
4. 使用 GZIP 压缩
- 概念:通过
Accept-Encoding: gzip
请求头压缩响应数据,减少传输量。 - 实现:
connection.setRequestProperty("Accept-Encoding", "gzip");
5. 批量处理请求
- 概念:合并多个小请求为一个批量请求(如通过 HTTP/2 多路复用)。
- 场景:适用于高频次、小数据量的 API 调用。
6. 关闭资源
- 关键操作:
- 及时关闭
InputStream
/OutputStream
。 - 调用
disconnect()
释放连接(在连接池中可能无效)。
- 及时关闭
- 示例:
try (InputStream is = connection.getInputStream()) { // 处理输入流 } finally { connection.disconnect(); }
7. 选择高效协议
- 建议:
- 优先使用 HTTP/2(减少连接数,支持头部压缩)。
- 对敏感数据启用 HTTPS 时,选择 TLS 1.3。
8. 减少重定向
- 优化点:
- 直接访问最终 URL,避免 301/302 跳转。
- 通过
connection.setInstanceFollowRedirects(false)
手动处理重定向逻辑。
9. 合理使用 Keep-Alive
- 概念:通过
Connection: keep-alive
复用 TCP 连接。 - 注意:默认已启用,需确保服务器支持。
10. 异步请求
- 场景:高延迟请求使用异步处理(如
CompletableFuture
或回调)。 - 示例:
CompletableFuture.supplyAsync(() -> { URL url = new URL("https://example.com"); return url.openStream(); });
线程安全注意事项
概念定义
线程安全指在多线程环境下,程序或组件能够正确、一致地处理共享数据,避免出现数据竞争、死锁等问题。在Java中,线程安全通常通过同步机制(如synchronized
、Lock
)或使用线程安全类(如ConcurrentHashMap
)实现。
使用场景
- 共享资源访问:当多个线程需要读写同一变量或对象时(如计数器、缓存等)。
- 单例模式:确保懒汉式单例在多线程下只创建一次实例。
- 集合操作:如多线程环境下操作
ArrayList
需替换为CopyOnWriteArrayList
。
常见误区与注意事项
-
过度同步:
- 滥用
synchronized
可能导致性能下降。 - 仅在必要时同步关键代码块(如共享数据修改部分)。
- 滥用
-
隐藏的线程不安全:
- 如
SimpleDateFormat
非线程安全,需用ThreadLocal
或替换为DateTimeFormatter
。 - 示例:
// 错误用法(多线程下可能抛出异常) SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); // 正确用法 ThreadLocal<SimpleDateFormat> threadSafeSdf = ThreadLocal.withInitial( () -> new SimpleDateFormat("yyyy-MM-dd") );
- 如
-
原子性误解:
- 即使单行代码(如
i++
)也非原子操作,需用AtomicInteger
或同步。 - 示例:
// 非线程安全 private int count = 0; public void increment() { count++; } // 线程安全方案1:AtomicInteger private AtomicInteger count = new AtomicInteger(0); public void increment() { count.incrementAndGet(); } // 线程安全方案2:synchronized public synchronized void increment() { count++; }
- 即使单行代码(如
-
死锁风险:
- 避免嵌套锁(如线程A持有锁1请求锁2,线程B持有锁2请求锁1)。
- 解决方案:按固定顺序获取锁,或使用
tryLock
超时机制。
-
可见性问题:
- 多线程修改共享变量时,需用
volatile
或同步确保修改对其他线程可见。 - 示例:
// 错误:可能读不到最新值 private boolean flag = false; // 正确:保证可见性 private volatile boolean flag = false;
- 多线程修改共享变量时,需用
线程安全工具推荐
- 并发集合:
ConcurrentHashMap
、CopyOnWriteArrayList
。 - 原子类:
AtomicInteger
、AtomicReference
。 - 锁工具:
ReentrantLock
、StampedLock
(比synchronized
更灵活)。
资源释放最佳实践
什么是资源释放
资源释放是指在使用完系统资源(如文件流、数据库连接、网络连接等)后,及时将其归还给系统,以避免资源泄漏和性能问题。
为什么需要资源释放
- 防止内存泄漏:未释放的资源会持续占用内存
- 避免文件锁定:未关闭的文件流可能导致文件被锁定
- 连接池管理:数据库连接不释放会影响连接池可用性
- 系统稳定性:资源耗尽可能导致应用崩溃
最佳实践方法
1. 使用try-with-resources(Java 7+)
try (FileInputStream fis = new FileInputStream("file.txt");
BufferedReader br = new BufferedReader(new InputStreamReader(fis))) {
// 使用资源
} // 自动关闭
2. 传统try-catch-finally方式
Connection conn = null;
try {
conn = DriverManager.getConnection(url);
// 使用连接
} finally {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
// 记录日志
}
}
}
3. 多层资源释放顺序
遵循"后开先关"原则:
try (Socket socket = new Socket();
OutputStream os = socket.getOutputStream();
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(os))) {
// 使用资源
} // 自动按相反顺序关闭
常见注意事项
- 关闭前检查null:避免NPE
- 处理关闭异常:关闭操作本身可能抛出异常
- 不要重复关闭:已关闭的资源再次关闭可能报错
- 使用工具类:如IOUtils.closeQuietly()(Apache Commons)
- 注意包装流:关闭外层流会自动关闭内层流
特殊资源处理
对于非AutoCloseable资源:
Graphics g = image.getGraphics();
try {
// 使用Graphics
} finally {
g.dispose(); // 不是close()
}
测试验证
使用内存分析工具(如VisualVM)或资源监控来确认资源是否被正确释放。
五、实际应用场景
REST API 调用概述
REST (Representational State Transfer) 是一种基于 HTTP 协议的软件架构风格,用于构建分布式系统。REST API 调用是指通过 HTTP 方法(如 GET、POST、PUT、DELETE 等)与 RESTful 服务进行交互的过程。
核心概念
HTTP 方法
- GET:获取资源
- POST:创建资源
- PUT:更新资源
- DELETE:删除资源
- PATCH:部分更新资源
资源标识
- 使用 URI (Uniform Resource Identifier) 唯一标识资源
- 例如:
https://api.example.com/users/123
数据格式
- 常用 JSON 或 XML 格式传输数据
- 通过
Content-Type
头指定(如application/json
)
Java 实现方式
1. 使用 HttpURLConnection
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
public class RestApiExample {
public static void main(String[] args) throws IOException {
// GET 请求示例
String response = sendGetRequest("https://jsonplaceholder.typicode.com/posts/1");
System.out.println("GET Response: " + response);
// POST 请求示例
String postData = "{\"title\":\"foo\",\"body\":\"bar\",\"userId\":1}";
String postResponse = sendPostRequest("https://jsonplaceholder.typicode.com/posts", postData);
System.out.println("POST Response: " + postResponse);
}
public static String sendGetRequest(String url) throws IOException {
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
connection.setRequestMethod("GET");
int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
try (BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
StringBuilder response = new StringBuilder();
String line;
while ((line = in.readLine()) != null) {
response.append(line);
}
return response.toString();
}
}
return "GET request failed with response code: " + responseCode;
}
public static String sendPostRequest(String url, String data) throws IOException {
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/json");
connection.setDoOutput(true);
try (OutputStream os = connection.getOutputStream()) {
byte[] input = data.getBytes("utf-8");
os.write(input, 0, input.length);
}
int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_CREATED) {
try (BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
StringBuilder response = new StringBuilder();
String line;
while ((line = in.readLine()) != null) {
response.append(line);
}
return response.toString();
}
}
return "POST request failed with response code: " + responseCode;
}
}
2. 使用 HttpClient (Java 11+)
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class HttpClientExample {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
// GET 请求
HttpRequest getRequest = HttpRequest.newBuilder()
.uri(URI.create("https://jsonplaceholder.typicode.com/posts/1"))
.build();
HttpResponse<String> getResponse = client.send(
getRequest, HttpResponse.BodyHandlers.ofString());
System.out.println("GET Response: " + getResponse.body());
// POST 请求
String postData = "{\"title\":\"foo\",\"body\":\"bar\",\"userId\":1}";
HttpRequest postRequest = HttpRequest.newBuilder()
.uri(URI.create("https://jsonplaceholder.typicode.com/posts"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(postData))
.build();
HttpResponse<String> postResponse = client.send(
postRequest, HttpResponse.BodyHandlers.ofString());
System.out.println("POST Response: " + postResponse.body());
}
}
常见客户端库
1. Apache HttpClient
import org.apache.http.client.methods.*;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
public class ApacheHttpClientExample {
public static void main(String[] args) throws Exception {
try (CloseableHttpClient client = HttpClients.createDefault()) {
// GET 请求
HttpGet getRequest = new HttpGet("https://jsonplaceholder.typicode.com/posts/1");
CloseableHttpResponse getResponse = client.execute(getRequest);
System.out.println("GET Response: " + EntityUtils.toString(getResponse.getEntity()));
// POST 请求
HttpPost postRequest = new HttpPost("https://jsonplaceholder.typicode.com/posts");
postRequest.setHeader("Content-Type", "application/json");
postRequest.setEntity(new StringEntity("{\"title\":\"foo\",\"body\":\"bar\",\"userId\":1}"));
CloseableHttpResponse postResponse = client.execute(postRequest);
System.out.println("POST Response: " + EntityUtils.toString(postResponse.getEntity()));
}
}
}
2. Spring RestTemplate
import org.springframework.web.client.RestTemplate;
public class RestTemplateExample {
public static void main(String[] args) {
RestTemplate restTemplate = new RestTemplate();
// GET 请求
String getResponse = restTemplate.getForObject(
"https://jsonplaceholder.typicode.com/posts/1", String.class);
System.out.println("GET Response: " + getResponse);
// POST 请求
String postData = "{\"title\":\"foo\",\"body\":\"bar\",\"userId\":1}";
String postResponse = restTemplate.postForObject(
"https://jsonplaceholder.typicode.com/posts", postData, String.class);
System.out.println("POST Response: " + postResponse);
}
}
最佳实践
- 错误处理:始终检查 HTTP 响应状态码
- 超时设置:配置合理的连接和读取超时
- 重试机制:对临时性错误实现自动重试
- 连接池:复用 HTTP 连接提高性能
- 认证:正确处理基本认证、OAuth 等
- 日志记录:记录请求和响应以便调试
- 序列化:使用 JSON 库(如 Jackson、Gson)处理复杂对象
常见问题
- 忘记设置 Content-Type:导致服务器无法解析请求体
- 忽略响应状态码:只处理成功情况
- 资源泄漏:未关闭连接或流
- 线程安全:某些客户端不是线程安全的
- 编码问题:未指定正确的字符编码
URL 与 HttpURLConnection:网页内容抓取
概念定义
网页内容抓取是指通过编程方式从指定的 URL 地址获取网页的 HTML 内容或其他资源。在 Java 中,通常使用 HttpURLConnection
类来实现这一功能,它是 Java 标准库中用于发送 HTTP 请求和接收响应的核心类。
使用场景
- 数据采集:从网页中提取结构化数据(如新闻、商品信息等)。
- 爬虫开发:构建自动化抓取工具,用于搜索引擎或数据分析。
- API 调用:与 RESTful 服务交互,获取 JSON 或 XML 数据。
- 网页监控:定期检查网页内容是否更新。
核心步骤
- 创建
URL
对象,指定目标地址。 - 通过
openConnection()
获取HttpURLConnection
实例。 - 设置请求方法(如
GET
/POST
)和请求头(如User-Agent
)。 - 获取输入流并读取响应内容。
- 关闭连接和流。
示例代码
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class WebPageFetcher {
public static String fetchContent(String urlString) throws Exception {
URL url = new URL(urlString);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
// 设置请求方法和超时时间
connection.setRequestMethod("GET");
connection.setConnectTimeout(5000);
connection.setReadTimeout(5000);
// 添加请求头(模拟浏览器访问)
connection.setRequestProperty("User-Agent", "Mozilla/5.0");
// 读取响应内容
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(connection.getInputStream()))) {
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
return response.toString();
} finally {
connection.disconnect();
}
}
public static void main(String[] args) {
try {
String content = fetchContent("https://example.com");
System.out.println(content);
} catch (Exception e) {
e.printStackTrace();
}
}
}
注意事项
- 异常处理:必须捕获
IOException
,处理网络超时或连接失败。 - 资源释放:确保关闭
InputStream
和HttpURLConnection
(可用try-with-resources
)。 - 请求头设置:
- 添加
User-Agent
避免被服务器拒绝。 - 需要 Cookie 时设置
Cookie
请求头。
- 添加
- 重定向:默认自动跟随重定向,可通过
setInstanceFollowRedirects(false)
禁用。 - 性能优化:复用
HttpURLConnection
(需手动调用disconnect()
)。
常见问题
- 403 禁止访问:未设置
User-Agent
或目标网站反爬。 - 乱码问题:需正确指定响应编码(如
"UTF-8"
)。 - HTTPS 支持:直接使用
https://
协议即可,无需额外配置。
高级用法
- POST 请求:设置
setDoOutput(true)
并写入请求体。 - 代理设置:通过
Proxy
类配置代理服务器。 - 连接池:使用第三方库(如 Apache HttpClient)提升性能。
数据提交表单处理
概念定义
数据提交表单处理是指通过 HTTP 协议将用户输入的表单数据发送到服务器,并由服务器接收、解析和处理的过程。在 Java 中,通常使用 HttpURLConnection
或第三方库(如 Apache HttpClient)来实现表单数据的提交。
使用场景
- 用户登录/注册
- 文件上传
- 搜索查询
- 问卷调查
- 任何需要将用户输入数据发送到服务器的场景
常见数据提交方式
1. GET 方式
- 数据通过 URL 的查询字符串传递
- 适合少量、非敏感数据
- 示例代码:
URL url = new URL("http://example.com/login?username=test&password=123");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
2. POST 方式
- 数据通过请求体传递
- 适合大量或敏感数据
- 示例代码:
URL url = new URL("http://example.com/login");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setDoOutput(true);
String postData = "username=test&password=123";
try(OutputStream os = conn.getOutputStream()) {
os.write(postData.getBytes());
}
表单编码格式
1. application/x-www-form-urlencoded
- 默认表单编码
- 格式:
key1=value1&key2=value2
- URL 编码特殊字符
2. multipart/form-data
- 用于文件上传
- 包含多个数据部分
- 需要设置边界分隔符
注意事项
- 敏感数据必须使用 HTTPS
- POST 方法比 GET 更安全(数据不在 URL 中显示)
- 对用户输入进行验证和转义,防止 SQL 注入
- 设置合适的 Content-Type 头
- 处理可能的重定向
完整示例(POST 表单)
import java.io.*;
import java.net.*;
public class FormSubmitter {
public static void main(String[] args) throws Exception {
URL url = new URL("http://example.com/api/submit");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
// 设置请求属性
conn.setRequestMethod("POST");
conn.setDoOutput(true);
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
// 构建表单数据
String formData = "name=John+Doe&email=john%40example.com&message=Hello+World";
// 发送数据
try(OutputStream os = conn.getOutputStream()) {
byte[] input = formData.getBytes("utf-8");
os.write(input, 0, input.length);
}
// 获取响应
try(BufferedReader br = new BufferedReader(
new InputStreamReader(conn.getInputStream(), "utf-8"))) {
StringBuilder response = new StringBuilder();
String responseLine;
while ((responseLine = br.readLine()) != null) {
response.append(responseLine.trim());
}
System.out.println(response.toString());
}
}
}
文件上传示例
// 设置multipart/form-data请求
String boundary = "----WebKitFormBoundary7MA4YWxkTrZu0gW";
conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
// 构建请求体
StringBuilder sb = new StringBuilder();
sb.append("--").append(boundary).append("\r\n");
sb.append("Content-Disposition: form-data; name=\"file\"; filename=\"test.txt\"\r\n");
sb.append("Content-Type: text/plain\r\n\r\n");
// 写入文件内容
try(OutputStream os = conn.getOutputStream();
FileInputStream fis = new FileInputStream("test.txt")) {
os.write(sb.toString().getBytes());
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
os.write(buffer, 0, bytesRead);
}
os.write(("\r\n--" + boundary + "--\r\n").getBytes());
}
URL 与 HttpURLConnection 实现简单 Web 客户端
概念定义
HttpURLConnection
是 Java 标准库中用于发送 HTTP 请求的核心类,基于 URLConnection
实现,支持 GET/POST 等常用方法,适用于简单的 HTTP 客户端开发。
核心步骤
- 创建
URL
对象 - 通过
openConnection()
获取HttpURLConnection
实例 - 设置请求方法(GET/POST)
- 读取响应数据
示例代码(GET 请求)
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class SimpleWebClient {
public static void main(String[] args) throws Exception {
URL url = new URL("https://example.com");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
int responseCode = conn.getResponseCode();
System.out.println("Response Code: " + responseCode);
try (BufferedReader in = new BufferedReader(
new InputStreamReader(conn.getInputStream()))) {
String inputLine;
StringBuilder response = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
System.out.println(response.toString());
}
conn.disconnect();
}
}
关键注意事项
- 资源释放:必须调用
disconnect()
关闭连接 - 异常处理:需捕获
IOException
处理网络异常 - 响应码检查:应先检查
getResponseCode()
再读取响应体 - 超时设置:建议设置连接/读取超时
conn.setConnectTimeout(5000); // 5秒连接超时 conn.setReadTimeout(10000); // 10秒读取超时
POST 请求示例
// 设置POST参数
conn.setRequestMethod("POST");
conn.setDoOutput(true);
try (OutputStream os = conn.getOutputStream()) {
os.write("param1=value1¶m2=value2".getBytes());
}
性能优化建议
- 复用
HttpURLConnection
对象(需手动处理重定向) - 启用响应缓存(Android 默认禁用)
urlConnection.setUseCaches(true);
- 使用
BufferedReader
提升读取效率
常见问题
- HTTPS 支持:需配置 SSL 证书(或跳过验证)
- 重定向处理:默认自动跟随重定向(可通过
setInstanceFollowRedirects(false)
禁用) - 请求头设置:
conn.setRequestProperty("User-Agent", "Mozilla/5.0"); conn.setRequestProperty("Accept-Language", "en-US");
- 数据编码:POST 数据需正确 URL 编码
与其他网络库的比较(HttpClient, OkHttp)
1. HttpURLConnection
- 定义:Java 标准库中的 HTTP 客户端,基于
java.net
包,支持 HTTP/1.1。 - 特点:
- 简单易用,无需额外依赖。
- 功能较为基础,缺乏高级特性(如连接池、缓存)。
- 需要手动处理输入/输出流和状态码。
- 适用场景:
- 简单的 HTTP 请求(如 GET/POST)。
- 对依赖敏感(如 Android 基础库开发)。
2. Apache HttpClient
- 定义:Apache 提供的功能丰富的 HTTP 客户端库(属于
org.apache.http
包)。 - 特点:
- 支持连接池、重试机制、缓存等高级功能。
- 提供同步/异步 API,扩展性强。
- 依赖较大,适合复杂场景。
- 适用场景:
- 需要高性能或复杂 HTTP 交互(如多线程下载)。
- 企业级应用或服务端开发。
3. OkHttp
- 定义:Square 开源的现代 HTTP 客户端(支持 HTTP/2)。
- 特点:
- 轻量高效,默认支持连接池和 GZIP 压缩。
- 简洁的链式 API(如
Request.Builder
)。 - 广泛用于 Android 开发(如 Retrofit 的底层实现)。
- 适用场景:
- 移动端或需要简洁 API 的项目。
- 需要 HTTP/2 或 WebSocket 支持。
4. 关键对比
特性 | HttpURLConnection | HttpClient | OkHttp |
---|---|---|---|
依赖 | JDK 内置 | 需额外引入 | 需额外引入 |
连接池 | 不支持 | 支持 | 支持 |
HTTP/2 | 不支持 | 部分支持 | 支持 |
API 易用性 | 较低(需手动处理流) | 中等 | 高(链式调用) |
适用平台 | 所有 Java 平台 | 服务端为主 | Android/Java |
5. 示例代码对比
HttpURLConnection(GET 请求)
URL url = new URL("https://example.com");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(conn.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
}
OkHttp(GET 请求)
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("https://example.com")
.build();
try (Response response = client.newCall(request).execute()) {
System.out.println(response.body().string());
}
6. 选择建议
- 简单需求:优先使用
HttpURLConnection
(减少依赖)。 - 复杂需求:选择
HttpClient
或OkHttp
(推荐后者)。
URL 与 HttpURLConnection 在 Android 中的使用注意事项
1. 主线程限制
- 禁止在主线程执行网络请求:Android 4.0+ 会直接抛出
NetworkOnMainThreadException
。 - 解决方案:使用
AsyncTask
、Thread
+Handler
或协程(如 Kotlin 的Coroutine
)。
2. 权限声明
- 必须添加网络权限:
<uses-permission android:name="android.permission.INTERNET" />
3. HTTPS 支持
- Android 9 (Pie) 及以上:默认禁用明文流量(HTTP),需在
AndroidManifest.xml
中配置:<application android:usesCleartextTraffic="true">
- 自定义证书:需重写
HostnameVerifier
和SSLSocketFactory
(生产环境不推荐忽略证书验证)。
4. 连接超时设置
- 必须设置超时(默认无超时可能导致 ANR):
connection.setConnectTimeout(15000); // 15秒 connection.setReadTimeout(15000);
5. 资源释放
- 必须关闭连接:在
finally
块中释放资源:try { HttpURLConnection connection = (HttpURLConnection) url.openConnection(); // ...操作代码 } finally { if (connection != null) { connection.disconnect(); } }
6. 响应码处理
- 检查响应码后再读取数据:
int responseCode = connection.getResponseCode(); if (responseCode == HttpURLConnection.HTTP_OK) { // 处理输入流 }
7. 输入输出流
- 输出流:POST 请求需先获取输出流再写入数据:
connection.setDoOutput(true); OutputStream os = connection.getOutputStream(); os.write(data.getBytes()); os.flush();
- 输入流:用
BufferedReader
读取响应:BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
8. 重定向处理
- 默认自动处理 3xx 重定向,可通过以下方法禁用:
connection.setInstanceFollowRedirects(false);
9. 缓存控制
- 避免缓存问题可添加请求头:
connection.setRequestProperty("Cache-Control", "no-cache");
10. 用户代理设置
- 部分服务器会验证 User-Agent:
connection.setRequestProperty("User-Agent", "Mozilla/5.0");
处理 JSON/XML 响应
概念定义
JSON(JavaScript Object Notation)和 XML(eXtensible Markup Language)是两种常用的数据交换格式,用于在客户端和服务器之间传输结构化数据。Java 提供了多种库来处理这两种格式的响应数据。
使用场景
- JSON:轻量级、易读,常用于 Web API 和移动应用。
- XML:结构严谨,支持复杂数据,常用于企业级应用和配置文件。
常见 JSON 处理库
- org.json:简单易用,适合基础需求。
- Gson:Google 提供的库,支持对象与 JSON 的相互转换。
- Jackson:高性能,功能丰富,支持流式处理。
常见 XML 处理库
- DOM:将整个 XML 加载到内存,适合小型文件。
- SAX:事件驱动,适合大型文件。
- JAXB:Java 标准库,支持 XML 与对象的绑定。
JSON 处理示例(使用 Gson)
import com.google.gson.Gson;
public class JsonExample {
public static void main(String[] args) {
String json = "{\"name\":\"John\", \"age\":30}";
Gson gson = new Gson();
Person person = gson.fromJson(json, Person.class);
System.out.println(person.getName()); // 输出: John
}
}
class Person {
private String name;
private int age;
// Getters and Setters
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
}
XML 处理示例(使用 DOM)
import org.w3c.dom.*;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.StringReader;
import org.xml.sax.InputSource;
public class XmlExample {
public static void main(String[] args) throws Exception {
String xml = "<person><name>John</name><age>30</age></person>";
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(new InputSource(new StringReader(xml)));
NodeList nameList = doc.getElementsByTagName("name");
String name = nameList.item(0).getTextContent();
System.out.println(name); // 输出: John
}
}
常见误区或注意事项
-
JSON:
- 字段名区分大小写。
- 确保 JSON 字符串格式正确,否则解析会失败。
- 使用
try-catch
处理解析异常。
-
XML:
- 避免使用 DOM 处理大型 XML 文件,可能导致内存溢出。
- 注意 XML 的命名空间和编码声明。
- 使用
try-catch
处理解析异常。
性能优化建议
- JSON:对于大数据量,使用 Jackson 的流式 API(
JsonParser
)。 - XML:对于大型文件,优先选择 SAX 或 StAX 解析器。
断点续传
概念定义
断点续传是指在上传或下载文件时,能够从上次中断的地方继续传输,而不需要重新开始。它通过记录已传输的字节位置来实现。
核心实现原理
- HTTP Range头:客户端通过
Range: bytes=start-end
请求头指定需要下载的字节范围 - 服务器支持:服务器需要响应
206 Partial Content
状态码和Content-Range
头 - 本地记录:客户端需要保存已下载的字节数和文件临时数据
Java实现关键步骤
- 检查服务器是否支持断点续传(检测
Accept-Ranges
头) - 获取已下载文件大小(本地临时文件)
- 设置
Range
请求头 - 处理服务器响应
- 追加写入文件
示例代码
URL url = new URL(fileUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
// 检查本地已下载部分
File file = new File(localPath);
long existingLength = file.exists() ? file.length() : 0;
if (existingLength > 0) {
conn.setRequestProperty("Range", "bytes=" + existingLength + "-");
}
try (InputStream in = conn.getInputStream();
RandomAccessFile out = new RandomAccessFile(file, "rw")) {
out.seek(existingLength);
byte[] buffer = new byte[1024];
int len;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
}
注意事项
- 服务器必须支持
Range
请求(返回Accept-Ranges: bytes
) - 临时文件需要正确处理,避免损坏
- 网络中断后需要正确处理连接
- 多线程下载时需要协调各线程的下载范围
优化建议
- 使用多线程分段下载
- 添加MD5校验确保文件完整性
- 实现下载进度监控
- 合理设置缓冲区大小(通常8KB-32KB)
多线程下载实现
概念定义
多线程下载是指将一个文件分割成多个部分,使用多个线程同时下载不同部分,最后合并成完整文件的技术。核心思想是利用多线程并行下载提高下载速度。
核心实现步骤
- 获取文件信息:通过HTTP HEAD请求获取文件大小、是否支持断点续传
- 任务划分:根据线程数计算每个线程负责的下载区间
- 创建下载线程:每个线程负责下载指定区间的数据
- 合并文件:所有线程完成后将临时文件合并
- 异常处理:处理网络中断、磁盘空间不足等情况
关键技术点
HttpURLConnection设置范围请求
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setRequestProperty("Range", "bytes=" + startPos + "-" + endPos);
线程任务实现
class DownloadTask implements Runnable {
private long start;
private long end;
private URL url;
private File tempFile;
public void run() {
HttpURLConnection conn = url.openConnection();
conn.setRequestProperty("Range", "bytes=" + start + "-" + end);
try(InputStream in = conn.getInputStream();
RandomAccessFile out = new RandomAccessFile(tempFile, "rw")) {
out.seek(start);
byte[] buffer = new byte[1024];
int len;
while((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
}
}
}
注意事项
- 服务器必须支持Range请求(检查Accept-Ranges头)
- 合理设置线程数(通常3-8个为宜)
- 临时文件需要唯一命名避免冲突
- 需要处理线程中断异常
- 下载完成后校验文件完整性(MD5/SHA1)
完整示例流程
// 1. 获取文件总大小
long fileSize = getFileSize(url);
// 2. 创建空文件
RandomAccessFile file = new RandomAccessFile("target.zip", "rw");
file.setLength(fileSize);
// 3. 计算每个线程下载区间
long blockSize = fileSize / THREAD_COUNT;
// 4. 创建并启动线程
ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT);
for(int i=0; i<THREAD_COUNT; i++) {
long start = i * blockSize;
long end = (i == THREAD_COUNT-1) ? fileSize-1 : start + blockSize-1;
executor.execute(new DownloadTask(url, file, start, end));
}
// 5. 等待所有线程完成
executor.shutdown();
executor.awaitTermination(1, TimeUnit.HOURS);
性能优化方向
- 动态调整线程数(根据网速和服务器响应)
- 实现断点续传功能
- 添加下载速度统计和显示
- 支持下载队列管理
- 添加代理服务器支持
URL 与 HttpURLConnection 结合线程池管理连接
线程池管理连接的必要性
- 资源优化:避免频繁创建/销毁线程带来的性能开销
- 并发控制:防止过多并发连接导致服务器拒绝服务
- 统一管理:便于监控和统计网络请求情况
核心实现方案
// 创建固定大小的线程池
ExecutorService executor = Executors.newFixedThreadPool(5);
// 封装URL连接任务
class ConnectionTask implements Runnable {
private String url;
public ConnectionTask(String url) {
this.url = url;
}
@Override
public void run() {
try {
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
// 设置连接参数
connection.setRequestMethod("GET");
connection.setConnectTimeout(5000);
// 处理响应
int code = connection.getResponseCode();
if(code == 200) {
// 读取响应数据
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 提交任务到线程池
executor.execute(new ConnectionTask("http://example.com/api"));
关键注意事项
- 连接超时设置:必须设置合理的connectTimeout和readTimeout
- 异常处理:确保捕获IOException等网络异常
- 资源释放:在finally块中关闭InputStream和connection
- 线程数配置:根据实际需求调整线程池大小
高级优化建议
- 使用
ThreadPoolExecutor
替代Executors
以便更精细控制 - 添加任务队列监控机制
- 实现重试机制处理临时性网络故障
- 考虑使用连接池(如Apache HttpClient)替代原生HttpURLConnection
完整示例(带资源清理)
executor.execute(() -> {
HttpURLConnection connection = null;
InputStream is = null;
try {
connection = (HttpURLConnection) new URL(url).openConnection();
// 请求处理...
is = connection.getInputStream();
// 数据读取...
} catch (Exception e) {
// 错误处理
} finally {
if(is != null) try { is.close(); } catch(IOException ignored) {}
if(connection != null) connection.disconnect();
}
});