欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解Java多線程和IO流的應用

 更新時間:2023年04月11日 10:05:31   作者:CrazyDragon_King  
這篇文章主要介紹了詳解Java多線程和IO流的應用,無論是本地文件復制,還是網(wǎng)絡多線程下載,對于流的使用都是一樣的,需要的朋友可以參考下

Java多線程和流的應用

最近看到了一個例子,是使用多線程的方式下載文件,感覺很有趣,探索了一下,并且嘗試了使用多線程進行本地復制文件。寫完之后,發(fā)現(xiàn)了這兩個其實很相似,無論是本地文件復制,還是網(wǎng)絡多線程下載,對于流的使用都是一樣的。對于本地文件系統(tǒng)來說,輸入流就是從本地文件系統(tǒng)的一個文件來獲取,對于網(wǎng)絡資源來說,是從遠處服務器上的一個文件來獲取。

注: 雖然這個多線程下載的代碼,很多人都寫過了,不過應該不是所有人都能理解吧,我這里就再寫一遍,哈。

使用多線程的一個顯而易見的好處就是:利用空閑的 CPU,加快速度。 但是注意不是線程越多越好,雖然好像n個線程一起下載,每個線程下載一小部分,下載時間就會變?yōu)?/n 了。這是很淺顯的認識,就好像一個人蓋一個房子需要100天,難道10000個人,只需要1/10 天一樣了?(這是一個夸張的說法,哈哈!)

線程之間的切換也是需要系統(tǒng)開銷的,線程的數(shù)目還是要控制在一個合理的范圍內才行。

RamdomAccessFile

這個類比較獨特,它即可以從文件中讀取數(shù)據(jù),也可以向文件中寫入數(shù)據(jù)。但是它不是 OutputStream 和 InputStream 的子類,它是實現(xiàn)了這兩個接口 DataOutput 、DataInput 的一個類。

API 中的介紹:

該類的實例支持讀取和寫入隨機訪問文件。 隨機訪問文件的行為類似于存儲在文件系統(tǒng)中的大量字節(jié)。 有一種游標,或索引到隱含的數(shù)組,稱為文件指針 ; 輸入操作讀取從文件指針開始的字節(jié),并使文件指針超過讀取的字節(jié)。 如果在讀/寫模式下創(chuàng)建隨機訪問文件,則輸出操作也可用; 輸出操作從文件指針開始寫入字節(jié),并將文件指針提前到寫入的字節(jié)。 寫入隱式數(shù)組的當前端的輸出操作會導致擴展數(shù)組。 文件指針可以通過讀取getFilePointer方法和由設置seek方法。

所以,這個類最重要的就是 seek 方法了,使用 seek 方法,可以控制寫入的位置,所以實現(xiàn)多線程就容易的多了。因此,無論是本地文件復制,還是網(wǎng)絡多線程下載都需要這個類的作用。

具體思路是: 首先使用 RandomAccessFile 創(chuàng)建一個 File 對象,然后設置這個文件的大小。(對的,它可以直接設置文件的大小。)將這個文件設置成和要復制或下載的文件一樣的。(雖然我們并沒有向這個文件中寫入數(shù)據(jù),但是這個文件已經(jīng)創(chuàng)建了。)將文件分為若干部分,使用線程復制或者下載每一部分的內容。

這有點類似文件的覆蓋,如果一個已經(jīng)存在的文件,從這個文件的頭部開始寫入數(shù)據(jù),一直寫到文件的尾部,那么原來的文件就不存在了,變成了寫入的新文件。

設置文件大?。?/p>

	private static void createOutputFile(File file, long length) throws IOException {
		try (   
			RandomAccessFile raf = new RandomAccessFile(file, "rw")){
			raf.setLength(length);
		}
	}

用圖片來說明: 這個圖表示一個 8191 字節(jié)大小的文件: 每一個部分大小是:8191 / 4 = 2047字節(jié)

將這個文件的分為四個部分,每一個部分使用一個線程進行復制或下載,其中每一個箭頭代表一個線程的開始下載位置。我特意將最后一個部分,沒有設置為 1024 byte,這是因為文件很少是正好能被 1024 byte 整除的。(之所以使用 1024 byte,是因為我每次會讀取 1024 byte,如果讀取到 1024 byte的話, 否則寫入讀取到的相應字節(jié)數(shù))。

