java 斷點續傳

原理解析java

在開發當中,「斷點續傳」這種功能很實用和常見,聽上去也是比較有「逼格」的感受。因此一般咱們都有興趣去研究研究這種功能是如何實現的?
以Java來講,網絡上也能找到很多關於實現相似功能的資料。可是呢,大多數都是舉個Demo而後貼出源碼,真正對其實現原理有詳細的說明不多。
因而咱們在最初接觸的時候,極可能就是直接Crtl + C/V代碼,而後搗鼓搗鼓,然而最終也能把效果弄出來。但初學時這樣作其實很顯然是有好有壞的。
好處在於,源碼不少,解釋不多;若是咱們肯下功夫,針對於別人貼出的代碼裏那些本身不明白的東西去查資料,去鑽研。最終多半會收穫頗豐。
壞處也很明顯:做爲初學者,面對一大堆的源碼,感受好多東西都很陌生,就很容易望而生畏。即便最終大體瞭解了用法,但也不必定明白實現原理。數據庫

咱們今天就一塊兒從最基本的角度切入,來看看所謂的「斷點續傳」這個東西是否是真的如此「高逼格」。
其實在接觸一件新的「事物」的時候,將它擬化成一些咱們自己比較熟悉的事物,來參照和對比着學習。一般會事半功倍。
若是咱們剛接觸「斷點續傳」這個概念,確定很難說清楚個一二三。那麼,「玩遊戲」咱們確定不會陌生。編程

OK,那就假設咱們如今有一款「通關制的RPG遊戲」。想一想咱們在玩這類遊戲時一般會怎麼作?
很明顯,第一天咱們浴血奮戰,大殺四方,假設終於來到了第四關。雖然激戰正酣,但一看牆上的時鐘,已經凌晨12點,該睡覺了。
這個時候就很尷尬了,爲了可以在下一次玩的時候,順利接軌上咱們本次遊戲的進度,咱們應該怎麼辦呢?
很簡單,咱們不關掉遊戲,直接去睡覺,次日再接着玩唄。這樣是能夠,但彷佛總覺着有哪裏讓人不爽。
那麼,這個時候,若是這個遊戲有一個功能叫作「存檔」,就很關鍵了。咱們直接選擇存檔,輸入存檔名「第四關」,而後就能夠關閉遊戲了。
等到下次進行遊戲時,咱們直接找到「第四關」這個存檔,而後進行讀檔,就能夠接着進行遊戲了。數組

這個時候,所謂的「斷點續傳」就很好理解了。咱們順着咱們以前「玩遊戲」的思路來理一下:
假設,如今有一個文件須要咱們進行下載,當咱們下載了一部分的時候,出現狀況了,好比:電腦死機、沒電、網絡中斷等等。
其實這就比如咱們以前玩遊戲玩着玩着,忽然12點須要去睡覺休息了是一個道理。OK,那麼這個時候的狀況是:服務器

 • 若是遊戲不能存檔,那麼則意味着咱們下次遊戲的時候,此次已經經過的4關的進度將會丟失,沒法接檔。
 • 對應的,若是「下載」的行爲沒法記錄本次下載的一個進度。那麼,當咱們再次下載這個文件也就只能從頭來過。
 話到這裏,其實咱們已經發現了,對於咱們以上所說的行爲,關鍵就在於一個字「」!
而咱們要實現讓一種斷開的行爲「續」起來的目的,關鍵就在於要有「介質」可以記錄和讀取行爲出現」中斷」的這個節點的信息。網絡

轉化到編程世界dom

