1. 程式人生 > >JAVA遇到大批資料處理時會出現Java heap space的報錯的解決方案

JAVA遇到大批資料處理時會出現Java heap space的報錯的解決方案

Java heap space一直是困擾我們的一個問題。像Matlab就可以一次性讀取5000*5000的資料到一個矩陣matrix中。然而Java確不行。

我遇到實驗室處理一個“合併5000左右txt文件到一個txt檔案中”的問題,相同的演算法用Matlab就不會出現記憶體不足的問題,而JAVA則會報出:java.lang.OutOfMemoryError: Java heap space。從這裡可以判斷的說Matlab是為處理大型資料而設計(It is conceived for),所以其執行程式碼時對記憶體啟動了自動的分配與管理。因為我的5000左右txt文件大約有180M,顯而易見,同時讀到記憶體肯定會出現記憶體空間不足的警告,所以也不怪JAVA。當然,你可以在執行JAVA程式碼之前提高記憶體分配值,但是這實際上不能從根本上解決問題。因為哪天萬一還有更大的資料遇到時,你是不是還要繼續增大記憶體?要知道現在一般電腦的記憶體也就2G,就算1G的也很普遍。

所以根本上我覺得是要改變你的演算法。一個很自然的思路就是:資料大了就分配處理嘛。

所以我最後就是利用了遞迴的手段,將5000左右txt文件分批調入記憶體進行處理,處理一部分就釋放一部分資源。這樣不管是5000個數據檔案還是7000,9000都沒問題。試驗測得我的情況是如果不進行分批處理而是一次性調入記憶體在合併文件,那最大檔案數只能是2000,就是超過2000個數據檔案就會出現java.lang.OutOfMemoryError: Java heap space這樣的錯誤。

Matlab的缺點就是速度慢。所以處理海里資料為求速度還是可以試一下用JAVA實現的。我的實驗結果表明,速度相差幾十倍。

下面將一步一步介紹對這個同一問題不同的程式設計版本以及其結果:

1. 以我實驗室的頻譜測量儀(spectrometer)對鐳射所獲的離散取樣為背景。

本程式是屬於資料處理程式,用JAVA所實現。

spectrometer中光柵的每次取樣所獲得的資料被儲存在一個文字檔案中, 分為2列:第一列為波長值,第二列為對應的光強值。 第1次取樣文字檔案被命名為“000000.txt”,第二個為“000001.txt”,依次類推。 比如若讓spectrometer取樣3mins,大概會出現4000次取樣, 被儲存的檔案就為000000.txt~003999.txt

此程式使用的是二維LinkedList型別來拉取資料。 LinkedList:就是採用連結串列結構儲存物件。 PS:JAVA中集合類分為List,Set,和Map。 List又有LinkedList和ArrayList; Set分為HashSet和TreeSet; Map分為HashMap和TreeMap。 顧名思義,Hash就是由雜湊表提供支援,Tree樹結構提供了排序和順序輸出的功能。

package com.han;

import java.io.*;
import java.util.*;

/**
 * 以我實驗室的頻譜測量儀(spectrometer)對鐳射所獲的離散取樣為背景。
 * <p>
 * 本程式是屬於資料處理程式,用JAVA所實現。
 * <p>
 * spectrometer中光柵的每次取樣所獲得的資料被儲存在一個文字檔案中,
 * 分為2列:第一列為波長值,第二列為對應的光強值。
 * 第1次取樣文字檔案被命名為“000000.txt”,第二個為“000001.txt”,依次類推。
 * 比如若讓spectrometer取樣3mins,大概會出現4000次取樣,
 * 被儲存的檔案就為000000.txt~003999.txt
 * <p>
 * 此程式使用的是二維LinkedList型別來拉取資料。
 * LinkedList:就是採用連結串列結構儲存物件。
 * PS:JAVA中集合類分為List,Set,和Map。
 * List又有LinkedList和ArrayList;
 * Set分為HashSet和TreeSet;
 * Map分為HashMap和TreeMap。
 * 顧名思義,Hash就是由雜湊表提供支援,Tree樹結構提供了排序和順序輸出的功能。
 * @author han
 *
 */
public class CombinedTextSpectres {
	static final int N=6;//N是儲存的檔名稱的長度
	static final int M=4520;//M是儲存的檔案數目
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		long startTime=System.currentTimeMillis();
		try {
			//若是當前class類檔案目錄則用下面的getClass().getRessource()程式碼
			//File new_file=new File(new File(new test().getClass().getResource("").toURI()),"combinedFile.txt");		
			File new_file=new File("/home/han/Desktop","combinedFile.txt");//定義合併後的檔案的儲存路徑
			FileWriter fw = new FileWriter(new_file);
			BufferedWriter bfw=new BufferedWriter(fw);
			List row=new LinkedList();
			int num_line = 0;
			for(int i=0;i<M;i++){
				List column=new LinkedList();
				/*獲得每個離散取樣檔案的名稱*/
				String filename="00"+i;//"00"是儲存的檔案的字首	
				int temLength=filename.length();
				for(int j=0;j<N-temLength;j++){
					filename="0"+filename;
				}
				filename=filename+".txt";
				
				//定義存放離散取樣資料檔案的資料夾
				File file=new File("/home/han/Ubuntu One/apresmidi sans pola",filename);
				//File file=new File(new File(new test().getClass().getResource("").toURI()),filename);
				FileReader fr=new FileReader(file);
				BufferedReader bfr=new BufferedReader(fr);

				String s=null;
				while((s=bfr.readLine())!=null){
					s=s.replace(',', '.');
					if(i==0){
						column.add(s);
					}else{
						String[] sArray=s.split("\t");
						column.add(sArray[1]);
					}
				}
				row.add(column);
				num_line=column.size();
				bfr.close();
				fr.close();	
			}
			System.out.println("Files are all viewed");
			for(int i=0;i<num_line;i++){
				Iterator it=row.iterator();
				while(it.hasNext()){
					List tempList=(List)it.next();
					bfw.write((String)tempList.get(i));
					bfw.write("\t");
				}
				/*for(int j=0;j<row.size();j++){
					List tempList=(List)row.get(j);
					bfw.write((String)tempList.get(i));
					bfw.write("\t");	
				} */
				bfw.newLine();
			}
			bfw.close();
			fw.close();
			System.out.println("OK");
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		long endTime=System.currentTimeMillis();
		System.out.println("耗費時間: "+(endTime-startTime)+" ms");
	}
}		

