国产片侵犯亲女视频播放_亚洲精品二区_在线免费国产视频_欧美精品一区二区三区在线_少妇久久久_在线观看av不卡

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|JAVA教程|ASP教程|

服務器之家 - 編程語言 - JAVA教程 - 很簡單的Java斷點續傳實現原理

很簡單的Java斷點續傳實現原理

2020-05-28 13:46ghost_Programmer JAVA教程

這篇文章主要以實例的方式為大家詳細介紹了簡單的Java斷點續傳實現原理,感興趣的小伙伴們可以參考一下

原理解析

在開發當中,“斷點續傳”這種功能很實用和常見,聽上去也是比較有“逼格”的感覺。所以通常我們都有興趣去研究研究這種功能是如何實現的?
以Java來說,網絡上也能找到不少關于實現類似功能的資料。但是呢,大多數都是舉個Demo然后貼出源碼,真正對其實現原理有詳細的說明很少。
于是我們在最初接觸的時候,很可能就是直接Crtl + C/V代碼,然后搗鼓搗鼓,然而最終也能把效果弄出來。但初學時這樣做其實很顯然是有好有壞的。
好處在于,源碼很多,解釋很少;如果我們肯下功夫,針對于別人貼出的代碼里那些自己不明白的東西去查資料,去鉆研。最終多半會收獲頗豐。
壞處也很明顯:作為初學者,面對一大堆的源碼,感覺好多東西都很陌生,就很容易望而生畏。即使最終大致了解了用法,但也不一定明白實現原理。

我們今天就一起從最基本的角度切入,來看看所謂的“斷點續傳”這個東西是不是真的如此“高逼格”。
其實在接觸一件新的“事物”的時候,將它擬化成一些我們本身比較熟悉的事物,來參照和對比著學習。通常會事半功倍。
如果我們剛接觸“斷點續傳”這個概念,肯定很難說清楚個一二三。那么,“玩游戲”我們肯定不會陌生。

OK,那就假設我們現在有一款“通關制的RPG游戲”。想想我們在玩這類游戲時通常會怎么做?
很明顯,第一天我們浴血奮戰,大殺四方,假設終于來到了第四關。雖然激戰正酣,但一看墻上的時鐘,已經凌晨12點,該睡覺了。
這個時候就很尷尬了,為了能夠在下一次玩的時候,順利接軌上我們本次游戲的進度,我們應該怎么辦呢?
很簡單,我們不關掉游戲,直接去睡覺,第二天再接著玩唄。這樣是可以,但似乎總覺著有哪里讓人不爽。
那么,這個時候,如果這個游戲有一個功能叫做“存檔”,就很關鍵了。我們直接選擇存檔,輸入存檔名“第四關”,然后就可以關閉游戲了。
等到下次進行游戲時,我們直接找到“第四關”這個存檔,然后進行讀檔,就可以接著進行游戲了。

這個時候,所謂的“斷點續傳”就很好理解了。我們順著我們之前“玩游戲”的思路來理一下:
假設,現在有一個文件需要我們進行下載,當我們下載了一部分的時候,出現情況了,比如:電腦死機、沒電、網絡中斷等等。
其實這就好比我們之前玩游戲玩著玩著,突然12點需要去睡覺休息了是一個道理。OK,那么這個時候的情況是:

 • 如果游戲不能存檔,那么則意味著我們下次游戲的時候,這次已經通過的4關的進度將會丟失,無法接檔。
 • 對應的,如果“下載”的行為無法記錄本次下載的一個進度。那么,當我們再次下載這個文件也就只能從頭來過。
 話到這里,其實我們已經發現了,對于我們以上所說的行為,關鍵就在于一個字“”!
而我們要實現讓一種斷開的行為“續”起來的目的,關鍵就在于要有“介質”能夠記錄和讀取行為出現”中斷”的這個節點的信息。

轉化到編程世界

實際上這就是“斷點續傳”最最基礎的原理,用大白話說就是:我們要在下載行為出現中斷的時候,記錄下中斷的位置信息,然后在下次行為中讀取。
有了這個位置信息之后,想想我們該怎么做。是的,很簡單,在新的下載行為開始的時候,直接從記錄的這個位置開始下載內容,而不再從頭開始。
好吧,我們用大白話掰扯了這么久的原理,開始覺得無聊了。那么我們現在最后總結一下,然后就來看看我們應該怎么把原理轉換到編程世界中去。

 • 當“上傳(下載)的行為”出現中斷,我們需要記錄本次上傳(下載)的位置(position)。
 • 當“續”這一行為開始,我們直接跳轉到postion處繼續上傳(下載)的行為。 