實際上這就是「斷點續傳」最最基礎的原理,用大白話說就是:咱們要在下載行爲出現中斷的時候,記錄下中斷的位置信息,而後在下次行爲中讀取。
有了這個位置信息以後,想一想咱們該怎麼作。是的,很簡單,在新的下載行爲開始的時候,直接從記錄的這個位置開始下載內容,而再也不從頭開始。
好吧,咱們用大白話掰扯了這麼久的原理,開始以爲無聊了。那麼咱們如今最後總結一下,而後就來看看咱們應該怎麼把原理轉換到編程世界中去。post

 • 當「上傳(下載)的行爲」出現中斷,咱們須要記錄本次上傳(下載)的位置(position)。
 • 當「續」這一行爲開始,咱們直接跳轉到postion處繼續上傳(下載)的行爲。 學習

顯然問題的關鍵就在於所謂的「position」,以咱們舉的「通關遊戲來講」,能夠用「第幾關」來做爲這個position的單位。
那麼轉換到所謂的「斷點續傳」,咱們該使用什麼來衡量「position」呢?很顯然,迴歸二進制,由於這裏的本質無非就是文件的讀寫。測試

那麼剩下的工做就很簡單了,先是記錄position,這彷佛都沒什麼值得說的,由於只是數據的持久化而已(內存,文件,數據庫),咱們有不少方式。

另外一個關鍵在於當「續傳」的行爲開始,咱們須要須要從上次記錄的position位置開始讀寫操做,因此咱們須要一個相似於「指針」功能的東西。
咱們固然也能夠本身想辦法去實現這樣一個「指針」,但高興地是,Java已經爲咱們提供了這樣的一個類,那就是RandomAccessFile。
這個類的功能從名字就很直觀的體現了,可以隨機的去訪問文件。咱們看一下API文檔中對該類的說明:

此類的實例支持對隨機訪問文件的讀取和寫入。隨機訪問文件的行爲相似存儲在文件系統中的一個大型 byte 數組。

若是隨機訪問文件以讀取/寫入模式建立,則輸出操做也可用;輸出操做從文件指針開始寫入字節,並隨着對字節的寫入而前移此文件指針。

寫入隱含數組的當前末尾以後的輸出操做致使該數組擴展。該文件指針能夠經過 getFilePointer 方法讀取,並經過 seek 方法設置。 

看完API說明,咱們笑了,是的,這不正是咱們要的嗎?那好吧,咱們磨刀磨了這麼久了,還不去砍砍柴嗎?

實例演示

既然是針對於文件的「斷點續傳」,那麼很明顯,咱們先搞一個文件出來。也許音頻文件,圖像文件什麼的看上去會更上檔次一點。
但咱們已經說了,在計算機大兄弟眼中,它們最終都將回歸「二進制」。因此咱們這裏就建立一個簡單的」txt」文件,由於txt更利於理解。

咱們在D盤的根目錄下建立一個名爲」test.txt」的文件,文件內容很簡單,如圖所示:

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

咱們看到,文件如今的大小是6個字節。這也就是爲何咱們說,全部的東西到最後仍是離不開「二進制」。
是的,咱們都明白,由於咱們輸入了6個英文字母,而1個英文字母將佔據的存儲空間是1個字節(即8個比特位)。
目前爲止,咱們看到的都很無聊,由於這基本等於廢話,稍微有計算機常識的人都知道這些知識。彆着急,咱們繼續。

在Java中對一個文件進行讀寫操做很簡單。假設如今的需求若是是「把D盤的這個文件寫入到E盤」,那麼咱們會提起鍵盤,啪啪啪啪,搞定!
但其實所謂的文件的「上傳(下載)」不是也沒什麼不一樣嗎?區別就僅僅在於行爲由「僅僅在本機之間」轉變成了」本機與服務器之間」的文件讀寫。
這時咱們會說,「別逼逼了,這些誰都知道,‘斷點續傳'呢?「,其實到了這裏也已經很簡單了,咱們再次明確,斷點續傳要作的無非就是:
前一次讀寫行爲若是出現中斷,請記錄下這次讀寫完成的文件內容的位置信息;當「續傳開始」則直接將指針移到此處,開始繼續讀寫操做。

