一、概述
java下的多线程其实就是对RandomAccessFile类的应用,事实上就是一个很纯粹、很基础的java程序。RandomAccessFile最变态的是它可以对文件进行读写。java的多线程,其实就是学习RandomAccessFile这个类。
二、RandomAccessFile类
此类的实例支持对随机访问文件的读取和写入。随机访问文件的行为类似存储在文件系统中的一个大型 byte 数组。存在指向该隐含数组的光标或索引,称为文件指针;输入操作从文件指针开始读取字节,并随着对字节的读取而前移此文件指针。如果随机访问文件以读取/写入模式创建,则输出操作也可用;输出操作从文件指针开始写入字节,并随着对字节的写入而前移此文件指针。写入隐含数组的当前末尾之后的输出操作导致该数组扩展。该文件指针可以通过 getFilePointer 方法读取,并通过 seek 方法设置。
多线程应用的方法和构造方法有:
* RandomAccessFile(File file, String mode) 关注mode的取值
“r” 以只读方式打开。调用结果对象的任何 write 方法都将导致抛出 IOException。
“rw” 打开以便读取和写入。如果该文件尚不存在,则尝试创建该文件。
“rws” 打开以便读取和写入,对于 “rw”,还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备。
“rwd” 打开以便读取和写入,对于 “rw”,还要求对文件内容的每个更新都同步写入到底层存储设备。
write(byte[] b, int off, int len) 不多说
seek(long pos) 设置到此文件开头测量到的文件指针偏移量,在该位置发生下一个读取或写入操作。
三、多线程步骤简述
- 与服务器连接前,先设置对一个线程中文件分段下载情况:
conn.setRequestProperty("Range" , "bytes="+startIndex +"-"+endIndex);
- 然后用RandomAccessFile的对象进行写入,
RandomAccessFile raf = new RandomAccessFile("/mnt/sdcard/wubi.exe","rw");
- 跳过n个字符(其它线程下载)
raf.seek(startIndex);
- 开时写入
raf.write(buf, 0, len);
四、测试程序
package com.sky.test;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
/**
* 这个类模拟三个线程同时下载,但是不能断点续传
*
* */
public class ThreeThreadDownLoad {
private static final int THREAD_COUNT = 3;
private static int runningThread;
public static void main(String[] args) throws Exception {
String path = "http://localhost:8080/wubi.exe";
//下面是文件读取开始和结束的标记
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
int length = conn.getContentLength();
//根据服务器的文件大小,在本地也创建一个空的大小 的文件。
RandomAccessFile raf = new RandomAccessFile("wubi.exe", "rw");
raf.setLength(length);
//每一个线程文字读取开始和结束的标记
int startIndex;
int endIndex;
runningThread = THREAD_COUNT;
//
//下面是开三个线程
for (int threadId = 0; threadId<THREAD_COUNT; threadId++) {
startIndex=threadId*length/THREAD_COUNT;
endIndex=(threadId+1)*length/THREAD_COUNT-1;
if(threadId==THREAD_COUNT-1){
//如果是最后一个线程,考虑不能整除的情况
endIndex=length-1;
}
//每次开启三个线程,并把字节的首和尾传进去
new MyThread(startIndex,endIndex,threadId,path).start();
}
}
static class MyThread extends Thread{
private int startIndex;
private int endIndex;
private int threadId;
private String path;
private int currentPosition;
public MyThread(int startIndex, int endIndex, int threadId, String path) {
super();
this.startIndex = startIndex;
this.endIndex = endIndex;
this.threadId = threadId;
this.path = path;
currentPosition = startIndex;
}
@Override
public void run() {
try {
//这里是判断之前是否下载过,下载过就接着怼
File pf = new File(threadId+".position");
if(pf.exists()){
FileInputStream fos = new FileInputStream(threadId+".position");
BufferedReader br = new BufferedReader(new InputStreamReader(fos));
String line = br.readLine();
currentPosition=Integer.valueOf(line);
System.out.println("这个文件被线程:"+threadId+"下载过,"+"下载的最后的位置是:"+currentPosition);
}
//1.与服务器建立连接,并得到连接对象
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestProperty("Range" , "bytes="+startIndex +"-"+endIndex);
//2.连接对象得到文件的相关信息
//下面这句话已经对服务器请求了,如果setRequestProperty方法一定要放在连接请求的前面。
int code = conn.getResponseCode();
//3.建立文件读写类,开怼
RandomAccessFile raf = new RandomAccessFile("wubi.exe","rw");
System.out.println("…………………………");
raf.seek(startIndex);
//告诉服务器,客户端想要从什么位置型读取,读到什么时候结束
//3.文件断点下载时,状态码是206,不是200
if(code==206){
//4.得到输入流,
InputStream is = conn.getInputStream();
byte[] buf = new byte[1024];
int len=0;
//6.读出并写入
while((len=is.read(buf))!=-1){
raf.write(buf, 0, len);
currentPosition+=len;
//下面是断点续传的文件记录
RandomAccessFile frec=new RandomAccessFile(threadId+".position", "rws");
frec.write((currentPosition+"").getBytes());
frec.close();
}
//7.关流
is.close();
raf.close();
System.out.println("下载完成");
synchronized (ThreeThreadDownLoad.class) {
runningThread--;
if(runningThread<=0){
for (int i = 0; i < THREAD_COUNT; i++) {
File positionfile = new File(i+".position");
positionfile.delete();
}
}
}
}
System.out.println("Thread:"+threadId+"下载完成,下载了"+(endIndex-startIndex));
} catch ( Exception e) {
e.printStackTrace();
System.out.println("下载失败");
}
}
}
}
五、正式的程序
try {
//这里是判断之前是否下载过,下载过就接着怼
File pf = new File(threadId+".position");
if(pf.exists()){
FileInputStream fos = new FileInputStream(threadId+".position");
BufferedReader br = new BufferedReader(new InputStreamReader(fos));
String line = br.readLine();
currentPosition=Integer.vralueOf(line);
System.out.println("这个文件被线程:"+threadId+"下载过,"+"下载的最后的位置是:"+currentPosition);
}
//1.与服务器建立连接,并得到连接对象
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestProperty("Range" , "bytes="+startIndex +"-"+endIndex);
//2.连接对象得到文件的相关信息
int code = conn.getResponseCode();
//3.建立文件读写类,开怼
RandomAccessFile raf = new RandomAccessFile("/mnt/sdcard/wubi.exe","rw");
System.out.println("…………………………");
raf.seek(startIndex);
//告诉服务器,客户端想要从什么位置型读取,读到什么时候结束
//3.文件断点下载时,状态码是206,不是200
if(code==206){
//4.得到输入流,
InputStream is = conn.getInputStream();
byte[] buf = new byte[1024];
int len=0;
//6.读出并写入
while((len=is.read(buf))!=-1){//这里一定要注意把read放到buf里
raf.write(buf, 0, len);
currentPosition+=len;
//下面是断点续传的文件记录
RandomAccessFile frec=new RandomAccessFile("/mnt/sdcard/"+threadId+".position", "rws");
frec.write((currentPosition+"").getBytes());
frec.close();
//下面是对进度条的更新
if(threadId==0){
pb1.setMax(endIndex-startIndex);
pb1.setProgress(currentPosition-startIndex);
}else if(threadId==1){
pb2.setMax(endIndex-startIndex);
pb2.setProgress(currentPosition-startIndex);
}else if(threadId==2){
pb3.setMax(endIndex-startIndex);
pb3.setProgress(currentPosition-startIndex);
}
}
//7.关流
is.close();
raf.close();
System.out.println("下载完成");
synchronized (MyThread.class) {
runningThread--;
if(runningThread<=0){
for (int i = 0; i < THREAD_COUNT; i++) {
File positionfile = new File("/mnt/sdcard/"+i+".position");
positionfile.delete();
}
}
}
}
System.out.println("Thread:"+threadId+"下载完成,下载了"+(endIndex-startIndex));
} catch ( Exception e) {
e.printStackTrace();
System.out.println("下载失败");
}