java 文件斷點續傳實現原理


出處: Java斷點續傳實現原理很簡單

 

關鍵字: RandomAccessFile

 

一、作用:

         隨機流(RandomAccessFile)不屬於IO流,支持對文件的讀取和寫入隨機訪問。

二、隨機訪問文件原理:       

         首先把隨機訪問的文件對象看作存儲在文件系統中的一個大型 byte 數組,然后通過指向該 byte 數組的光標或索引(即:文件指針 FilePointer)在該數組任意位置讀取或寫入任意數據。

三、相關方法說明:

       1、對象聲明:RandomAccessFile raf = newRandomAccessFile(File file, String mode);

            其中參數 mode 的值可選 "r":可讀,"w" :可寫,"rw":可讀性;

       2、獲取當前文件指針位置:int RandowAccessFile.getFilePointer();

       3、改變文件指針位置(相對位置、絕對位置):

            1> 絕對位置:RandowAccessFile.seek(int index);

            2> 相對位置:RandowAccessFile.skipByte(int step);         相對當前位置

       4、給寫入文件預留空間:RandowAccessFile.setLength(long len);

 

斷點續傳實現原理:

  1)下載斷開的時候,記錄文件斷點的位置position;

  2)繼續下載的時候,通過RandomAccessFile找到之前的position位置開始下載

 

實際操作:

  我們在D盤的根目錄下創建一個名為”test.txt”的文件,文件內容很簡單,如圖所示: 

  沒錯,我們輸入的內容就是簡單的6個英語字母。然后我們右鍵→屬性: 

  我們要實現的效果很簡單:將在D盤的”test.txt”文件寫入到E盤當中,但中途我們會模擬一次”中斷”行為,然后在重新繼續上傳,最終完成整個過程。
  也就是說,我們這里將會把“D盤”視作一台電腦,並且直接將”E盤”視作一台服務器。那么這樣我們甚至都不再與http協議扯上半毛錢關系了,(當然實際開發我們肯定是還是得與它扯上關系的 ^<^),從而只關心最基本的文件讀寫的”斷”和”續”的原理是怎么樣的。

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;

public class Test {
    private static int position = -1;