反覆的強調原理,其實是由於只要弄明白了原理,剩下的就只是招式而已了。這就就像武俠小說裏的「九九歸一」大法同樣,最高境界就是迴歸本源。
任何複雜的事物,只要明白其原理,咱們就能將其剝離,還原爲一個個簡單的事物。同理,一系列簡單的事物,通過邏輯組合,就造成了複雜的事物。

下面,咱們立刻就將回歸混沌,以最基本的形式模擬一次「斷點續傳」。在這裏咱們連服務器的代碼都不去寫了,直接經過一個本地測試類搞定。
咱們要實現的效果很簡單:將在D盤的」test.txt」文件寫入到E盤當中,但中途咱們會模擬一次」中斷」行爲,而後在從新繼續上傳,最終完成整個過程。
也就是說,咱們這裏將會把「D盤」視做一臺電腦,而且直接將」E盤」視做一臺服務器。那麼這樣咱們甚至都再也不與http協議扯上半毛錢關係了,(固然實際開發咱們確定是仍是得與它扯上關係的 ^<^),從而只關心最基本的文件讀寫的」斷」和」續」的原理是怎麼樣的。

爲了經過對比加深理解,咱們先來寫一段正常的代碼,即正常讀寫,不發生中斷:

public class Test {
 
 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) {
    System.out.println("write data...");
    fos.write(buf);
   }
  } catch (FileNotFoundException e) {
   System.out.println("指定文件不存在");
  } catch (IOException e) {
   // TODO: handle exception
  } finally {
   try {
    // 關閉輸入輸出流
    if (fis != null)
     fis.close();
 
    if (fos != null)
     fos.close();
   } catch (IOException e) {
    e.printStackTrace();
   }
 
  }
 }
}

 

該段代碼運行,咱們就會發如今E盤中已經成功拷貝了一份「test.txt」。這段代碼很簡單,惟一稍微說一下就是:
咱們看到咱們將buf,即緩衝區 設置的大小是1,這其實就表明咱們每次read,是讀取一個字節的數據(即1個英文字母)。

如今,咱們就來模擬這個讀寫中斷的行爲,咱們將以前的代碼完善以下:

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) {
   // TODO: handle exception
  } 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 (InterruptedException e) {
   // TODO Auto-generated catch block
   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) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } catch (IOException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
 }
 
}
 
class FileAccessException extends Exception {
 
}

 

總結一下,咱們在此次改動當中都作了什麼工做:

 • 首先,咱們定義了一個變量position,記錄在發生中斷的時候,已完成讀寫的位置。(這是爲了方便,實際來講確定應該講這個值存到文件或者數據庫等進行持久化)
 而後在文件讀寫的while循環中,咱們去模擬一箇中斷行爲的發生。這裏是當targetFile的文件長度爲3個字節則模擬拋出一個咱們自定義的異常。(咱們能夠想象爲實際下載中,已經上傳(下載)了」x」個字節的內容,這個時候網絡中斷了,那麼咱們就在網絡中斷拋出的異常中將」x」記錄下來)。
 • 剩下的就若是咱們以前說的同樣,在「續傳」行爲開始後,經過RandomAccessFile類來包裝咱們的文件,而後經過seek將指針指定到以前發生中斷的位置進行讀寫就搞定了。
(實際的文件下載上傳,咱們固然須要將保存的中斷值上傳給服務器,這個方式一般爲httpConnection.setRequestProperty(「RANGE」,」bytes=x」);)

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

沒錯,這個時候咱們發現內容只有「abc」。這是在咱們預料之內的,由於咱們的程序模擬在文件上傳了3個字節的時候發生了中斷。

Ok,咱們靜靜的等待10秒鐘過去,而後再點開該文件,看看是否可以成功:

經過截圖咱們發現內容的確已經變成了「abc」,由此也就完成了續傳。

轉載於http://www.jb51.net/article/88707.htm

相關文章
相關標籤/搜索