package bbt.servlet.ceshi; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; /** * 主题:多线程下载文件, * 通过设置请求的头字段确定下载部分; * * * @author xiaoqiang * */ public class ThreadUploadImg { /** * 知识点: 1.关键类使用-randomAccessFile的认识. 这个方法有两个主要的参数 <color >先认识这个类</color> * JDK此类的实例支持对随机访问文件的读取和写入。 随机访问文件的行为类似存储在文件系统中的一个大型 byte 数组。 * 存在指向该隐含数组的光标或索引,称为文件指针;输入操作从文件指针开始读取字节,并随着对字节的读取而前移此文件指针。 * 如果随机访问文件以读取/写入模式创建,则输出操作也可用;输出操作从文件指针开始写入字节,并随着对字节的写入而前移此文件指针。 * 写入隐含数组的当前末尾之后的输出操作导致该数组扩展。该文件指针可以通过 getFilePointer 方法读取,并通过 <Strong>seek<Strong> 方法设置。 * 通常,如果此类中的所有读取例程在读取所需数量的字节之前已到达文件末尾,则抛出 EOFException(是一种 IOException)。 * 如果由于某些原因无法读取任何字节,而不是在读取所需数量的字节之前已到达文件末尾, 则抛出 IOException,而不是 * EOFException。需要特别指出的是,如果流已被关闭,则可能抛出 IOException。 * * <Strong>构造方法</Strong> public RandomAccessFile(File file, String mode) throws * FileNotFoundException 创建从中读取和向其中写入(可选)的随机访问文件流,该文件由 File 参数指定。将创建一个新的 FileDescriptor * 对象来表示此文件的连接。 mode 参数指定用以打开文件的访问模式。允许的值及其含意为: "r" 以只读方式打开。调用结果对象的任何 write 方法都将导致抛出 * IOException。 "rw" 打开以便读取和写入。如果该文件尚不存在,则尝试创建该文件。 "rws"打开以便读取和写入,对于 * "rw",还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备。 "rwd" 打开以便读取和写入,对于"rw",还要求对文件内容的每个更新都同步写入到底层存储设备。 "rws" * 和 "rwd" 模式的工作方式极其类似 FileChannel 类的 force(boolean) 方法,分别传递 true 和 false 参数,除非它们始终应用于每个 I/O * 操作,并因此通常更为高效。如果该文件位于本地存储设备上,那么当返回此类的一个方法的调用时,可以保证由该调用对此文件所做的所有更改均被写入该设备。这对确保在系统崩溃时不会丢失重要信息特别有用。如果该文件不在本地设备上,则无法提供这样的保证。 * "rwd" 模式可用于减少执行的 I/O 操作数量。使用 "rwd" 仅要求更新要写入存储的文件的内容;使用 "rws" * 要求更新要写入的文件内容及其元数据,这通常要求至少一个以上的低级别 I/O 操作。 * * <Strong>重要方法</Strong> 设置到此文件开头测量到的文件指针偏移量,在该位置发生下一个读取或写入操作。 public void seek(long pos) throws * IOException 设置到此文件开头测量到的文件指针偏移量,在该位置发生下一个读取或写入操作。偏移量的设置可能会超出文件末尾。偏移量的设置超出文件末尾不会改变文件的长度。 * 只有在偏移量的设置超出文件末尾的情况下对文件进行写入才会更改其长度。 参数: pos - 从文件开头以字节为单位测量的偏移量位置,在该位置设置文件指针。 抛出: IOException * - 如果 pos 小于 0 或者发生 I/O 错误。 * * * 2.URLConnection java.net.URLConnection * 抽象类 URLConnection 是所有类的超类,它代表应用程序和 * URL之间的通信链接。此类的实例可用于读取和写入此 URL 引用的资源。 通常,创建一个到 URL 的连接需要几个步骤: 通过在 URL 上调用 openConnection * 方法创建连接对象。 处理设置参数和一般请求属性。 使用 connect 方法建立到远程对象的实际连接。 远程对象变为可用。远程对象的头字段和内容变为可访问。 * 使用以下方法修改一般请求属性: 方法: public void setRequestProperty(String key, String value) * 设置一般请求属性。如果已存在具有该关键字的属性,则用新值改写其值。 注:HTTP * 要求所有能够合法拥有多个具有相同键的实例的请求属性,使用以逗号分隔的列表语法,这样可实现将多个属性添加到一个属性中。 参数: key - 用于识别请求的关键字(例如," * accept")。 value - 与该键关联的值。 抛出: IllegalStateException - 如果已连接 NullPointerException - 如果键为 null * 另请参见: getRequestProperty(java.lang.String) * * 3.一般使用的是HttpUrlConnection抽象类来链接 * public abstract class HttpURLConnection extends URLConnection * 支持 HTTP 特定功能的 URLConnection。有关详细信息,请参阅 the spec 。 每个 HttpURLConnection * 实例都可用于生成单个请求,但是其他实例可以透明地共享连接到 HTTP 服务器的基础网络。 请求后在 HttpURLConnection 的 InputStream 或 * OutputStream 上调用 close() 方法可以释放与此实例关联的网络资源,但对共享的持久连接没有任何影响。如果在调用 disconnect() * 时持久连接空闲,则可能关闭基础套接字。 * */ /** * 思路: * 首先,我们为什么要多线程下载,我们知道单次下载就是,一个客服端向资源端请求资源,服务器端响应并返回资源的一个过程。 * 而多线程下载就是,比如,现在这个资源是10滴水,我们每次请求这个流水的通道返回的都是一滴水,所以,我们在同时,我们将需求请求的水,拉10根管道 * 也就是10次请求,所以,就很清晰了, * 第一,将这个请求资源分离成10块去请求; * 第二,我们分别下载并且缓冲到一个文件。 */ /** * 资料档案一: * URLConnection 类是一个抽象类,代表应用程序和URL之间的通信连接,此类的实例可用于读取和写入此URL引用的资源。URLConnection * 允许使用GET,POST或者其他HTTP方法请求方式将请求数据发送到服务器。使用URLConnection对象一般分为以下7步。 * * 1:创建一个URL对象; * * 2:通过URL对象的openConnection方法创建URLConnection对象; * * 3:通过URLConnection对象提供的方法可以设置参数和一般请求属性。常用的请求属性设置方式有以下几种: * * public void setRequestProperty(String key,String value)设置指定的请求关键字对应的值 * * public void setDoInput(boolean doinput)设置是否使用URL连接进行输入,默认值为true * * public void setDoOutput(boolean * dooutput)设置是否使用URL连接进行输出,默认值为false,如果设置为true,就可以获取一个字节输出流,用于将数据发送到服务器 * * public void setUseCaches(boolean usecaches)设置此连接是否使用任何可用的缓存,默认值为true * * 4:调用URLConnection对象的connect方法连接到该远程资源 * * 5:连接到服务器后,就可以查询头部信息了,查询头部信息常用方法有以下几种: * * public String getHeaderField(String name)返回指定头字段的值 * * public Map<String,List<String>>getHeaderFields()返回头字段的不可修改的Map * * public String getContentType()返回content-type头字段的值 * * public String getContentEncoding()返回content-encoding的值 * * 6:获取输入流访问资源数据。使用getInputStream 方法,获取一个字节输入流,以便读取资源信息 * * 7:获取输出流并写数据 * @throws IOException */ // 请求资源地址. public static final String imgUrl = "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1516018538510&di=d011ded42a5e30b9678b3e2dc8ada950&imgtype=0&src=http%3A%2F%2Fa.hiphotos.baidu.com%2Fzhidao%2Fpic%2Fitem%2F3c6d55fbb2fb4316352d920a22a4462309f7d394.jpg"; // 本地下载文件文件. public static final File file = new File("F:/xiaoqiang/picture/starrySky.jpg"); public static void main(String[] args) throws IOException { ThreadUploadImg threadUploadImg = new ThreadUploadImg(); threadUploadImg.downLoad(); } /** * 下载DEMO. * * @throws IOException 抛出流异常 */ public void downLoad() throws IOException { // 获取链接 URLConnection internetConnection = null; // 定义线程数 final int threadNum = 2; try { URL url = new URL(imgUrl); // 初始化链接地址 internetConnection = url.openConnection(); // 获取链接 HttpURLConnection httpRequest = (HttpURLConnection)internetConnection; // httpUrl是UrlConnection子类,使用子类方法 httpRequest.setRequestMethod("GET");// 请求方式 httpRequest.setConnectTimeout(3000);// 最大响应时间 httpRequest.setRequestProperty("connection", "keep-alive");//保持连接 httpRequest.setRequestProperty("accept", "*/*");// 接受所有请求 int code = httpRequest.getResponseCode(); // 接受状态码 if (code == 200) { // 如果响应状态码为200 程序继续走下去 int allLength = httpRequest.getContentLength(); // 底层调用 return // return getHeaderFieldLong("content-length",-1) // getHeaderField; 响应中长度 // 将文件进行分割 System.err.println("总长度为: " + allLength); httpRequest.disconnect();// 断开链接 int dataNum = allLength / threadNum; System.out.println("将 " + allLength + "分为两份,每份平均分为: " + dataNum); for (int i = 0; i < threadNum; i++) { int downloadStartPort = 0; // 计算起点 int downloadEndPort = 0; // 计算止点 downloadStartPort = dataNum * i; downloadEndPort = dataNum * (i + 1) - 1; if (i == (threadNum - 1)) { downloadEndPort = allLength; } System.err.println("第 " + i +" 个线程下载的大小是" + downloadStartPort + " - " + downloadEndPort); //使用有参构造 /** *用start方法来启动线程,真正实现了多线程运行,这时无需等待run方法体代码执行完毕而直接继续执行下面的代码。 * 通过调用Thread类的start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行, * 一旦得到cpu时间片,就开始执行run()方法,这里方法 run()称为线程体, * 它包含了要执行的这个线程的内容,Run方法运行结束,此线程随即终止。 */ new DownThread(i, downloadStartPort, downloadEndPort).start(); } } else { System.err.print("请求失败!"); } } catch (MalformedURLException e) { System.err.println("链接失败"); } } /** * 线程下载任务类 * * @author xiaoqiang. * */ public class DownThread extends Thread { /*** * 线程编号. */ private int threadId; /** * 开始点. */ private int startPoint; /** * 结束点. */ private int endpoint; public DownThread(int threadId, int startPoint, int endpoint) { super(); this.threadId = threadId; this.startPoint = startPoint; this.endpoint = endpoint; } @Override public void run() { try { URL url = new URL(imgUrl); HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); urlConnection.setRequestMethod("GET");// 请求方式 urlConnection.setConnectTimeout(3000);// 最大响应时间 urlConnection.setRequestProperty("connection", "keep-alive"); urlConnection.setRequestProperty("accept", "*/*"); urlConnection.setRequestProperty("Range", "bytes=" + startPoint + "-" + endpoint); // 设置头部参数,请求这个资源的某一部分 /** * HTTP Status-Code 206: Partial Content. 返回部分内容为206 */ if (HttpURLConnection.HTTP_PARTIAL == urlConnection.getResponseCode()) { InputStream is = urlConnection.getInputStream(); // 得到输入流 int hasRead = 0; byte[] buf = new byte[1024]; RandomAccessFile raf = new RandomAccessFile(file, "rw"); raf.seek(startPoint); // 将文件的写的指针指定开始文件,因为randomAccessFile类是可以在任意位置之间进行操作的。seek()的方法是里面的参数是字节 while((hasRead = is.read(buf)) > 0) { raf.write(buf, 0, hasRead); // 写出文件(RandomAccessFile是同时拥有写入写出的类) } is.close(); // 关闭流 raf.close(); // 关闭流 System.out.println("线程" + threadId + "这块部分下载完成"); } } catch (Exception e) { System.err.println("异常"); } } } }
多线程使用-java网络开发-文件下载
最新推荐文章于 2024-03-20 13:56:32 发布