    public static void main(String[] args) {
        // 源文件與目標文件
        File sourceFile = new File("D:/", "test.txt");
        File targetFile = new File("E:/", "test.txt");
        // 輸入輸出流
        FileInputStream fis = null;
        FileOutputStream fos = null;
        // 數據緩沖區
        byte[] buf = new byte[1];

        try {
            fis = new FileInputStream(sourceFile);
            fos = new FileOutputStream(targetFile);
            // 數據讀寫
            while (fis.read(buf) != -1) {
                fos.write(buf);
                // 當已經上傳了3字節的文件內容時,模擬網絡中斷了,拋出異常
                if (targetFile.length() == 3) {
                    position = 3;
                    throw new FileAccessException();
                }
            }

        } catch (FileAccessException e) {
            keepGoing(sourceFile, targetFile, position);
        } catch (FileNotFoundException e) {
            System.out.println("指定文件不存在");
        } catch (IOException e) {

        } finally {
            try {
                // 關閉輸入輸出流
                if (fis != null)
                    fis.close();

                if (fos != null)
                    fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private static void keepGoing(File source, File target, int position) {
        try {
            Thread.sleep(10000);
        } catch (Exception e) {
            e.printStackTrace();
        }

        try {
            RandomAccessFile readFile = new RandomAccessFile(source, "rw");
            RandomAccessFile writeFile = new RandomAccessFile(target, "rw");

            readFile.seek(position);
            writeFile.seek(position);

            // 數據緩沖區
            byte[] buf = new byte[1];
            // 數據讀寫
            while (readFile.read(buf) != -1) {
                writeFile.write(buf);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

class FileAccessException extends Exception {
}

總結一下,我們在這次改動當中都做了什么工作:

  1. 首先,我們定義了一個變量position,記錄在發生中斷的時候,已完成讀寫的位置。(這是為了方便,實際來說肯定應該講這個值存到文件或者數據庫等進行持久化)
  2. 然后在文件讀寫的while循環中,我們去模擬一個中斷行為的發生。這里是當targetFile的文件長度為3個字節則模擬拋出一個我們自定義的異常。(我們可以想象為實際下載中,已經上傳(下載)了”x”個字節的內容,這個時候網絡中斷了,那么我們就在網絡中斷拋出的異常中將”x”記錄下來)。
  3. 剩下的就如果我們之前說的一樣,在“續傳”行為開始后,通過RandomAccessFile類來包裝我們的文件,然后通過seek將指針指定到之前發生中斷的位置進行讀寫就搞定了。(實際的文件下載上傳,我們當然需要將保存的中斷值上傳給服務器,這個方式通常為httpConnection.setRequestProperty(“RANGE”,”bytes=x”);)

在我們這段代碼,開啟”續傳“行為,即keepGoing方法中:我們起頭讓線程休眠10秒鍾,這正是為了讓我們運行程序看到效果。
現在我們運行程序,那么文件就會開啟“由D盤上傳到E盤的過程”,我們首先點開E盤,會發現的確多了一個test.txt文件,打開它發現內容如下:

  沒錯,這個時候我們發現內容只有“abc”。這是在我們預料以內的,因為我們的程序模擬在文件上傳了3個字節的時候發生了中斷。

Ok,我們靜靜的等待10秒鍾過去,然后再點開該文件,看看是否能夠成功:

  通過截圖我們發現內容的確已經變成了“abc”,由此也就完成了續傳。

 

 


 

java多線程下載文件原理解析+案例


原理解析: 利用RandomAccessFile在本地創建一個隨機訪問文件,文件大小和服務器要下載的文件大小相同。 根據線程的數量(假設有三個線程),服務器的文件三等分,並把我們在本地創建的文件同樣三等分,每個線程下載自己負責的部分,到相應的位置即可。

示例圖:

import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;

public class MutilDownload {
    private static String path = "http://192.168.80.85:8080/test.doc";
    private static final int threadCount = 3;

    public static void main(String[] args) {
        try {
            URL url = new URL(path);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("GET");
            conn.setConnectTimeout(5000);
            int responseCode = conn.getResponseCode();
            if (responseCode == 200) {
                int contentLength = conn.getContentLength();
                System.out.println("length" + contentLength);

                RandomAccessFile rafAccessFile = new RandomAccessFile("test.doc", "rw");
                rafAccessFile.setLength(contentLength);

                int blockSize = contentLength / threadCount;
                for (int i = 0; i < threadCount; i++) {
                    int startIndex = i * blockSize; //每個現成下載的開始位置
                    int endIndex = (i + 1) * blockSize - 1;// 每個線程的結束位置
                    if (i == threadCount - 1) {
                        //最后一個線程
                        endIndex = contentLength - 1;
                    }

                    new DownloadThread(startIndex, endIndex, i).start();
                }

            }
        } catch (Exception e) {

        }
    }

    private static class DownloadThread extends Thread {
        private int startIndex;
        private int endIndex;
        private int threadId;

        public DownloadThread(int startIndex, int endIndex, int threadId) {
            this.startIndex = startIndex;
            this.endIndex = endIndex;
            this.threadId = threadId;
        }

        @Override
        public void run() {
            try {
                URL url = new URL(path);
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                conn.setRequestMethod("GET");
                conn.setConnectTimeout(5000);
                conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex); //固定寫法,請求部分資源
                int responseCode = conn.getResponseCode();  // 206表示請求部分資源
                if (responseCode == 206) {
                    RandomAccessFile rafAccessFile = new RandomAccessFile("test.doc", "rw");
                    rafAccessFile.seek(startIndex);
                    InputStream is = conn.getInputStream();
                    int len = -1;
                    byte[] buffer = new byte[1024];
                    while ((len = is.read(buffer)) != -1) {
                        rafAccessFile.write(buffer, 0, len);
                    }
                    rafAccessFile.close();

                    System.out.println("線程" + threadId + "下載完成");
                }
            } catch (Exception e) {

            }
        }
    }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM