100G的文件如何讀取 - 第306篇

相關歷史文章(閱讀本文之前,您可能需要先看下之前的系列👇

國內最全的Spring Boot系列之三

精度不夠,滑動時間來湊「限流算法第二把法器:滑動時間窗口算法」- 第301篇

沒有預熱,不叫高併發「限流算法第三把法器:令牌桶算法」- 第302篇

水滿自溢「限流算法第四把法器:漏桶算法」- 第303篇

一分鐘get:緩存穿透、緩存擊穿、緩存雪崩 - 第304篇

布隆過濾器Bloom Filter竟然讓我解決了一個大廠的問題 - 第305篇

 

在前面的文章《布隆過濾器Bloom Filter竟然讓我解決了一個大廠的問題》大廠面試題中,還隱含着一個問題,已經被我們的粉絲提前嗅探出來了:“如何用4g內存讀取298g的文件(a和b文件)“。

這裏我們把它抽象成,Java如何讀取大文件

師傅:我可愛的小徒兒,你知否?

悟纖:知否?知否?應是綠肥紅瘦。你說我知否?

師傅:徒兒,你這是什麼亂七八糟的,你不說,我怎知你知否?

悟纖:那徒兒,今天來給你展示下我深厚的功力了。

師傅:來,請開始你的表演。

 

 

一、內存讀取法:簡單明瞭,不玩陰的

此方法的思路很簡單,就是把文件直接讀取到內存中,然後進行操作。

1.1 方法一:使用java.nio.file.Files讀取文本文件

       使用Files類將文件的所有內容讀入字節數組。Files類還有一個方法可以讀取所有行到字符串列表。Files類是在Java 7中引入的,如果想加載所有文件內容,使用這個類是比較適合的。只有在處理小文件並且需要加載所有文件內容到內存中時才應使用此方法。

	public static void readFileByFiles(String pathname) {
		Path path = Paths.get(pathname);
		try {
			/*
			 * 使用readAllLines的時候,小文件可以很快讀取.
			 * 那麼更大的文件,讀取的肯定會爆了。
			 */
			//List<String> lines = Files.readAllLines(path);
			
			byte[] bytes = Files.readAllBytes(path);
			String str = new String(bytes);
			System.out.println(str);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

對於小文件,輕鬆就可以讀取進來;

對於大文件就會拋出如下異常:

或者是:

結論:小文件可以使用這種方式;讀取大文件,不能使用。

 

二、IO逐行讀取法:循序漸進,好舒服

2.1 方法二:使用java.io.FileReader類

可以使用FileReader獲取BufferedReader,然後逐行讀取文件,FileRead也有讀取char的方法,當然這樣的讀取方式效率很低很低了。

	public static void readFileByFileReader(String pathname) {
		File file = new File(pathname);
		FileReader fileReader;
		BufferedReader bufferedReader;
		try {
			fileReader = new FileReader(file);
			
			bufferedReader = new BufferedReader(fileReader);
			String line;
			StringBuffer buffer = new StringBuffer();
			while((line = bufferedReader.readLine()) != null){
			    // 一行一行地處理...
			    //System.out.println(line);
				//處理字符串,並不會將字符串保存真正保存到內存中
				 // 這裏簡單模擬下處理操作.
				buffer.append(line.substring(0,1));
			}
			 System.out.println("buffer.length:"+buffer.length());
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}finally {
			//TODO close處理.
		}
		
	}

       對於大文件可以逐行讀取,沒啥問題,測試了下:3.46G 耗時:11

       注意:這裏本質是使用了BufferedReader的緩衝,如果是使用的fileReader讀取char的方式,那麼時間會更久。

 

2.2方法三:使用java.io.BufferedReader

       如果想逐行讀取文件並對它們進行處理,那麼BufferedReader是非常合適的。它適用於處理大文件,也支持編碼BufferedReader同步的,因此可以安全地從多個線程完成對BufferedReader的讀取操作。BufferedReader的默認緩衝區大小爲:8KB

public static void readFileByBufferedReader(String pathname) {
		
		File file = new File(pathname);
		BufferedReader reader = null;
		FileInputStream fileInputStream = null;
		InputStreamReader inputStreamReader = null;
		try {
			//使用BufferedReader,每次讀入1M數據.減少IO.如:
			fileInputStream = new FileInputStream(file);
			inputStreamReader = new InputStreamReader(fileInputStream, Charset.defaultCharset());
			reader = new BufferedReader(inputStreamReader,1*1024*1024);
			 String tempString = null;
			 StringBuffer buffer = new StringBuffer();
			 while( (tempString = reader.readLine()) != null) {
	              //System.out.println(tempString);
				 //處理字符串,並不會將字符串保存真正保存到內存中
				 // 這裏簡單模擬下處理操作.
				 buffer.append(tempString.substring(0,1));
	         }
			 System.out.println("buffer.length:"+buffer.length());
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}finally {
			//TODO close處理
		}
		
	}

       逐行讀取,可以處理大文件,測試3.46G 耗時:10

 

2.3方法四:使用Scanner讀取文本文件

       如果要逐行讀取文件或基於某些java正則表達式讀取文件,則可使用Scanner類。

Scanner類使用分隔符模式將其輸入分解爲標記,分隔符模式默認匹配空格。然後可以使用各種下一種方法將得到的標記轉換成不同類型的值。Scanner類不同步,因此不是線程安全的

public static void readFileByScanner(String filePath) {
        FileInputStream inputStream = null;
        Scanner sc = null;
        try {
            inputStream = new FileInputStream(filePath);
            sc = new Scanner(inputStream, "UTF-8");
            StringBuffer buffer = new StringBuffer();
            while (sc.hasNextLine()) {
                String line = sc.nextLine();
                //System.out.println(line);
                //處理字符串,並不會將字符串保存真正保存到內存中
                // 這裏簡單模擬下處理操作.
				buffer.append(line.substring(0,1));
            }
            System.out.println("buffer.length:"+buffer.length());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //TODO close處理
        }
    }

       逐行處理,可以處理大文件,測試3.46G 耗時:57

 

2.4 方法五:使用RandomAccessFile讀取文本文件

       Java中的RandomAccessFile提供了對文件的讀寫功能。RandomAccessFile 雖然屬於java.io下的類,但它不是InputStream或者OutputStream的子類;它也不同於FileInputStream和FileOutputStream。 FileInputStream 只能對文件進行讀操作,而FileOutputStream 只能對文件進行寫操作;但是RandomAccessFile 與輸入流和輸出流不同之處就是RandomAccessFile可以訪問文件的任意地方同時支持文件的讀和寫,並且它支持隨機訪問。RandomAccessFile包含InputStream的三個read方法,也包含OutputStream的三個write方法。同時RandomAccessFile還包含一系列的readXxx和writeXxx方法完成輸入輸出。

	public static void readFileByRandomAccessFile(String pathname) {
		RandomAccessFile randomAccessFile = null;
		String str;
		try {
			randomAccessFile = new RandomAccessFile(pathname, "r");
			StringBuffer buffer = new StringBuffer();
			while ((str = randomAccessFile.readLine()) != null) {
			    //System.out.println(str);
				//處理字符串,並不會將字符串保存真正保存到內存中
				 // 這裏簡單模擬下處理操作.
				 buffer.append(str.substring(0,1));
}
			System.out.println("buffer.length:"+buffer.length());
		} catch (IOException e) {
			e.printStackTrace();
		}finally {
			//TODO close處理
		}
	}

       逐行讀取,可以讀取大文件,測試3.46G 耗時:很長...

 

 

三、NIO逐行讀取法:新的姿勢,新的體驗

3.1 方法五:使用FileChannel讀取文本

顧名思義,FileChannel就是連接到文件的Channel。使用FileChannel,你可以讀取文件數據,以及往文件裏面寫入數據。Java NIO的FileChannel是使用標準Java IO讀取文件的一種替代方案。

	public static void readFileFileChannel(String pathname) {
		File file = new File(pathname);
		FileInputStream fileInputStream = null;
		
		try {
			fileInputStream = new FileInputStream(file);
			FileChannel fileChannel = fileInputStream.getChannel();
			
			 int capacity = 1*1024*1024;//1M
			 ByteBuffer byteBuffer = ByteBuffer.allocate(capacity);
			 StringBuffer buffer = new StringBuffer();
			 while( fileChannel.read(byteBuffer) != -1) {
				 //讀取後,將位置置爲0,將limit置爲容量, 以備下次讀入到字節緩衝中,從0開始存儲
	             byteBuffer.clear();
				 byte[] bytes = byteBuffer.array();  
				 String str = new String(bytes);
				 //System.out.println(str);
				//處理字符串,並不會將字符串保存真正保存到內存中
				 // 這裏簡單模擬下處理操作.
				 buffer.append(str.substring(0,1));
			 }
			 System.out.println("buffer.length:"+buffer.length());
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}finally {
			//TODO close處理.
		}
		
	}

       逐行讀取,可以讀取大文件,測試3.46G 耗時:3,測試6.5G 耗時:6

 

四、悟纖小結

小結下:

(1)java底層天然支持大文件的讀取。

(2)常用的方式就是BufferedReader、FileChannel,FileChannel已經是很快了,測試3.46G 耗時:3秒,測試6.5G 耗時:6秒,測試13G 耗時15秒,那麼130G,那麼也就150秒左右了。

(3)NIO在大文件上的操作很佔優勢。(NIO爲什麼會這麼快呢?)

 

師傅:不錯、不錯,總結的很讚了。

徒兒:都是師傅教學有方。

師傅:還是徒兒自己好學、喜歡研究的結果。現在是實現了大文件的讀取,那麼有沒有更快的方式吶?。

徒兒:那是,不知道師傅有何高招吶?

師傅:必修得有高招,不然怎麼做你師傅吶,預知詳情下回分解。

 

我就是我,是顏色不一樣的煙火。
我就是我,是與衆不同的小蘋果。

學院中有Spring Boot相關的課程:

à悟空學院:https://t.cn/Rg3fKJD

SpringBoot視頻:http://t.cn/A6ZagYTi

Spring Cloud視頻:http://t.cn/A6ZagxSR

SpringBoot Shiro視頻:http://t.cn/A6Zag7IV

SpringBoot交流平臺:https://t.cn/R3QDhU0

SpringData和JPA視頻:http://t.cn/A6Zad1OH

SpringSecurity5.0視頻:http://t.cn/A6ZadMBe

Sharding-JDBC分庫分表實戰:http://t.cn/A6ZarrqS

分佈式事務解決方案「手寫代碼」:http://t.cn/A6ZaBnIr

JVM內存模型和性能調優:http://t.cn/A6wWMVqG

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章