執行結果:Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.ArrayList.subList(ArrayList.java:915)
at java.lang.String.split(String.java:2359)
at java.lang.String.split(String.java:2403)

at com.han.CombinedTextSpectres.main(CombinedTextSpectres.java:64)

2. 若減少要合併的離散資料檔案個數,上面的程式是可以成功執行的。為方便讀者,還是把資料來源和程式碼分別列出如下:

package com.han;

import java.io.*;
import java.util.*;

/**
 * 以我實驗室的頻譜測量儀(spectrometer)對鐳射所獲的離散取樣為背景。
 * <p>
 * 本程式是屬於資料處理程式,用JAVA所實現。
 * <p>
 * spectrometer中光柵的每次取樣所獲得的資料被儲存在一個文字檔案中,
 * 分為2列:第一列為波長值,第二列為對應的光強值。
 * 第1次取樣文字檔案被命名為“000000.txt”,第二個為“000001.txt”,依次類推。
 * 比如若讓spectrometer取樣3mins,大概會出現4000次取樣,
 * 被儲存的檔案就為000000.txt~003999.txt
 * <p>
 * 此程式使用的是二維LinkedList型別來拉取資料。
 * LinkedList:就是採用連結串列結構儲存物件。
 * PS:JAVA中集合類分為List,Set,和Map。
 * List又有LinkedList和ArrayList;
 * Set分為HashSet和TreeSet;
 * Map分為HashMap和TreeMap。
 * 顧名思義,Hash就是由雜湊表提供支援,Tree樹結構提供了排序和順序輸出的功能。
 * @author han
 *
 */
public class CombinedTextSpectres {
	static final int N=6;//N是儲存的檔名稱的長度
	static final int M=357;//M是儲存的檔案數目
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		long startTime=System.currentTimeMillis();
		try {
			//若是當前class類檔案目錄則用下面的getClass().getRessource()程式碼
			//File new_file=new File(new File(new test().getClass().getResource("").toURI()),"combinedFile.txt");		
			File new_file=new File("/home/han/Desktop","combinedFile.txt");//定義合併後的檔案的儲存路徑
			FileWriter fw = new FileWriter(new_file);
			BufferedWriter bfw=new BufferedWriter(fw);
			List row=new LinkedList();
			int num_line = 0;
			for(int i=0;i<M;i++){
				List column=new LinkedList();
				/*獲得每個離散取樣檔案的名稱*/
				String filename="00"+i;//"00"是儲存的檔案的字首	
				int temLength=filename.length();
				for(int j=0;j<N-temLength;j++){
					filename="0"+filename;
				}
				filename=filename+".txt";
				
				//定義存放離散取樣資料檔案的資料夾
				File file=new File("/home/han/Ubuntu One/spectre sans pola",filename);
				//File file=new File(new File(new test().getClass().getResource("").toURI()),filename);
				FileReader fr=new FileReader(file);
				BufferedReader bfr=new BufferedReader(fr);

				String s=null;
				while((s=bfr.readLine())!=null){
					s=s.replace(',', '.');
					if(i==0){
						column.add(s);
					}else{
						String[] sArray=s.split("\t");
						column.add(sArray[1]);
					}
				}
				row.add(column);
				num_line=column.size();
				bfr.close();
				fr.close();	
			}
			System.out.println("Files are all viewed");
			for(int i=0;i<num_line;i++){
				Iterator it=row.iterator();
				while(it.hasNext()){
					List tempList=(List)it.next();
					bfw.write((String)tempList.get(i));
					bfw.write("\t");
				}
				/*for(int j=0;j<row.size();j++){
					List tempList=(List)row.get(j);
					bfw.write((String)tempList.get(i));
					bfw.write("\t");	
				} */
				bfw.newLine();
			}
			bfw.close();
			fw.close();
			System.out.println("OK");
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		long endTime=System.currentTimeMillis();
		System.out.println("耗費時間: "+(endTime-startTime)+" ms");
	}
}		

執行結果:Files are all viewed
OK
耗費時間: 14880 ms

3. 下面的方法一樣的使用二維LinkedList型別來讀取資料。 但是在重新寫入到單個合併檔案時使用了StringBuilder, 經測試,效率和CombinedTextSpectres(第一版)差不多。

但是依然對於大資料如超過2000個檔案要合併時則出現記憶體洩漏。

package com.han;

import java.io.*;
import java.util.*;

/**
 * 此方法一樣的使用二維LinkedList型別來讀取資料。
 * 但是在重新寫入到單個合併檔案時使用了StringBuilder,
 * 經測試,效率和CombinedTextSpectres(第一版)差不多。
 * <p>
 * 但是依然對於大資料如超過2000個檔案要合併時則出現記憶體洩漏。
 * @author han
 *
 */
