URL
URL类表示统一资源定位符,即指向万维网上“资源”的指针。 资源可以是简单的文件或目录,也可以是对更复杂对象的引用,例如对数据库或搜索引擎的查询。
protocol://host:port/file举例:http://news.baidu.com:80/guonei
URL类提供了多个构造器用于创建URL对象:
URL(String protocol, String host, int port, String file)URL(String protocol, String host, String file)URL(String protocol, String host, int port, String file, URLStreamHandler handler)注意:
抽象类URLStreamHandler是所有流协议处理程序的通用超类。 流协议处理程序知道如何为特定的协议类型建立连接,例如http或https 。
在大多数情况下, URLStreamHandler子类的实例不是由应用程序直接创建的。 相反,在构造URL时第一次遇到协议名称时,会自动加载适当的流协议处理程序。URL(String spec):参数:spec – 要解析为 URL 的String 。URL(URL context, String spec):context — 解析规范的上下文。URL(URL context, String spec, URLStreamHandler handler)
访问URL对象对应的资源的常用方法:
➢ String getFile():获取该URL的资源名。
(返回的文件部分将与getPath()相同,如果getQuery()有值则串联getQuery()的值,如果没有查询部分,此方法和getPath()将返回相同的结果。)
➢ String getHost():获取该URL的主机名。➢ String getPath():获取该URL的路径部分。
➢ int getPort():获取该URL的端口号。
➢ String getProtocol():获取该URL的协议名称。
➢ String getQuery():获取该URL的查询字符串部分。
➢ URLConnection openConnection():返回一个URLConnection对象,它代表了与URL所引用的远程对象的连接。
➢ URLConnection openConnection(Proxy proxy):与openConnection()相同,除了连接将通过指定的代理进行;不支持代理的协议处理程序将忽略代理参数并进行正常连接。 调用此方法会抢占系统的默认 ProxySelector 设置。
➢ InputStream openStream():打开与此URL的连接,并返回一个用于读取该URL资源的InputStream。
通过URL对象的openStream()方法来读取URL资源的InputStream,来实现多线程下载。
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.UUID;
/**
* @program: Download
* @description: 多线程下载工具类
* @author: YOUNG
* @create: 2021-09-29 16:56
*/
public class Download {
/**
* url
*/
private URL url;
/**
* 线程数量
*/
private int threadCount;
/**
* 文件名
*/
private String filename = UUID.randomUUID().toString();
public static void main(String[] args) {
new Download().load("下载链接", "文件名").setThreadCount(8).start();
}
public Download load(String url, String filename) {
try {
this.url = new URL(url);
} catch (MalformedURLException e) {
e.printStackTrace();
}
if (this.url.getProtocol() == null
|| this.url.getHost() == null
|| this.url.getFile() == null)
throw new RuntimeException("URL地址不正确!");
this.filename = filename;
return this;
}
public Download setThreadCount(int threadCount) {
if (threadCount < 0)
this.threadCount = 1;
else this.threadCount = Math.min(threadCount, Runtime.getRuntime().availableProcessors());
return this;
}
public void start() {
try {
URLConnection urlConnection = url.openConnection();
long fileLength = urlConnection.getContentLength();
System.out.println("下载文件的大小:" + fileLength);
RandomAccessFile file = new RandomAccessFile(filename, "rwd");
file.setLength(fileLength);
file.close();
long threadBlockSize = fileLength / threadCount;
long finalThreadSize = fileLength % threadCount;
System.out.println("分块大小:" + threadBlockSize);
System.out.println("最后一块大小:" + (threadBlockSize + finalThreadSize));
for (int i = 0; i < threadCount; i++) {
if (i == threadCount - 1)
new Thread(new threadBlock(i, threadBlockSize * i, threadBlockSize + finalThreadSize)).start();
else
new Thread(new threadBlock(i, threadBlockSize * i, threadBlockSize)).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
class threadBlock implements Runnable {
private final long skipSize;
private int threadId;
private long downloadSize;
public threadBlock(int threadId, long skipSize, long downloadSize) {
this.threadId = threadId;
this.skipSize = skipSize;
this.downloadSize = downloadSize;
}
public threadBlock(long skipSize, long downloadSize) {
this.skipSize = skipSize;
this.downloadSize = downloadSize;
}
@Override
public void run() {
try (InputStream inputStream = url.openStream();
RandomAccessFile tmpFile = new RandomAccessFile(filename, "rw")) {
inputStream.skip(skipSize);
tmpFile.seek(skipSize);
System.out.println(threadId + "-跳过:" + skipSize);
//1kB
byte[] buff = new byte[1024];
System.out.println(threadId + "-开始:" + tmpFile.getFilePointer());
int readLen = 0;
while (downloadSize > 0) {
readLen = inputStream.read(buff);
if (readLen == -1)
break;
tmpFile.write(buff, 0, readLen);
downloadSize -= readLen;
System.out.println(readLen);
System.out.println(threadId + "-当前:" + tmpFile.getFilePointer());
}
System.out.println(threadId + "-end:" + tmpFile.getFilePointer());
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
URLConnection
通常创建一个和URL的连接,并发送请求、读取此URL引用的资源需要如下几个步骤:
① 通过调用URL对象的openConnection()方法来创建URLConnection对象。
② 设置URLConnection的参数和普通请求属性。
③ 如果只是发送GET方式请求,则使用connect()方法建立和远程资源之间的实际连接即可;如果需要发送POST方式的请求,则需要获取URLConnection实例对应的输出流来发送请求参数。
④ 远程资源变为可用,程序可以访问远程资源的头字段或通过输入流读取远程资源的数据。
在建立和远程资源的实际连接之前,程序可以通过如下方法来设置请求头字段。
➢ setAllowUserInteraction():设置该URLConnection的allowUserInteraction请求头字段的值。
➢ setDoInput():设置该URLConnection的doInput请求头字段的值。
➢ setDoOutput():设置该URLConnection的doOutput请求头字段的值。
➢ setIfModifiedSince():设置该URLConnection的ifModifiedSince请求头字段的值。
➢ setUseCaches():设置该URLConnection的useCaches请求头字段的值。
➢ setRequestProperty(String key, String value):设置该URLConnection的key请求头字段的值为value。
➢ addRequestProperty(String key, String value):为该URLConnection的key请求头字段增加value值,该方法并不会覆盖原请求头字段的值,而是将新值追加到原请求头字段中。
当远程资源可用之后,程序可以使用以下方法来访问头字段和内容。
➢ Object getContent():获取该URLConnection的内容。
➢ String getHeaderField(String name):获取指定响应头字段的值。
- getContentEncoding():获取content-encoding响应头字段的值。
- getContentLength():获取content-length响应头字段的值。
- getContentType():获取content-type响应头字段的值。
- getDate():获取date响应头字段的值。
- getExpiration():获取expires响应头字段的值。
- getLastModified():获取last-modified响应头字段的值。
➢ getInputStream():返回该URLConnection对应的输入流,用于获取URLConnection响应的内容。➢ getOutputStream():返回该URLConnection对应的输出流,用于向URLConnection发送请求参数。
注意:
如果既要使用输入流读取URLConnection响应的内容,又要使用输出流发送请求参数,则一定要先使用输出流,再使用输入流。
/**
* 向指定URL发送GET方法的请求
*
* @param url 发送请求的URL
* @param param 请求参数,请求参数应该是name1=value1&name2=value2的形式。
* @return URL所代表远程资源的响应
*/
public static String get(String url, String param) {
StringBuilder result = new StringBuilder();
BufferedReader in = null;
try {
String urlName = url + "?" + param;
URL realUrl = new URL(urlName);
//打开和URL之间的连接
URLConnection conn = realUrl.openConnection();
//设置通用的请求属性
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("user-agent",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)");
//建立实际的连接
conn.connect();
//获取所有响应头字段
Map<String, List<String>> map = conn.getHeaderFields();
System.out.println(map);
//定义BufferedReader输入流来读取URL的响应
in = new BufferedReader(
new InputStreamReader(conn.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
result.append("\n").append(line);
}
} catch (Exception e) {
System.out.println("发送GET请求出现异常!" + e);
e.printStackTrace();
}
//使用finally块来关闭输入流
finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return result.toString();
}
/**
* 向指定URL发送POST方法的请求
*
* @param url 发送请求的URL
* @param param 请求参数,请求参数应该是name1=value1&name2=value2的形式。
* @return URL所代表远程资源的响应
*/
public static String post(String url, String param) {
PrintWriter out = null;
BufferedReader in = null;
StringBuilder result = new StringBuilder();
try {
URL realUrl = new URL(url);
//打开和URL之间的连接
URLConnection conn = realUrl.openConnection();
//设置通用的请求属性
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("user-agent",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)");
//发送POST请求必须设置如下两行
conn.setDoOutput(true);
conn.setDoInput(true);
//获取URLConnection对象对应的输出流
out = new PrintWriter(conn.getOutputStream());
//发送请求参数
out.print(param);
//flush输出流的缓冲
out.flush();
//定义BufferedReader输入流来读取URL的响应
in = new BufferedReader(
new InputStreamReader(conn.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
result.append("\n").append(line);
}
} catch (Exception e) {
System.out.println("发送POST请求出现异常!" + e);
e.printStackTrace();
}
//使用finally块来关闭输出流、输入流
finally {
try {
if (out != null) {
out.close();
}
if (in != null) {
in.close();
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
return result.toString();
}
URLPermission
Java 8 新增了一个 URLPermission 工具类,用于管理 HttpURLConnection 的权限问题,如果在 HttpURLConnection 安装了安全管理器,通过该对象打开连接时就需要先获得权限。