顯然問題的關鍵就在于所謂的“position”,以我們舉的“通關游戲來說”,可以用“第幾關”來作為這個position的單位。
那么轉換到所謂的“斷點續傳”,我們該使用什么來衡量“position”呢?很顯然,回歸二進制,因為這里的本質無非就是文件的讀寫。

那么剩下的工作就很簡單了,先是記錄position,這似乎都沒什么值得說的,因為只是數據的持久化而已(內存,文件,數據庫),我們有很多方式。

另一個關鍵在于當“續傳”的行為開始,我們需要需要從上次記錄的position位置開始讀寫操作,所以我們需要一個類似于“指針”功能的東西。
我們當然也可以自己想辦法去實現這樣一個“指針”,但高興地是,Java已經為我們提供了這樣的一個類,那就是RandomAccessFile。
這個類的功能從名字就很直觀的體現了,能夠隨機的去訪問文件。我們看一下API文檔中對該類的說明:

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

如果隨機訪問文件以讀取/寫入模式創建,則輸出操作也可用;輸出操作從文件指針開始寫入字節,并隨著對字節的寫入而前移此文件指針。

寫入隱含數組的當前末尾之后的輸出操作導致該數組擴展。該文件指針可以通過 getFilePointer 方法讀取,并通過 seek 方法設置。

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

實例演示

既然是針對于文件的“斷點續傳”,那么很明顯,我們先搞一個文件出來。也許音頻文件,圖像文件什么的看上去會更上檔次一點。
但我們已經說了,在計算機大兄弟眼中,它們最終都將回歸“二進制”。所以我們這里就創建一個簡單的”txt”文件,因為txt更利于理解。

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

很簡單的Java斷點續傳實現原理

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

很簡單的Java斷點續傳實現原理

我們看到,文件現在的大小是6個字節。這也就是為什么我們說,所有的東西到最后還是離不開“二進制”。
是的,我們都明白,因為我們輸入了6個英文字母,而1個英文字母將占據的存儲空間是1個字節(即8個比特位)。
目前為止,我們看到的都很無聊,因為這基本等于廢話,稍微有計算機常識的人都知道這些知識。別著急,我們繼續。

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

反復的強調原理,實際上是因為只要弄明白了原理,剩下的就只是招式而已了。這就就像武俠小說里的“九九歸一”大法一樣,最高境界就是回歸本源。
任何復雜的事物,只要明白其原理,我們就能將其剝離,還原為一個個簡單的事物。同理,一系列簡單的事物,經過邏輯組合,就形成了復雜的事物。

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

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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個英文字母)。

現在,我們就來模擬這個讀寫中斷的行為,我們將之前的代碼完善如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
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文件,打開它發現內容如下:

很簡單的Java斷點續傳實現原理

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

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

很簡單的Java斷點續傳實現原理

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

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 国产精品一区久久 | 久久久久久久久一区二区三区 | 亚洲成人第一网站 | 欧美综合一区二区三区 | 欧美视频中文字幕 | 国产无套丰满白嫩对白 | 日韩欧美在线观看一区二区 | 一区二区国产精品 | 欧美成人一区二区三区 | 999国产在线观看 | 午夜操操操 | 亚洲日本中文字幕 | 亚洲国产福利一区 | 国产精品久久久久无码av | 国产探花在线精品一区二区 | 国产超碰在线观看 | 日韩欧美精品在线 | 欧美在线不卡 | 国产精品亚洲综合 | 色永久 | 日本久久免费 | 最好看的2019年中文在线观看 | 国产激情视频 | 国产人久久人人人人爽 | 亚洲精品一区二区三区在线 | 不卡黄色 | 成人羞羞视频免费 | 欧美在线高清 | 黄片毛片在线 | 国产中文字幕一区 | 亚洲精品视频在线 | 久久亚洲黄色 | 午夜国产视频 | 亚洲人免费| 中文字幕亚洲一区 | 成人片免费视频 | 国产欧美久久久久久 | 一区二区免费视频 | 日本不卡一区二区三区在线观看 | 在线观看欧美日韩 | 黄色国产视频 |