public class CombinedTextSpectres_2 {
	static final int N=6;//N是儲存的檔名稱的長度
	static final int M=357;//M是儲存的檔案數目
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		long startTime=System.currentTimeMillis();
		try {
			//若是當前class類檔案目錄則用下面的getClass().getRessource()程式碼
			//File new_file=new File(new File(new test().getClass().getResource("").toURI()),"combinedFile.txt");		
			File new_file=new File("/home/han/Desktop","combinedFile.txt");
			FileWriter fw = new FileWriter(new_file);
			BufferedWriter bfw=new BufferedWriter(fw);
			List row=new LinkedList();
			int num_line = 0;
			for(int i=0;i<M;i++){
				List column=new LinkedList();
				/*獲得每個離散取樣檔案的名稱*/
				String filename="00"+i;//"00"是儲存的檔案的字首	
				int temLength=filename.length();
				for(int j=0;j<N-temLength;j++){
					filename="0"+filename;
				}
				filename=filename+".txt";
				//定義存放離散取樣資料檔案的資料夾
				File file=new File("/media/96D0265ED0264539/Users/HAN/Documents/Thèse ISMO/DonneesLabo/10_10-14_10/spectre sans pola",filename);
				//File file=new File(new File(new test().getClass().getResource("").toURI()),filename);
				FileReader fr=new FileReader(file);
				BufferedReader bfr=new BufferedReader(fr);

				String s=null;
				while((s=bfr.readLine())!=null){
					s=s.replace(',', '.');
					if(i==0){
						column.add(s);
					}else{
						String[] sArray=s.split("\t");
						column.add(sArray[1]);
					}
				}
				row.add(column);
				num_line=column.size();
				bfr.close();
				fr.close();	
			}
			System.out.println("Files are all viewed");
			StringBuilder sb=new StringBuilder(""); 
			for(int i=0;i<num_line;i++){
				Iterator it=row.iterator();
				while(it.hasNext()){
					List tempList=(List)it.next();
					sb.append((String)tempList.get(i));
					sb.append("\t");
				}
				//以下這種應用.size()的遍歷方法在大迴圈的時候效率要略低於上面的.iterator()方法
				/*for(int j=0;j<row.size();j++){ 
					List tempList=(List)row.get(j);
					bfw.write((String)tempList.get(i));
					bfw.write("\t");	
				} */
				sb.append("\n");
			}
			bfw.write(sb.toString());
			bfw.close();
			fw.close();
			System.out.println("OK");
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		long endTime=System.currentTimeMillis();
		System.out.println("耗費時間: "+(endTime-startTime)+" ms");
	}
}		


執行結果: Files are all viewed

OK
耗費時間: 15155 ms

4. 下面的方法一樣的使用經典固定長度陣列,以及在重新寫入到單個合併檔案時使用了StringBuilder (其實因為BufferedWriter也是快取,所以有無SringBuilder效率一樣)。 經測試,效率比CombinedTextSpectres(第一版) 和CombinedTextSpectres_2(第二版)都要高出10倍!。

但是依然對於大資料如超過2000個檔案要合併時則出現記憶體洩漏。

package com.han;

import java.io.*;
/**
 * 此方法一樣的使用經典固定長度陣列,以及在重新寫入到單個合併檔案時使用了StringBuilder
 * (其實因為BufferedWriter也是快取,所以有無SringBuilder效率一樣)。
 * 經測試,效率比CombinedTextSpectres(第一版)
 * 和CombinedTextSpectres_2(第二版)都要高出10倍!。
 * <p>
 * 但是依然對於大資料如超過2000個檔案要合併時則出現記憶體洩漏。
 * @author han
 *
 */
public class CombinedTextSpectres_3 {
	//此方法使用自動探測單個檔案的行數(即spectrometre的取樣點數),無SringBuilder 
	static final int N=6;//N是儲存的檔名稱的長度
	/***貌似由於.split()函式的影響,記憶體洩漏在檔案數超過1800時***/
	static final int M=357;//M是儲存的檔案數目 
	/****************************************************/
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		long startTime=System.currentTimeMillis();
		try {
			//定義存放離散取樣資料檔案的資料夾
			String dirPath="/media/96D0265ED0264539/Users/HAN/Documents/Thèse ISMO/DonneesLabo/10_10-14_10/spectre sans pola";
			File file_temp=new File(dirPath,"000000.txt");
			FileReader fr_temp=new FileReader(file_temp);
			BufferedReader bfr_temp=new BufferedReader(fr_temp);
			int NLine = 0;//自動探測單個檔案的行數(即spectrometre的取樣點數)
			@SuppressWarnings("unused")
			String s_temp=null;
			while((s_temp=bfr_temp.readLine())!=null){
				NLine++;
			}
			bfr_temp.close();
			fr_temp.close();	
			String[][] CombinedArray=new String[NLine][M+1];
			
			File new_file=new File("/home/han/Desktop","combinedFile.txt");
			//若是當前class類檔案目錄則用下面的getClass().getRessource()程式碼
			//File new_file=new File(new File(new test().getClass().getResource("").toURI()),"combinedFile.txt");		
			FileWriter fw = new FileWriter(new_file);
			BufferedWriter bfw=new BufferedWriter(fw);
			for(int i=0;i<M;i++){
				String filename="00"+i;//"00"是儲存的檔案的字首	
				int temLength=filename.length();
				for(int j=0;j<N-temLength;j++){
					filename="0"+filename;
				}
				filename=filename+".txt";
				
				File file=new File(dirPath,filename);
				//File file=new File(new File(new test().getClass().getResource("").toURI()),filename);
				FileReader fr=new FileReader(file);
				BufferedReader bfr=new BufferedReader(fr);

				String s=null;
				int num_line = 0;
				while((s=bfr.readLine())!=null){
					s=s.replace(',', '.');
					String[] sArray=s.split("\t");
					if(i==0){
						CombinedArray[num_line][0]=sArray[0];
						CombinedArray[num_line][1]=sArray[1];
					}else{
						CombinedArray[num_line][i+1]=sArray[1];
					}	
					num_line++;
				}
				bfr.close();
				fr.close();	
			}
			System.out.println("Files are all viewed");
			for(int i=0;i<NLine;i++){
				for(int j=0;j<M+1;j++){
					bfw.write(CombinedArray[i][j]);
					bfw.write("\t");
				}
				bfw.newLine();
			}
			bfw.close();
			fw.close();
			System.out.println("OK");
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		long endTime=System.currentTimeMillis();
		System.out.println("耗費時間: "+(endTime-startTime)+" ms");
	}
}		

