美文网首页
RandomAccessFile分析和demo

RandomAccessFile分析和demo

作者: 嘎嘣脆哦哦 | 来源:发表于2019-02-11 16:56 被阅读0次

    本文出自:https://blog.csdn.net/dt235201314/article/details/80911932

    一丶概述

    断点续传是IO章节的练习,显然实现断点续传知识点不仅仅是IO相关知识,RandomAccessFile倒是主要环节

    二丶效果图

    三丶RandomAccessFile

    1.简介

    我们可以看到它的父类是Object,没有继承字节流、字符流家族中任何一个类。并且它实现了DataInput、DataOutput这两个接口,也就意味着这个类既可以读也可以写。

    RandomAccessFile既可以读取文件内容,也可以向文件输出数据。同时支持“随机访问”的方式,可以直接跳转到文件的任意地方来读写数据。

    与OutputStream、Writer等输出流不同的是,RandomAccessFile允许自由定义文件记录指针,RandomAccessFile可以不从开始的地方开始输出,因此RandomAccessFile可以向已存在的文件后追加内容。如果程序需要向已存在的文件后追加内容,则应该使用RandomAccessFile。

    RandomAccessFile的一个重要使用场景就是网络请求中的多线程下载及断点续传。

    2.构造方法

    RandomAccessFile类有两个构造函数,其实这两个构造函数基本相同,只不过是指定文件的形式不同——一个需要使用String参数来指定文件名,一个使用File参数来指定文件本身。除此之外,创建RandomAccessFile对象时还需要指定一个mode参数,该参数指定RandomAccessFile的访问模式,一共有4种模式:

    "r": 以只读方式打开。调用结果对象的任何 write 方法都将导致抛出 IOException。

    "rw": 打开以便读取和写入。

    "rws": 打开以便读取和写入。相对于 "rw","rws" 还要求对“文件的内容”或“元数据”的每个更新都同步写入到基础存储设备。

    "rwd" : 打开以便读取和写入,相对于 "rw","rwd" 还要求对“文件的内容”的每个更新都同步写入到基础存储设备。

    3.重要方法

    RandomAccessFile对象包含了一个记录指针,用以标识当前读写处的位置,当程序新创建一个RandomAccessFile对象时,该对象的文件指针记录位于文件头(也就是0处),当读/写了n个字节后,文件记录指针将会后移n个字节。除此之外,RandomAccessFile还可以自由移动该记录指针。下面就是RandomAccessFile具有的两个特殊方法,来操作记录指针,实现随机访问:

    long getFilePointer( ):返回文件记录指针的当前位置

    void seek(long pos ):将文件指针定位到pos位置

    4.demo案例

    public static void main(String[] args)

    {

        try

        {

            insert("d:/out.txt",5,"插入的内容");

        }

        catch (IOException e)

        {

            e.printStackTrace();

        }

    }

    private static void insert(String fileName,long pos,String content) throws IOException

    {

        //创建临时空文件

        File tempFile = File.createTempFile("temp",null);

        //在虚拟机终止时,请求删除此抽象路径名表示的文件或目录

        tempFile.deleteOnExit();

        FileOutputStream fos = new FileOutputStream(tempFile);

        RandomAccessFile raf = new RandomAccessFile(fileName,"rw");

        raf.seek(pos);

        byte[] buffer = new byte[4];

        int num = 0;

        while(-1 != (num = raf.read(buffer)))

        {

            fos.write(buffer,0,num);

        }

        raf.seek(pos);

        raf.write(content.getBytes());

        FileInputStream fis = new FileInputStream(tempFile);

        while(-1 != (num = fis.read(buffer)))

        {

            raf.write(buffer,0,num);

        }

    }   

    一个汉字=2个英文字母=2字节  杭州G=5个字节

    四丶断点续传实现原理

    其实断点续传的原理很简单,从字面上理解,所谓断点续传就是从停止的地方重新下载。

    断点:线程停止的位置。

    续传:从停止的位置重新下载。

    用代码解析就是:

    断点 ==> 当前线程已经下载完成的数据长度。

    续传 ==> 向服务器请求上次线程停止位置之后的数据。

    原理知道了,功能实现起来也简单。每当线程停止时就把已下载的数据长度写入记录文件,当重新下载时,从记录文件读取已经下载了的长度。而这个长度就是所需要的断点。

    续传的实现也简单,可以通过设置网络请求参数,请求服务器从指定的位置开始读取数据。

    而要实现这两个功能只需要使用到httpURLconnection里面的setRequestProperty方法便可以实现.

    public void setRequestProperty(String field, String newValue)

    如下所示,便是向服务器请求500-1000之间的500个byte:

    conn.setRequestProperty("Range", "bytes=" + 500 + "-" + 1000);

    以上只是续传的一部分需求,当我们获取到下载数据时,还需要将数据写入文件,而普通发File对象并不提供从指定位置写入数据的功能,这个时候,就需要使用到RandomAccessFile来实现从指定位置给文件写入数据的功能。

    public void seek(long offset)

    如下所示,便是从文件的的第100个byte后开始写入数据。

    raFile.seek(100);

    而开始写入数据时还需要用到RandomAccessFile里面的另外一个方法

    public void write(byte[] buffer, int byteOffset, int byteCount)

    该方法的使用和OutputStream的write的使用一模一样...

    而多线程断点续传便是在单线程的断点续传上延伸的,而多线程断点续传是把整个文件分割成几个部分,每个部分由一条线程执行下载,而每一条下载线程都要实现断点续传功能。

    为了实现文件分割功能,我们需要使用到httpURLconnection的另外一个方法:

    public int getContentLength()

    当请求成功时,可以通过该方法获取到文件的总长度。

    每一条线程下载大小 = fileLength / THREAD_NUM

    在多线程断点续传下载中,有一点需要特别注意:

    由于文件是分成多个部分是被不同的线程的同时下载的,这就需要,每一条线程都分别需要有一个断点记录,和一个线程完成状态的记录;

    只有所有线程的下载状态都处于完成状态时,才能表示文件已经下载完成。

    五丶核心代码实现

    主Activity

    public class DownLoadActivity extends Activity {

        private static final int PROCESSING = 1;

        private static final int FAILURE = -1;

        private EditText pathText;

        private Button downloadButton;

        private Button stopButton;

        private ProgressBar progressBar;

        private Context context;

        Handler handler = new UIHandler(this);

        private static class UIHandler extends Handler {

            private final WeakReference<DownLoadActivity> mActivity;

            //弱引用,避免内存泄露

            UIHandler(DownLoadActivity activity) {

                mActivity = new WeakReference<>(activity);

            }

            @Override

            public void handleMessage(Message msg) {

                DownLoadActivity activity = mActivity.get();

                if (activity != null) {

                    switch (msg.what) {

                        case PROCESSING:

                            ProgressBar progressBar = (ProgressBar) activity.findViewById(R.id.progressBar);

                            TextView resultView = (TextView) activity.findViewById(R.id.resultView);

                            progressBar.setProgress(msg.getData().getInt("size"));

                            float num = (float) progressBar.getProgress() / (float) progressBar.getMax();

                            int result = (int) (num * 100);

                            resultView.setText(result + "%");

                            if (progressBar.getProgress() == progressBar.getMax()) {

                                Toast.makeText(activity, R.string.success, Toast.LENGTH_LONG).show();

                            }

                            break;

                        case FAILURE:

                            Toast.makeText(activity, R.string.error, Toast.LENGTH_LONG).show();

                            break;

                        default:

                    }

                }

            }

        }

        @Override

        protected void onCreate(Bundle savedInstanceState) {

            super.onCreate(savedInstanceState);

            setContentView(R.layout.down_load_activity);

            context = this;

            pathText = (EditText) findViewById(R.id.path);

            downloadButton = (Button) findViewById(R.id.downloadbutton);

            stopButton = (Button) findViewById(R.id.stopbutton);

            progressBar = (ProgressBar) findViewById(R.id.progressBar);

            ButtonClickListener listener = new ButtonClickListener();

            downloadButton.setOnClickListener(listener);

            stopButton.setOnClickListener(listener);

        }

        private class ButtonClickListener implements View.OnClickListener {

            @Override

            public void onClick(View v) {

                switch (v.getId()) {

                    case R.id.downloadbutton:

                        String path = pathText.getText().toString();

                        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {

                            //外部存储

                            //File savDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES);

                            //File savDir = Environment.getExternalStorageDirectory();

                            //内部存储

                            File savDir = new File(context.getCacheDir(), "/cache/exe");

                            download(path, savDir);

                        } else {

                            Toast.makeText(getApplicationContext(), R.string.sdcarderror, Toast.LENGTH_LONG).show();

                        }

                        downloadButton.setEnabled(false);

                        stopButton.setEnabled(true);

                        break;

                    case R.id.stopbutton:

                        exit();

                        Toast.makeText(getApplicationContext(), "Now thread is Stopping!!", Toast.LENGTH_LONG).show();

                        downloadButton.setEnabled(true);

                        stopButton.setEnabled(false);

                        break;

                    default:

                }

            }

            private DownloadTask task;

            private void exit() {

                if (task != null) {

                    task.exit();

                }

            }

            private void download(String path, File savDir) {

                task = new DownloadTask(path, savDir);

                new Thread(task).start();

            }

            class DownloadTask implements Runnable {

                private String path;

                private File saveDir;

                private FileDownloader loader;

                DownloadTask(String path, File saveDir) {

                    this.path = path;

                    this.saveDir = saveDir;

                }

                void exit() {

                    if (loader != null) {

                        loader.exit();

                    }

                }

                //进度监听,通过message机制把传送进度

                DownloadProgressListener downloadProgressListener = new DownloadProgressListener() {

                    @Override

                    public void onDownloadSize(int size) {

                        Message msg = new Message();

                        msg.what = PROCESSING;

                        msg.getData().putInt("size", size);

                        handler.sendMessage(msg);

                    }

                };

                @Override

                public void run() {

                    try {

                        //固定三个线程

                        loader = new FileDownloader(getApplicationContext(), path, saveDir, 3);

                        progressBar.setMax(loader.getFileSize());

                        loader.download(downloadProgressListener);

                    } catch (Exception e) {

                        e.printStackTrace();

                        handler.sendMessage(handler.obtainMessage(FAILURE));

                    }

                }

            }

        }

    }

    下载线程类

    public class DownloadThread extends Thread {

        private static final String TAG = "DownloadThread";

        private File saveFile;

        private URL downUrl;

        private int block;

        private int threadId = -1;

        private int downloadedLength;

        private boolean finished = false;

        private FileDownloader downloader;

        DownloadThread(FileDownloader downloader, URL downUrl, File saveFile, int block, int downloadedLength, int threadId) {

            this.downUrl = downUrl;

            this.saveFile = saveFile;

            this.block = block;

            this.downloader = downloader;

            this.threadId = threadId;

            this.downloadedLength = downloadedLength;

        }

        @Override

        public void run() {

            if(downloadedLength < block){

                try {

                    HttpURLConnection http = (HttpURLConnection) downUrl.openConnection();

                    http.setConnectTimeout(5 * 1000);

                    http.setRequestMethod("GET");

                    http.setRequestProperty("Accept", "image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*");

                    http.setRequestProperty("Accept-Language", "zh-CN");

                    http.setRequestProperty("Referer", downUrl.toString());

                    http.setRequestProperty("Charset", "UTF-8");

                    int startPos = block * (threadId - 1) + downloadedLength;

                    int endPos = block * threadId -1;

                    http.setRequestProperty("Range", "bytes=" + startPos + "-"+ endPos);

                    http.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)");

                    http.setRequestProperty("Connection", "Keep-Alive");

                    InputStream inStream = http.getInputStream();

                    byte[] buffer = new byte[1024];

                    int offset = 0;

                    print("Thread " + this.threadId + " starts to download from position "+ startPos);

                    RandomAccessFile threadFile = new RandomAccessFile(this.saveFile, "rwd");

                    threadFile.seek(startPos);

                    while (!downloader.getExited() && (offset = inStream.read(buffer, 0, 1024)) != -1) {

                        threadFile.write(buffer, 0, offset);

                        downloadedLength += offset;

                        downloader.update(this.threadId, downloadedLength);

                        downloader.append(offset);

                    }

                    threadFile.close();

                    inStream.close();

                    if(downloader.getExited())

                        print("Thread " + this.threadId + " has been paused");

                    else

                        print("Thread " + this.threadId + " download finish");

                    this.finished = true;

                } catch (Exception e) {

                    this.downloadedLength = -1;

                    print("Thread "+ this.threadId+ ":"+ e);

                }

            }

        }

        private static void print(String msg){

            Log.i(TAG, msg);

        }

        public boolean isFinished() {

            return finished;

        }

        public long getDownloadedLength() {

            return downloadedLength;

        }

    }

    文件下载类

    public class FileDownloader {

        private static final String TAG = "FileDownloader";

        private Context context;

        private FileService fileService;

        private boolean exited;

        private int downloadedSize = 0;

        private int fileSize = 0;

        private DownloadThread[] threads;

        private File saveFile;

        private Map<Integer, Integer> data = new ConcurrentHashMap<Integer, Integer>();

        private int block;

        private String downloadUrl;

        public int getThreadSize() {

            return threads.length;

        }

        public void exit() {

            this.exited = true;

        }

        public boolean getExited() {

            return this.exited;

        }

        public int getFileSize() {

            return fileSize;

        }

        protected synchronized void append(int size) {

            downloadedSize += size;

        }

        protected synchronized void update(int threadId, int pos) {

            this.data.put(threadId, pos);

            this.fileService.update_tyc(this.downloadUrl, threadId, pos);

            //this.fileService.update(this.downloadUrl, this.data);

        }

        public FileDownloader(Context context, String downloadUrl, File fileSaveDir, int threadNum) {

            try {

                this.context = context;

                this.downloadUrl = downloadUrl;

                fileService = new FileService(this.context);

                URL url = new URL(this.downloadUrl);

                if (!fileSaveDir.exists())

                    fileSaveDir.mkdirs();

                this.threads = new DownloadThread[threadNum];

                HttpURLConnection conn = (HttpURLConnection) url.openConnection();

                conn.setConnectTimeout(5 * 1000);

                conn.setRequestMethod("GET");

                conn.setRequestProperty("Accept","image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*");

                conn.setRequestProperty("Accept-Language", "zh-CN");

                conn.setRequestProperty("Referer", downloadUrl);

                conn.setRequestProperty("Charset", "UTF-8");

                conn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)");

                conn.setRequestProperty("Connection", "Keep-Alive");

                conn.connect();

                printResponseHeader(conn);

                if (conn.getResponseCode() == 200) {

                    this.fileSize = conn.getContentLength();

                    if (this.fileSize <= 0)

                        throw new RuntimeException("Unkown file size ");

                    String filename = getFileName(conn);

                    this.saveFile = new File(fileSaveDir, filename);

                    Map<Integer, Integer> logdata = fileService.getData(downloadUrl);

                    if (logdata.size() > 0) {

                        for (Map.Entry<Integer, Integer> entry : logdata.entrySet())

                            data.put(entry.getKey(), entry.getValue());

                    }

                    if (this.data.size() == this.threads.length) {

                        for (int i = 0; i < this.threads.length; i++) {

                            this.downloadedSize += this.data.get(i + 1);

                        }

                        print("已经下载的长度" + this.downloadedSize + "个字节");

                    }

                    this.block = (this.fileSize % this.threads.length) == 0 ? this.fileSize / this.threads.length : this.fileSize / this.threads.length + 1;

                } else {

                    print("服务器响应错误:" + conn.getResponseCode() + conn.getResponseMessage());

                    throw new RuntimeException("server response error ");

                }

            } catch (Exception e) {

                print(e.toString());

                throw new RuntimeException("Can't connection this url");

            }

        }

        private String getFileName(HttpURLConnection conn) {

            String filename = this.downloadUrl.substring(this.downloadUrl.lastIndexOf('/') + 1);

            if (filename == null || "".equals(filename.trim())) {

                for (int i = 0;; i++) {

                    String mine = conn.getHeaderField(i);

                    if (mine == null)

                        break;

                    if ("content-disposition".equals(conn.getHeaderFieldKey(i).toLowerCase())) {

                        Matcher m = Pattern.compile(".*filename=(.*)").matcher(mine.toLowerCase(Locale.getDefault()));

                        if (m.find())

                            return m.group(1);

                    }

                }

                filename = UUID.randomUUID() + ".tmp";

            }

            return filename;

        }

        public int download(DownloadProgressListener listener) throws Exception {

            try {

                RandomAccessFile randOut = new RandomAccessFile(this.saveFile, "rwd");

                if (this.fileSize > 0)

                    randOut.setLength(this.fileSize);

                randOut.close();

                URL url = new URL(this.downloadUrl);

                if (this.data.size() != this.threads.length) {

                    this.data.clear();

                    for (int i = 0; i < this.threads.length; i++) {

                        this.data.put(i + 1, 0);

                    }

                    this.downloadedSize = 0;

                }

                for (int i = 0; i < this.threads.length; i++) {

                    int downloadedLength = this.data.get(i + 1);

                    if (downloadedLength < this.block && this.downloadedSize < this.fileSize) {

                        this.threads[i] = new DownloadThread(this, url, this.saveFile, this.block, this.data.get(i + 1), i + 1);

                        this.threads[i].setPriority(7);

                        this.threads[i].start();

                    } else {

                        this.threads[i] = null;

                    }

                }

                this.fileService.delete(this.downloadUrl);

                this.fileService.save(this.downloadUrl, this.data);

                boolean notFinished = true;

                while (notFinished) {

                    Thread.sleep(900);

                    notFinished = false;

                    for (int i = 0; i < this.threads.length; i++) {

                        if (this.threads[i] != null && !this.threads[i].isFinished()) {

                            notFinished = true;

                            if (this.threads[i].getDownloadedLength() == -1) {

                                this.threads[i] = new DownloadThread(this, url, this.saveFile, this.block, this.data.get(i + 1), i + 1);

                                this.threads[i].setPriority(7);

                                this.threads[i].start();

                            }

                        }

                    }

                    if (listener != null)

                        listener.onDownloadSize(this.downloadedSize);

                }

                if (downloadedSize == this.fileSize)

                    this.fileService.delete(this.downloadUrl);

            } catch (Exception e) {

                print(e.toString());

                throw new Exception("File downloads error");

            }

            return this.downloadedSize;

        }

        public static Map<String, String> getHttpResponseHeader(HttpURLConnection http) {

            Map<String, String> header = new LinkedHashMap<String, String>();

            for (int i = 0;; i++) {

                String mine = http.getHeaderField(i);

                if (mine == null)

                    break;

                header.put(http.getHeaderFieldKey(i), mine);

            }

            return header;

        }

        public static void printResponseHeader(HttpURLConnection http) {

            Map<String, String> header = getHttpResponseHeader(http);

            for (Map.Entry<String, String> entry : header.entrySet()) {

                String key = entry.getKey() != null ? entry.getKey() + ":" : "";

                print(key + entry.getValue());

            }

        }

        private static void print(String msg) {

            Log.i(TAG, msg);

        }

    }

    Android内存处理类

    public class FileService {

        private DBOpenHelper openHelper;

        public FileService(Context context) {

            openHelper = new DBOpenHelper(context);

        }

        @SuppressLint("UseSparseArrays")

        public Map<Integer, Integer> getData(String path) {

            SQLiteDatabase db = openHelper.getReadableDatabase();

            Cursor cursor = db.rawQuery("select threadid, downlength from filedownlog where downpath=?", new String[]{path});

            Map<Integer, Integer> data = new HashMap<Integer, Integer>();

            while (cursor.moveToNext()) {

                data.put(cursor.getInt(0), cursor.getInt(1));

                data.put(cursor.getInt(cursor.getColumnIndexOrThrow("threadid")), cursor.getInt(cursor.getColumnIndexOrThrow("downlength")));

            }

            cursor.close();

            db.close();

            return data;

        }

        public void save(String path, Map<Integer, Integer> map) {

            SQLiteDatabase db = openHelper.getWritableDatabase();

            db.beginTransaction();

            try {

                for (Map.Entry<Integer, Integer> entry : map.entrySet()) {

                    db.execSQL("insert into filedownlog(downpath, threadid, downlength) values(?,?,?)", new Object[]{path, entry.getKey(), entry.getValue()});

                }

                db.setTransactionSuccessful();

            } finally {

                db.endTransaction();

            }

            db.close();

        }

        public void update_tyc(String path, int threadId, int pos) {

            SQLiteDatabase db = openHelper.getWritableDatabase();

            db.execSQL("update filedownlog set downlength=? where downpath=? and threadid=?", new Object[]{pos, path, threadId});

            db.close();

        }

        public void update(String path, Map<Integer, Integer> map) {

            SQLiteDatabase db = openHelper.getWritableDatabase();

            db.beginTransaction();

            try {

                for (Map.Entry<Integer, Integer> entry : map.entrySet()) {

                    db.execSQL("update filedownlog set downlength=? where downpath=? and threadid=?", new Object[]{entry.getValue(), path, entry.getKey()});

                }

                db.setTransactionSuccessful();

            } finally {

                db.endTransaction();

            }

            db.close();

        }

        public void delete(String path) {

            SQLiteDatabase db = openHelper.getWritableDatabase();

            db.execSQL("delete from filedownlog where downpath=?", new Object[]{path});

            db.close();

        }

    }

    数据辅助类

    public class DBOpenHelper extends SQLiteOpenHelper {

        private static final String DBNAME = "eric.db";

        private static final int VERSION = 1;

        public DBOpenHelper(Context context) {

            super(context, DBNAME, null, VERSION);

        }

        @Override

        public void onCreate(SQLiteDatabase db) {

            db.execSQL("CREATE TABLE IF NOT EXISTS filedownlog (id integer primary key autoincrement, downpath varchar(100), threadid INTEGER, downlength INTEGER)");

        }

        @Override

        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

            db.execSQL("DROP TABLE IF EXISTS filedownlog");

            onCreate(db);

        }

    }

    六丶提升学习

    这里只是入门简单的用HttpURLConnection实现,现在流行的RxRetrofit大神封装见

    RxRetrofit - 终极封装 - 深入浅出 & 断点续传

    七丶参考文章

    深入理解JAVA I/O系列四:RandomAccessFile

    Android 多线程断点续传下载

    源码下载:

    https://github.com/JinBoy23520/CoderToDeveloperByTCLer

    ---------------------

    作者:天一方蓝

    来源:CSDN

    原文:https://blog.csdn.net/dt235201314/article/details/80911932

    版权声明:本文为博主原创文章,转载请附上博文链接!

    相关文章

      网友评论

          本文标题:RandomAccessFile分析和demo

          本文链接:https://www.haomeiwen.com/subject/nwwqeqtx.html