按照這個示意圖,每一個線程下載 2047 byte,那么總共下載的字節(jié)數(shù)是:2047 * 4 = 8188 字節(jié) < 8191 字節(jié)(文件的總大小) 所以這就產(chǎn)生了一個問題,下載的字節(jié)數(shù)少于總字節(jié)數(shù),這就是問題了,所以必須要下載的字節(jié)數(shù)大于總字節(jié)數(shù)才行。(多了沒有關系,因為多下載的部分,會被后面的給覆蓋掉,不會產(chǎn)生了問題。

所以每個部分的大小應該是:8191 / 4 + 1 = 2048 字節(jié)。(這樣四部分的大小相加是超過總大小的,不會發(fā)生數(shù)據(jù)的丟失問題。)

所以,這里這個加 1 是很有必要的。

long size = len / FileCopyUtil.THREAD_NUM + 1;

在這里插入圖片描述

每個線程下載完成的位置(右邊) 每個線程,只復制下載自己的那部分,所以不需要全部下載完所有的內容,所以讀取文件數(shù)據(jù)并寫入文件的部分,會多加一個判斷。

這里增加一個計數(shù)器:curlen。它表示是當前復制或者下載的長度,然后每次讀取后和 size(每部分的大小)進行比較,如果 curlen 大于 size 就表示相應的部分下載完成了(當然了,這些都要在數(shù)據(jù)沒有讀取完的條件下判斷)。

try (
		BufferedInputStream bis = new BufferedInputStream(new FileInputStream(targetFile));
		RandomAccessFile raf = new RandomAccessFile(outputFile, "rw")){
		bis.skip(position);  
		raf.seek(position);  
		int hasRead = 0;
		byte[] b = new byte[1024];
		long curlen = 0;
		while(curlen < size && (hasRead = bis.read(b)) != -1) {
			raf.write(b, 0, hasRead);
			curlen += (long)hasRead;
		}
		System.out.println(n+" "+position+" "+curlen+" "+size);
	} catch (IOException e) {
		e.printStackTrace();
}

在這里插入圖片描述

還有需要注意的是,每個線程下載的時候都要: 1. 輸出流設置文件指針的位置。 2. 輸入流跳過不需要讀取的字節(jié)。

這是很重要的一步,應該是很好理解的。

		bis.skip(position);  
		raf.seek(position);  

多線程本地文件復制(完整代碼)

package dragon;

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

/**
 * 用于進行文件復制,但不是常規(guī)的文件復制 。
 * 準備仿照瘋狂Java,寫一個多線程的文件復制工具。
 * 即可以本地復制和網(wǎng)絡復制
 * */

/**
 * 設計思路:
 * 獲取目標文件的大小,然后設置復制文件的大?。ㄟ@樣做是有好處的),
 * 然后使用將文件分為 n 分,使用 n 個線程同時進行復制(這里我將 n 取為 4)。
 * 
 * 可以進一步拓展:
 * 加強為斷點復制功能,即程序中斷以后,
 * 仍然可以繼續(xù)從上次位置恢復復制,減少不必要的重復開銷
 * */

public class FileCopyUtil {
	//設置一個常量,復制線程的數(shù)量
	private static final int THREAD_NUM = 4;
	
	private FileCopyUtil() {}
	
	/**
	 * @param targetPath 目標文件的路徑
	 * @param outputPath 復制輸出文件的路徑
	 * @throws IOException 
	 * */
	public static void transferFile(String targetPath, String outputPath) throws IOException {
		File targetFile = new File(targetPath);
		File outputFilePath = new File(outputPath);
		if (!targetFile.exists() || targetFile.isDirectory()) {   //目標文件不存在,或者是一個文件夾,則拋出異常
			throw new FileNotFoundException("目標文件不存在:"+targetPath);
		}
		if (!outputFilePath.exists()) {     //如果輸出文件夾不存在,將會嘗試創(chuàng)建,創(chuàng)建失敗,則拋出異常。
			if(!outputFilePath.mkdir()) {
				throw new FileNotFoundException("無法創(chuàng)建輸出文件:"+outputPath);
			}
		}
		
		long len = targetFile.length();
		
		File outputFile = new File(outputFilePath, "copy"+targetFile.getName());
		createOutputFile(outputFile, len);    //創(chuàng)建輸出文件,設置好大小。
		
		long[] position = new long[4];
		//每一個線程需要復制文件的起點
		long size = len / FileCopyUtil.THREAD_NUM + 1;
		for (int i = 0; i < FileCopyUtil.THREAD_NUM; i++) {
			position[i] = i*size;
			copyThread(i, position[i], size, targetFile, outputFile);
		}
	}
	
	//創(chuàng)建輸出文件,設置好大小。
	private static void createOutputFile(File file, long length) throws IOException {
		try (   
			RandomAccessFile raf = new RandomAccessFile(file, "rw")){
			raf.setLength(length);
		}
	}
	
	private static void copyThread(int i, long position, long size, File targetFile, File outputFile) {
		int n = i;   //Lambda 表達式的限制,無法使用變量。
		new Thread(()->{
			try (
				BufferedInputStream bis = new BufferedInputStream(new FileInputStream(targetFile));
				RandomAccessFile raf = new RandomAccessFile(outputFile, "rw")){
				bis.skip(position);  //跳過不需要讀取的字節(jié)數(shù),注意只能先后跳
				raf.seek(position);  //跳到需要寫入的位置,沒有這句話,會出錯,但是很難改。
				int hasRead = 0;
				byte[] b = new byte[1024];
				/**
				 * 注意,每個線程只是讀取一部分數(shù)據(jù),不能只以 -1 作為循環(huán)結束的條件
				 * 循環(huán)退出條件應該是兩個,即寫入的字節(jié)數(shù)大于需要讀取的字節(jié)數(shù) 或者 文件讀取結束(最后一個線程讀取到文件末尾)
				 */
				long curlen = 0;
				while(curlen < size && (hasRead = bis.read(b)) != -1) {
					raf.write(b, 0, hasRead);
					curlen += (long)hasRead;
				}
				System.out.println(n+" "+position+" "+curlen+" "+size);
			} catch (IOException e) {
				e.printStackTrace();
			}
		}).start(); 
	}
}

多線程網(wǎng)絡下載(完整代碼)

package dragon;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.URL;
import java.net.URLConnection;

/*
 * 多線程下載文件:
 * 通過一個 URL 獲取文件輸入流,使用多線程技術下載這個文件。
 * */
public class FileDownloadUtil {
	//下載線程數(shù)
	private static final int THREAD_NUM = 4;
	
	/**
	 * @param url 資源位置
	 * @param output 輸出路徑
	 * @throws IOException 
	 * */
	public static void transferFile(String url, String output) throws IOException {
		init(output);
		URL resource = new URL(url);
		URLConnection connection = resource.openConnection();
		
		//獲取文件類型
		String type = connection.getContentType();
		if (type != null) {
			type = "."+type.split("/")[1];
		} else {
			type = "";
		}
		
		//創(chuàng)建文件,并設置長度。
		long len = connection.getContentLength();
		String filename = System.currentTimeMillis()+type;
		try (RandomAccessFile raf = new RandomAccessFile(new File(output, filename), "rw")){
			raf.setLength(len);
		}
		
		//為每一個線程分配相應的下載其實位置
		long size = len / THREAD_NUM + 1;  
		long[] position = new long[THREAD_NUM];

		File downloadFile = new File(output, filename);
		//開始下載文件: 4個線程
		download(url, downloadFile, position, size);
	}
	
	private static void download(String url, File file, long[] position, long size) throws IOException {
		//開始下載文件: 4個線程
		for (int i = 0 ; i < THREAD_NUM; i++) {
			position[i] = i * size;   //每一個線程下載的起始位置
			int n = i;  // Lambda 表達式的限制,無法使用變量
			new Thread(()->{
				URL resource = null;
				URLConnection connection = null;
				try {
					resource = new URL(url);
					connection = resource.openConnection();
				} catch (IOException e) {
					e.printStackTrace();
				}
				
				try (
					BufferedInputStream bis = new BufferedInputStream(connection.getInputStream());
					RandomAccessFile raf = new RandomAccessFile(file, "rw")){  //每個流一旦關閉,就不能打開了
					raf.seek(position[n]);     //跳到需要下載的位置
					bis.skip(position[n]);   //跳過不需要下載的部分
					
					int hasRead = 0;
					byte[] b = new byte[1024];	
					long curlen = 0;
					while(curlen < size && (hasRead = bis.read(b)) != -1) {
						raf.write(b, 0, hasRead); 
						curlen += (long)hasRead;
					}
					System.out.println(n+" "+position[n]+" "+curlen+" "+size);
				} catch (FileNotFoundException e) {
					e.printStackTrace();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}) .start();
		}
	}
	
	private static void init(String output) throws FileNotFoundException {
		File path = new File(output);
		if (!path.exists()) {
			if (!path.mkdirs()) {
				throw new FileNotFoundException("無法創(chuàng)建輸出路徑:"+output);
			}
		} else if (path.isFile()) {
			throw new FileNotFoundException("輸出路徑不是一個目錄:"+output);
		}
	}	
}

測試代碼及結果

因為這個多線程文件復制和多線程下載是很相似的,所以就放在一起測試了。我也想將兩個寫在一個類里面,這樣可以做成方法的重載調用。 文件復制的第一個參數(shù)可以是 String 或者 URI。 使用這個作為目標文件的參數(shù)。

public File(URI uri)

網(wǎng)絡文件下載的第一個參數(shù),可以使用 String 或者是 URL。 不過,因為先寫的這個文件復制,后寫的多線程下載,就沒有做這部分。不過現(xiàn)在這樣功能也達到了,可以進行本地文件的復制(多線程)和網(wǎng)絡文件的下載(多線程)。

package dragon;

import java.io.IOException;

public class FileCopyTest {
	public static void main(String[] args) throws IOException {
		//復制文件
		long start = System.currentTimeMillis();
		try {
			FileCopyUtil.transferFile("D:\\DB\\download\\timg.jfif", "D:\\DBC");
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		long time = System.currentTimeMillis()-start;
		System.out.println("time: "+time);
		
		//下載文件
		start = System.currentTimeMillis();
		FileDownloadUtil.transferFile("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1578151056184&di=594a34f05f3587c31d9377a643ddd72e&imgtype=0&src=http%3A%2F%2Fn.sinaimg.cn%2Fsinacn%2Fw1600h1000%2F20180113%2F0bdc-fyqrewh5850115.jpg", "D:\\DB\\download");
		System.out.println("time: "+(System.currentTimeMillis()-start));
	}
}

運行截圖: 注意:這里這個時間并不是復制和下載需要的時間,實際上它沒有這個功能!

注意:雖然兩部分代碼是相同的,但是第三列數(shù)字,卻不是完全相同的,這個似乎是因為本地和網(wǎng)絡得區(qū)別吧。但是最后得文件是完全相同的,沒有問題得。(我本地文件復制得是網(wǎng)絡下載得那張圖片,使用圖片進行測試有一個好處,就是如果錯了一點(字節(jié)數(shù)目不對),這個圖片基本上就會產(chǎn)生問題。)

在這里插入圖片描述

產(chǎn)生錯誤之后的圖片: 圖片無法正常顯示,會出現(xiàn)很多的問題,這就說明一定是代碼寫錯了,哈哈。不過代碼的 bug 有時候還是很費時間的。

在這里插入圖片描述

總結

多線程復制和多線程下載,對于IO 流的使用都是相同的,所以掌握好IO 流的使用是很關鍵的一步,這樣就可以做很多很有趣的事情了。將IO流結合線程和網(wǎng)絡是一片廣闊的天地,但是我也是最近才看了一點網(wǎng)絡的知識。(網(wǎng)絡是很難的,寫這個多線程下載就遇到了一些網(wǎng)絡類上面的問題。)

使用計時器實現(xiàn)了多線程復制文件的斷點復制功能,感興趣的可以了解一下。 多線程斷點復制

到此這篇關于詳解Java多線程和IO流的應用的文章就介紹到這了,更多相關Java多線程和IO流內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • Java字符串拼接+和StringBuilder的比較與選擇

    Java字符串拼接+和StringBuilder的比較與選擇

    Java 提供了兩種主要的方式:使用 "+" 運算符和使用 StringBuilder 類,本文主要介紹了Java字符串拼接+和StringBuilder的比較與選擇,感興趣的可以了解一下
    2023-10-10
  • spring boot的maven配置依賴詳解

    spring boot的maven配置依賴詳解

    本篇文章主要介紹了spring boot的maven配置依賴詳解,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-11-11
  • 關于springBoot yml文件的list讀取問題總結(親測)

    關于springBoot yml文件的list讀取問題總結(親測)

    這篇文章主要介紹了關于springBoot yml文件的list讀取問題總結,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-12-12
  • 一個處理用戶登陸的servlet簡單實例

    一個處理用戶登陸的servlet簡單實例

    這篇文章主要介紹了一個處理用戶登陸的servlet簡單實例,可通過servlet實現(xiàn)處理用戶登錄的功能,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-01-01
  • springboot使用外置tomcat啟動方式

    springboot使用外置tomcat啟動方式

    這篇文章主要介紹了springboot使用外置tomcat啟動方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • java 題解LeetCode38外觀數(shù)列示例

    java 題解LeetCode38外觀數(shù)列示例

    這篇文章主要為大家介紹了java 題解LeetCode38外觀數(shù)列示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-08-08
  • Java 如何使用JDBC連接數(shù)據(jù)庫

    Java 如何使用JDBC連接數(shù)據(jù)庫

    這篇文章主要介紹了Java 如何使用JDBC連接數(shù)據(jù)庫,幫助大家更好的理解和學習使用Java,感興趣的朋友可以了解下
    2021-02-02
  • Java 動態(tài)加載jar和class文件實例解析

    Java 動態(tài)加載jar和class文件實例解析

    這篇文章主要介紹了Java 動態(tài)加載jar和class文件實例解析,分享了相關代碼示例,小編覺得還是挺不錯的,具有一定借鑒價值,需要的朋友可以參考下
    2018-02-02
  • springboot?整合sentinel的示例代碼

    springboot?整合sentinel的示例代碼

    本文主要介紹了springboot?整合sentinel的示例代碼,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-02-02
  • springboot整合Excel填充數(shù)據(jù)代碼示例

    springboot整合Excel填充數(shù)據(jù)代碼示例

    這篇文章主要給大家介紹了關于springboot整合Excel填充數(shù)據(jù)的相關資料,文中通過代碼示例介紹的非常詳細,對大家學習或者使用springboot具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-08-08

最新評論