執行結果: Files are all viewed
OK
耗費時間: 1802 ms

5. 利用了遞迴的手段,將5000左右txt文件分批調入記憶體進行處理, 處理一部分就釋放一部分資源。這樣不管是5000個數據檔案還是7000,9000都沒問題。 試驗測得我的情況是如果不進行分批處理而是一次性調入記憶體在合併文件的話, 那最大檔案數只能是2000,就是超過2000個數據檔案就會出現 java.lang.OutOfMemoryError: Java heap space這樣的錯誤。

程式碼中level變數就是每次分批處理的數目,本程式設為1000。

此方法使用自動探測單個檔案的行數(即spectrometre的取樣點數), 因為BufferedWriter也是快取,所以有無SringBuilder效率差不多, 本程式因此寫入到單個合併檔案時沒有使用StringBuilder。 但是寫的時候也向系統申請了String[][] CombinedArray=new String[2048][Column]; 其中Column為批處理次數,比如3或4。由此可見雖然載入的還是“整個資料”, 但申請的String二維陣列只有String[2048][4],系統處理速度和記憶體佔用卻大大改善!

package com.han;

import java.io.*;

/**
 * 利用了遞迴的手段,將5000左右txt文件分批調入記憶體進行處理,
 * 處理一部分就釋放一部分資源。這樣不管是5000個數據檔案還是7000,9000都沒問題。
 * 試驗測得我的情況是如果不進行分批處理而是一次性調入記憶體在合併文件的話,
 * 那最大檔案數只能是2000,就是超過2000個數據檔案就會出現
 * java.lang.OutOfMemoryError: Java heap space這樣的錯誤。
 * <p>
 * 程式碼中level變數就是每次分批處理的數目,本程式設為1000。
 * <p>
 * 此方法使用自動探測單個檔案的行數(即spectrometre的取樣點數),
 * 因為BufferedWriter也是快取,所以有無SringBuilder效率差不多,
 * 本程式因此寫入到單個合併檔案時沒有使用StringBuilder。
 * 但是寫的時候也向系統申請了String[][] CombinedArray=new String[2048][Column];
 * 其中Column為批處理次數,比如3或4。由此可見雖然載入的還是“整個資料”,
 * 但申請的String二維陣列只有String[2048][4],系統處理速度和記憶體佔用卻大大改善!
 * @author han
 *
 */
public class CombinedTextSpectres_4 {
	static int N=6;//N是儲存的檔名稱的長度
	static int M=4520;//M是儲存的檔案數目 
	int level=1000;//每次分批處理的數目
	int filenumBegin;//每批處理時的第一個檔名
	int filenumEnd;//每批處理時的最後一個檔名
	String dirPath="/media/96D0265ED0264539/Users/HAN/Documents/Thèse ISMO/DonneesLabo/17_10-21_10/apresmidi sans pola";//定義存放離散取樣資料檔案的資料夾
	static File tempFile; //定義臨時資料夾來存放每批合併後的結果檔案
	static String destDirPath="/home/han/Desktop";//定義存放最終結果檔案的資料夾
	/**
	 * the construct function
	 */
	public CombinedTextSpectres_4(){
		try {
			combine(M,0);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * @param num 剩餘待處理的檔案數
	 * @param loop 遞迴的次數
	 * @throws IOException
	 */
	void combine(int num, int loop) throws IOException{
		if(num>level){	
			//如果剩餘的檔案數還是大於設定的level=1000的話,則繼續往下遞迴
			combine(num-level,loop+1);
		}
		filenumBegin=loop*level;
		if(num<=level){
			filenumEnd=filenumBegin+num;
		}else{
			filenumEnd=filenumBegin+level;
		}
		
		//定義經典固定長度陣列,因為比集合等動態陣列效率要高。
		//並且此時也可以保證String[][] CombinedArray不會
		//超出記憶體,因為每次處理的不會超過level=1000
		String[][] CombinedArray=new String[2048][filenumEnd-filenumBegin];
		
		for(int i=filenumBegin;i<filenumEnd;i++){
			String filename="00"+i;//"00"是儲存的檔案的字首	
			int temLength=filename.length();
			for(int j=0;j<N-temLength;j++){
				filename="0"+filename;
			}
			filename=filename+".txt";
			File file=new File(dirPath,filename);
			FileReader fr=new FileReader(file);
			BufferedReader bfr=new BufferedReader(fr);
			int num_line = 0;
			String s=null;
			while((s=bfr.readLine())!=null){
				if(i==0){
					CombinedArray[num_line][0]=s.replace(',', '.');
				}else{
					String[] sArray=s.split("\t");
					CombinedArray[num_line][i-filenumBegin]=sArray[1].replace(',', '.');
				}
				num_line++;
			}
			bfr.close();
			fr.close();	
		}
		//System.out.println("Files are all viewed");
		
		/*建立臨時資料夾存放每批合併後的結果檔案*/
		String filename=loop+".txt";
		tempFile=new File(dirPath,"temp");
		if(!tempFile.exists()){
			tempFile.mkdir();
		}
		File new_file=new File(tempFile,filename);
		FileWriter fw = new FileWriter(new_file);
		BufferedWriter bfw=new BufferedWriter(fw);
		for(int i=0;i<2048;i++){
			for(int j=0;j<filenumEnd-filenumBegin;j++){
				bfw.write(CombinedArray[i][j]);
				bfw.write("\t");
			}
			bfw.newLine();
		}
		CombinedArray=null;//讓JAVA垃圾自動回收裝置認為此CombinedArray變數可以回收
		bfw.close();
		fw.close();
		System.out.println(new_file);
		System.out.println("OK");
	}
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		long startTime=System.currentTimeMillis();
		new CombinedTextSpectres_4();
		/*再把中間臨時檔案都合併成最終的單個檔案*/
		File[] FileArray=tempFile.listFiles();
		int Column=FileArray.length;
		String[][] CombinedArray=new String[2048][Column];
		try {
			for(int i=0;i<Column;i++){
				FileReader fr=new FileReader(FileArray[i]);
				BufferedReader bfr=new BufferedReader(fr);
				int num_line = 0;
				String s=null;	
				while((s=bfr.readLine())!=null){
					CombinedArray[num_line][i]=s;
					num_line++;
				}
				bfr.close();
				fr.close();	
			}
			File new_file=new File(destDirPath,"CombinedSpectres.txt");
			FileWriter fw = new FileWriter(new_file);
			BufferedWriter bfw=new BufferedWriter(fw);
			/***************加入實驗條件**********************/
			String ExperienceCondition="Integrated time is 1 ms, with the HR Spectrometer, with polarizorH, using the syringe Rh6G+Glycol and the other is Huile";
			bfw.write(ExperienceCondition);
			bfw.newLine();
			/***************加入每列數值代表的物理含義***********/
			bfw.write("Wavelength (nm)\t");
			for(int i=0;i<M;i++){
				String filename="00"+i;//"00"是儲存的檔案的字首	
				int temLength=filename.length();
				for(int j=0;j<N-temLength;j++){
					filename="0"+filename;
				}
				filename=filename+".txt";
				bfw.write(filename);
				bfw.write("\t");
			}
			bfw.newLine();
			/*****************寫入全部資料********************/
			for(int i=0;i<2048;i++){
				for(int j=0;j<Column;j++){
					bfw.write(CombinedArray[i][j]);
					//bfw.write("\t");
				}
				bfw.newLine();
			}
			CombinedArray=null;
			bfw.close();
			fw.close();
			System.out.println(new_file);
			System.out.println("OK");
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		long endTime=System.currentTimeMillis();
		System.out.println("耗費時間: "+(endTime-startTime)+" ms");
	}
}		
執行結果:/media/96D0265ED0264539/Users/HAN/Documents/Thèse ISMO/DonneesLabo/17_10-21_10/apresmidi sans pola/temp/4.txt
OK
/media/96D0265ED0264539/Users/HAN/Documents/Thèse ISMO/DonneesLabo/17_10-21_10/apresmidi sans pola/temp/3.txt
OK
/media/96D0265ED0264539/Users/HAN/Documents/Thèse ISMO/DonneesLabo/17_10-21_10/apresmidi sans pola/temp/2.txt
OK
/media/96D0265ED0264539/Users/HAN/Documents/Thèse ISMO/DonneesLabo/17_10-21_10/apresmidi sans pola/temp/1.txt
OK
/media/96D0265ED0264539/Users/HAN/Documents/Thèse ISMO/DonneesLabo/17_10-21_10/apresmidi sans pola/temp/0.txt
OK
/home/han/Desktop/CombinedSpectres.txt
OK
耗費時間: 30842 ms

6. 但是考慮到上個程式最後寫的時候也向系統申請了 String[][] CombinedArray=new String[2048][Column]; 其中Column為批處理次數,比如3或4。由此可見載入的還是“整個資料”, 雖然試驗證明了系統處理速度和記憶體佔用大大改善了,是可行的。但是, 在本程式中還是又採用了另一種方法:同時開通4個輸入流分別指向4個臨時檔案, 寫入最後的單個結果檔案。效果證明也非常不錯!

package com.han;

import java.io.*;

/**
 * 利用了遞迴的手段,將5000左右txt文件分批調入記憶體進行處理,
 * 處理一部分就釋放一部分資源。這樣不管是5000個數據檔案還是7000,9000都沒問題。
 * 試驗測得我的情況是如果不進行分批處理而是一次性調入記憶體在合併文件的話,
 * 那最大檔案數只能是2000,就是超過2000個數據檔案就會出現
 * java.lang.OutOfMemoryError: Java heap space這樣的錯誤。
 * <p>
 * 程式碼中level變數就是每次分批處理的數目,本程式設為1000。
 * <p>
 * 此方法使用自動探測單個檔案的行數(即spectrometre的取樣點數),
 * 因為BufferedWriter也是快取,所以有無SringBuilder效率差不多,
 * 本程式因此寫入到單個合併檔案時沒有使用StringBuilder。
 * 但是考慮到最後寫的時候也向系統申請了
 * String[][] CombinedArray=new String[2048][Column];
 * 其中Column為批處理次數,比如3或4。由此可見載入的還是“整個資料”,
 * 雖然試驗證明了系統處理速度和記憶體佔用大大改善了,是可行的。但是,
 * 在本程式中還是又採用了另一種方法:同時開通4個輸入流分別指向4個臨時檔案,
 * 寫入最後的單個結果檔案。效果證明也非常不錯!
 * @author han
 *
 */
public class CombinedTextSpectres_5 {
	static int N=6;//N是儲存的檔名稱的長度
	static int M=4520;//M是儲存的檔案數目 
	int level=1000;//每次分批處理的數目
	int filenumBegin;//每批處理時的第一個檔名
	int filenumEnd;//每批處理時的最後一個檔名
	String dirPath="/media/96D0265ED0264539/Users/HAN/Documents/Thèse ISMO/DonneesLabo/17_10-21_10/apresmidi sans pola";//定義存放離散取樣資料檔案的資料夾
	static File tempFile; //定義臨時資料夾來存放每批合併後的結果檔案
	static String destDirPath="/home/han/Desktop";//定義存放最終結果檔案的資料夾
	/**
	 * the construct function
	 */
	public CombinedTextSpectres_5(){
		try {
			combine(M,0);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * @param num 剩餘待處理的檔案數
	 * @param loop 遞迴的次數
	 * @throws IOException
	 */
	void combine(int num, int loop) throws IOException{
		if(num>level){	
			//如果剩餘的檔案數還是大於設定的level=1000的話,則繼續往下遞迴
			combine(num-level,loop+1);
		}
		filenumBegin=loop*level;
		if(num<=level){
			filenumEnd=filenumBegin+num;
		}else{
			filenumEnd=filenumBegin+level;
		}

		//定義經典固定長度陣列,因為比集合等動態陣列效率要高。
		//並且此時也可以保證String[][] CombinedArray不會
		//超出記憶體,因為每次處理的不會超過level=1000
		String[][] CombinedArray=new String[2048][filenumEnd-filenumBegin];

		for(int i=filenumBegin;i<filenumEnd;i++){
			String filename="00"+i;//"00"是儲存的檔案的字首	
			int temLength=filename.length();
			for(int j=0;j<N-temLength;j++){
				filename="0"+filename;
			}
			filename=filename+".txt";
			File file=new File(dirPath,filename);
			FileReader fr=new FileReader(file);
			BufferedReader bfr=new BufferedReader(fr);
			int num_line = 0;
			String s=null;
			while((s=bfr.readLine())!=null){
				if(i==0){
					CombinedArray[num_line][0]=s.replace(',', '.');
				}else{
					String[] sArray=s.split("\t");
					CombinedArray[num_line][i-filenumBegin]=sArray[1].replace(',', '.');
				}
				num_line++;
			}
			bfr.close();
			fr.close();	
		}
		//System.out.println("Files are all viewed");

		//建立臨時資料夾存放每批合併後的結果檔案
		String filename=loop+".txt";
		tempFile=new File(dirPath,"temp");
		if(!tempFile.exists()){
			tempFile.mkdir();
		}
		File new_file=new File(tempFile,filename);
		FileWriter fw = new FileWriter(new_file);
		BufferedWriter bfw=new BufferedWriter(fw);
		for(int i=0;i<2048;i++){
			for(int j=0;j<filenumEnd-filenumBegin;j++){
				bfw.write(CombinedArray[i][j]);
				bfw.write("\t");
			}
			bfw.newLine();
		}
		CombinedArray=null;//讓JAVA垃圾自動回收裝置認為此CombinedArray變數可以回收
		bfw.close();
		fw.close();
		System.out.println(new_file);
		System.out.println("OK");
	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		long startTime=System.currentTimeMillis();
		new CombinedTextSpectres_5();
		try{
			File new_file=new File(destDirPath,"CombinedSpectres.txt");
			FileWriter fw = new FileWriter(new_file);
			BufferedWriter bfw=new BufferedWriter(fw);
			/***************加入實驗條件**********************/
			String ExperienceCondition="Integrated time is 1 ms, with the HR Spectrometer, with polarizorH, using the syringe Rh6G+Glycol and the other is Huile";
			bfw.write(ExperienceCondition);
			bfw.newLine();
			/***************加入每列數值代表的物理含義***********/
			bfw.write("Wavelength (nm)\t");
			for(int i=0;i<M;i++){
				String filename="00"+i;//"00"是儲存的檔案的字首	
				int temLength=filename.length();
				for(int j=0;j<N-temLength;j++){
					filename="0"+filename;
				}
				filename=filename+".txt";
				bfw.write(filename);
				bfw.write("\t");
			}
			bfw.newLine();
			/*****************寫入全部資料********************/
			//再把中間臨時檔案都合併成最終的單個檔案
			File[] FileArray=tempFile.listFiles();
			int Column=FileArray.length;
			FileReader[] fr=new FileReader[Column];
			BufferedReader[] bfr=new BufferedReader[Column];
			/*for(int i=0;i<Column;i++){
				System.out.println(FileArray[i]);
			}*/
			for(int i=0;i<Column;i++){
				fr[i]=new FileReader(FileArray[i]);
				bfr[i]=new BufferedReader(fr[i]);
			}
			String s;
			while((s=bfr[0].readLine())!=null){
				for(int i=0;i<Column;i++){
					if(i==0){
						bfw.write(s);
					}else{
						bfw.write(bfr[i].readLine());
					}
				}
				bfw.newLine();
			}
			for(int i=0;i<Column;i++){
				bfr[i].close();
				fr[i].close();			
			}	
			bfw.close();
			fw.close();
			System.out.println(new_file);
			System.out.println("OK");
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		long endTime=System.currentTimeMillis();
		System.out.println("耗費時間: "+(endTime-startTime)+" ms");
	}
}		

執行結果: /media/96D0265ED0264539/Users/HAN/Documents/Thèse ISMO/DonneesLabo/17_10-21_10/apresmidi sans pola/temp/4.txt
OK
/media/96D0265ED0264539/Users/HAN/Documents/Thèse ISMO/DonneesLabo/17_10-21_10/apresmidi sans pola/temp/3.txt
OK
/media/96D0265ED0264539/Users/HAN/Documents/Thèse ISMO/DonneesLabo/17_10-21_10/apresmidi sans pola/temp/2.txt
OK
/media/96D0265ED0264539/Users/HAN/Documents/Thèse ISMO/DonneesLabo/17_10-21_10/apresmidi sans pola/temp/1.txt
OK
/media/96D0265ED0264539/Users/HAN/Documents/Thèse ISMO/DonneesLabo/17_10-21_10/apresmidi sans pola/temp/0.txt
OK
/home/han/Desktop/CombinedSpectres.txt
OK
耗費時間: 28230 ms

到此,實驗室中頻譜資料處理就告一段落了。總結就是:改變演算法非常重要。遇到問題,找到根源後,總會有改良的演算法來解決它的。

******************************************************************************************************************************************************************************

不過,隨著繼續經驗的積累,我發現全部使用流操作是可以的,即:用約5000個輸入流同時分別指向每一個要合併的檔案,然後用一個輸出流指向合併結果檔案。

雖然也許會遇到同時開啟的流數限制(發現在Linux下是這樣的,Windows尚未發現),但是可以再通過分批處理的思想來解決的。

在本程式中又採用了另一種方法:同時開通N個輸入流分別指向N個要合併的資料檔案, 寫入最後的單個結果檔案。效果證明執行時間差不多,但是佔用記憶體更少。

PS: 但是發現了一個Linux下特有的問題(因為此問題在Windows下不存在): 當檔案讀到大約第4092個時,丟擲了“檔案找不到“的異常。 所以斷定同時開啟輸入流的數目在Linux下此時被限制為4092。 所以此時必須分批來處理,每批處理數目自動設為能夠同時開啟的最大流數目 (實際發現是輸入流和輸出流的總數,而此數字可以通過丟擲的異常資訊獲得)。

總結:這樣此程式即可以在Linux中執行,又可以在Windows中執行,就是做到了真正的跨平臺!


程式碼如下:

package com.han;

import java.io.*;

/**
 * 在本程式中又採用了另一種方法:同時開通N個輸入流分別指向N個要合併的資料檔案,
 * 寫入最後的單個結果檔案。效果證明執行時間差不多,但是佔用記憶體更少。
 * <p>
 * PS: 但是發現了一個Linux下特有的問題(因為此問題在Windows下不存在):
 * 當檔案讀到大約第4092個時,丟擲了“檔案找不到“的異常。
 * 所以斷定同時開啟輸入流的數目在Linux下此時被限制為4092。
 * 所以此時必須分批來處理,每批處理數目自動設為能夠同時開啟的最大流數目
 * (實際發現是輸入流和輸出流的總數,而此數字可以通過丟擲的異常資訊獲得)。
 * <p>
 * 總結:這樣此程式即可以在Linux中執行,又可以在Windows中執行,就是做到了真正的跨平臺!
 * @author han
 *
 */
public class CombinedTextSpectres6 {
	
	private final int N=6;//N是儲存的檔名稱的長度
	private final int M=4520;//M是儲存的檔案數目 
	String ExperienceCondition="Integrated time is 1 ms, with the HR Spectrometer, with polarizorH, using the syringe Rh6G+Glycol and the other is Huile";
	String dirPath="/media/96D0265ED0264539/Users/HAN/Documents/Thèse ISMO/DonneesLabo/17_10-21_10/apresmidi sans pola";//定義存放離散取樣資料檔案的資料夾
	//	String dirPath="/media/96D0265ED0264539/Users/HAN/Documents/Thèse ISMO/DonneesLabo/10_10-14_10/spectre sans pola";
	static String destDirPath="/home/han/Desktop";//定義存放最終結果檔案的資料夾
	File new_file;

	File[] FileArray;
	int Column;
	FileReader[] fr;
	BufferedReader[] bfr;
	FileWriter fw;
	BufferedWriter bfw;
	/**
	 * the construct function
	 * @throws IOException 
	 */
	public CombinedTextSpectres6() throws IOException{
		long startTime=System.currentTimeMillis();
		try{
			/*****************寫入全部資料********************/
			FileArray=new File(dirPath).listFiles();
			Column=FileArray.length;// 4000 limit
			fr=new FileReader[Column];//宣告
			bfr=new BufferedReader[Column];
			/*for(int i=0;i<Column;i++){
			System.out.println(FileArray[i]);
		}*/
			for(int i=0;i<Column;i++){//初始化
				fr[i]=new FileReader(FileArray[i]);
				bfr[i]=new BufferedReader(fr[i]);
			}
			
			new_file=new File(destDirPath,"CombinedSpectres.txt");
			fw = new FileWriter(new_file);
			bfw=new BufferedWriter(fw);
			/***************加入實驗條件**********************/
			bfw.write(ExperienceCondition);
			bfw.newLine();
			/***************加入每列數值代表的物理含義***********/
			bfw.write("Wavelength (nm)\t");
			for(int i=0;i<M;i++){
				String filename="00"+i;//"00"是儲存的檔案的字首	
				int temLength=filename.length();
				for(int j=0;j<N-temLength;j++){
					filename="0"+filename;
				}
				filename=filename+".txt";
				bfw.write(filename);
				bfw.write("\t");
			}
			bfw.newLine();
			
			String s;
			while((s=bfr[0].readLine())!=null){
				for(int i=0;i<Column;i++){
					if(i==0){
						bfw.write(s.replace(',', '.'));
						bfw.write("\t");
					}else{
						bfw.write(bfr[i].readLine().split("\t")[1].replace(',', '.'));
						bfw.write("\t");
					}
				}
				bfw.newLine();
			}
			for(int i=0;i<Column;i++){
				bfr[i].close();
				fr[i].close();			
			}	
			bfw.close();
			fw.close();
			System.out.println(new_file);
			System.out.println("OK");
		} catch (Exception e) {
			// TODO Auto-generated catch block
			String strFile=e.getMessage();
			String fileName=strFile.substring(dirPath.length()+1, dirPath.length()+7);
			int maxNumFile=Integer.parseInt(fileName);
			maxNumFile=maxNumFile-6;//for "bfr[i].close();" successful
			/*System.out.println(fileName);
			System.out.println(maxNumFile);*/
			for(int i=0;i<maxNumFile;i++){ //close all input streams opened for the aims of releasing the resources.
				bfr[i].close();
				fr[i].close();			
			}	
			System.out.printf("The maximum opened file number limited by the current computer environment is : %d\n",maxNumFile-1);
			System.out.println("So we have to retreat the problem by considering the limitation of the computer resources.");
			new LinuxLimit(maxNumFile-1).run();
		}
		long endTime=System.currentTimeMillis();
		System.out.println("耗費時間: "+(endTime-startTime)+" ms");
	}
	class LinuxLimit{
		private final int maxNumFile;
		File tempDir=new File(destDirPath,"temp");

		LinuxLimit(int maxNumFile){
			this.maxNumFile=maxNumFile;
		}
		void run(){	
			int fileStart=0;
			int fileEnd=0;
			int num=0;
			String filename=num+".txt";
			if(!tempDir.exists()){
				tempDir.mkdir();
			}
			Column=maxNumFile;
			try{
				while(Column>=maxNumFile){
					fileEnd=fileStart+maxNumFile;
					fw=new FileWriter(new File(tempDir,filename));
					bfw=new BufferedWriter(fw);
					System.out.println(fileStart);
					System.out.println(fileEnd);
					for(int i=fileStart;i<fileEnd;i++){//初始化
						fr[i-fileStart]=new FileReader(FileArray[i]);
						bfr[i-fileStart]=new BufferedReader(fr[i-fileStart]);
					}
					String s;
					while((s=bfr[0].readLine())!=null){
						for(int i=0;i<maxNumFile;i++){
							if(i==0 && num==0){
								bfw.write(s.replace(',', '.'));
								bfw.write("\t");
							}else if(i==0 && num!=0){
								bfw.write(s.split("\t")[1].replace(',', '.'));
								bfw.write("\t");
							}else if(i!=0){
								bfw.write(bfr[i].readLine().split("\t")[1].replace(',', '.'));
								bfw.write("\t");
							}
						}
						bfw.newLine();
					}
					for(int i=0;i<maxNumFile;i++){
						bfr[i].close();
						fr[i].close();			
					}
					bfw.close();
					fw.close();	
					fileStart=fileEnd;
					Column=M-fileEnd;		
					num++;
					filename=num+".txt";
				}
				if(Column!=0){
					fw=new FileWriter(new File(tempDir,filename));
					bfw=new BufferedWriter(fw);
					for(int i=fileStart;i<M;i++){//初始化 M=fileStart+Column
						fr[i-fileStart]=new FileReader(FileArray[i]);
						bfr[i-fileStart]=new BufferedReader(fr[i-fileStart]);
					}
					String s;
					while((s=bfr[0].readLine())!=null){
						for(int i=0;i<Column;i++){
							if(i==0){
								bfw.write(s.split("\t")[1].replace(',', '.'));
								bfw.write("\t");
							}else{
								bfw.write(bfr[i].readLine().split("\t")[1].replace(',', '.'));
								bfw.write("\t");
							}
						}
						bfw.newLine();
					}
					for(int i=0;i<Column;i++){
						bfr[i].close();
						fr[i].close();			
					}
					bfw.close();
					fw.close();					
				}

				/*****************************/
				FileArray=tempDir.listFiles();
				Column=FileArray.length; 
				fr=new FileReader[Column];//宣告
				bfr=new BufferedReader[Column];
				new_file=new File(destDirPath,"CombinedSpectres.txt");
				fw = new FileWriter(new_file);
				bfw=new BufferedWriter(fw);
				for(int i=0;i<Column;i++){
					System.out.println(FileArray[i]);
				}
				for(int i=0;i<Column;i++){//初始化
					fr[i]=new FileReader(FileArray[i]);
					bfr[i]=new BufferedReader(fr[i]);
				}
				
				/***************加入實驗條件**********************/
				bfw.write(ExperienceCondition);
				bfw.newLine();
				/***************加入每列數值代表的物理含義***********/
				bfw.write("Wavelength (nm)\t");
				for(int i=0;i<M;i++){
					String fname="00"+i;//"00"是儲存的檔案的字首	
					int temLength=fname.length();
					for(int j=0;j<N-temLength;j++){
						fname="0"+fname;
					}
					fname=fname+".txt";
					bfw.write(fname);
					bfw.write("\t");
				}
				bfw.newLine();
				
				String s;
				while((s=bfr[0].readLine())!=null){
					for(int i=0;i<Column;i++){
						if(i==0){
							bfw.write(s);
							bfw.write("\t");
						}else{
							bfw.write(bfr[i].readLine());
							bfw.write("\t");
						}
					}
					bfw.newLine();
				}
				for(int i=0;i<Column;i++){
					bfr[i].close();
					fr[i].close();			
				}	
				bfw.close();
				fw.close();
				for(File e:FileArray){
					e.delete();
				}
				tempDir.delete();
				System.out.println("The temp folder has been deleted.");
			}catch(Exception e){
				e.printStackTrace();
			}
		}
	}
	public static void main(String[] args) {
		// TODO Auto-generated method stub		
		try {
			new CombinedTextSpectres6();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}
}	

執行結果:The maximum opened file number limited by the current computer environment is : 4091
So we have to retreat the problem by considering the limitation of the computer resources.
0
4091
/home/han/Desktop/temp/0.txt
/home/han/Desktop/temp/1.txt
The temp folder has been deleted.
耗費時間: 17582 ms

程式中所要用到的資料檔案以及合併後的結果檔案都和前面是相同的。