CSV文件的生成與分析

CSV文件是指Excel可以識別的後綴名爲CSV的文件,網站系統後臺用來存儲分析數據的時候有可能會用到它。其實CSV文件的讀取和做成比較簡單,主要的技術點是文件的讀寫。不過CSV文件的分析和生成有一定的代表性,可以通過對CSV文件的生成和分析的實現,來了解後臺處理批量數據的簡單思路。

打開Excel,新建一個文件,在裏面隨便輸入一些數據,然後另存爲一個CSV文件,再次用Excel打開的時候,發現只有一個sheet。用一個記事本打開這個文件,可以看到類似如下的數據。
aaa,bbb,ccc,ddd
aaa2,bbb2,,ddd2
aaa3,bbb3,ccc3,
aaa4,bbb4,ccc4,ddd4

仔細觀察數據,可以確定以下幾點:

CSV文件是文本型文件(非二進制)
文件中的一行在Excel中顯示的一行
同一行中的數據用半角逗號分隔
發現以上的規律,我們可以自己也生成一個CSV文件。也可以簡單的分析CSV文件。
現在給CSV文件的生成和分析寫個簡單的例子。(如果對文件處理類不熟悉,可以看一下java的文件處理,或者看一下JDK中關於java.io.File類的幫助信息)
生成CSV文件。(HelloCsvCreater.java)

package com.vogoal.test;

import java.io.FileWriter;
import java.io.IOException;

/**
 * @author SinNeR
 *
 * create a CSV file
 */
public class HelloCsvCreater {
 public static void main(String[] args) {
  try {
   FileWriter fw = new FileWriter("C://helloCsv.csv");
   fw.write("aaa,bbb,ccc,ddd,eee,fff,ggg,hhh/r/n");
   fw.write("aa1,bb1,cc1,dd1,ee1,ff1,gg1,hh1/r/n");
   fw.write("aaa/r/n");
   fw.write("aa2,bb2,cc2,dd2,ee2,ff2,gg2,hh2/r/n");
   fw.close();
  } catch (IOException e) {
   e.printStackTrace();
  }
 }
}

/**
  * MIS出廠項目中的一個方法,循環寫csv數據
  * */
 private void createCsv(List<VinStatusView> vinStatusView){
  FileWriter fw;
  try {
   fw = new FileWriter("D://helloCsv.csv");
  fw.write("VIN,庫位,經銷商代碼,經銷商/r/n");
  for(VinStatusView view : vinStatusView){
   String rowCsv = view.getVin()+","+view.getLocation()+","+view.getShipToName()+"/r/n";
   fw.write(rowCsv);
  }
  fw.close();
  } catch (IOException e) {
   throw new BusinessException("write.file.faile");
  }
 }

這個類編譯後運行,可以看到在C盤根目錄下生成了一個名字爲helloCsv.csv的CSV文件,雙擊打開,如下的樣子。

分析CSV文件

package com.vogoal.test;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;

/**
 * @author SinNeR
 *
 * analysis a CSV file
 */
public class HelloCSVAnalysis {
 public static void main(String[] args) {
  InputStreamReader fr = null;
  BufferedReader br = null;
  try {
   fr = new InputStreamReader(new FileInputStream("C://helloCsv.csv"));
   br = new BufferedReader(fr);
   String rec = null;
   String[] argsArr = null;
   while ((rec = br.readLine()) != null) {
    System.out.println(rec);
    argsArr = rec.split(",");
    for (int i = 0; i < argsArr.length; i++) {
     System.out.println("num " + (i + 1) + ":" + argsArr[i]);
    }
   }
  } catch (IOException e) {
   e.printStackTrace();
  } finally {
   try {
    if (fr != null)
     fr.close();
    if (br != null)
     br.close();
   } catch (IOException ex) {
    ex.printStackTrace();
   }
  }
 }
}

這裏將剛纔生成的csv文件讀取並分析。編譯後運行,正常情況下,可以看到剛纔生成的CSV文件的內容。

至此,CSV文件的生成與分析其實已經完成。如果要寫適合自己需要的CSV文件分析類,完全可以根據自己的業務邏輯和需要來自己實現。因爲CSV文件的分析確實很簡單。

不過上面的程序還是存在一些問題的。這些問題在開發的過程中應當注意,不然可能出現致命的錯誤。

比較2個類中對資源的釋放問題。CSV生成類中FileWriter對象的關閉(close()方法)是在try中執行的。而CSV分析類中InputStreamReader,BufferedReader對象的關閉(close()方法)是在finally中執行的。CSV生成類是錯誤的。因爲在文件和流的生成過程中,是有可能產生IO異常的,如果在對象close前發生IO異常,那麼close方法永遠不會被調用,這樣資源不會及時釋放,會產生致命錯誤的。而在finally中的程序,是一定會被執行的語句,所以即使操作中途發生問題,也會在最後執行close方法。(try-catch-finally是java語法中基本而重要的部分,不熟悉的可查閱相關資料。)
在CSV文件的操作過程中,我們是按照半角逗號來分隔數據的,如果某個數據中正好有半角逗號,那麼數據不是出錯了?
如果分析的數據有全角字符,是否能夠正確分析。(亂碼問題)
以上的第二個問題時必須考慮的。在寫一個類的時候,不要相信這個類要操作的數據或者得到的數據是好數據(完全符合要求的正確的數據),寫好的一個類用完全正確的數據測試完,很有可能一個小小的數據錯誤的問題,就有可能導致程序處理崩潰。所以,細節問題要充分考慮並對應到,使自己編寫的類具有一定的健壯性。
對於2的問題的討論:

如果我們用Excel文件生成CSV文件,其中數據有半角逗號,Excel會怎麼處理呢?試一下,可以看到類似如下的數據。
aaa,bbb,ccc,ddd
aaa2,bbb2,,ddd2
aaa3,bbb3,ccc3,
aaa4,"bb,b4",ccc4,ddd4
bb,b4被用雙引號包圍了,這樣,我們自己在生成CSV文件的時候,可以模仿EXCEL的操作,把所有的數據都用雙引號包圍。這時候又出現一個問題,如果數據中有雙引號,會怎麼樣?再次嘗試一下。這次輸入的數據是bb,b"4",結果是:
aaa,bbb,ccc,ddd
aaa2,bbb2,,ddd2
aaa3,bbb3,ccc3,
aaa4,"bb,b""4""",ccc4,ddd4
雙引號被用2個雙引號替換了。Excel是這麼處理的,我們在生成Excel文件的時候可以模仿處理。這樣分析數據的時候,就要有一個嚴格的算法來進行分析。
由於做的這個類是給web開發用的,我們可以考慮用web常用的轉意,將"這個字符轉換成"來避免這樣的衝突,這樣處理的好處是分析字符串的時候,處理簡單化了。但是這又引發了別的問題,就是如果數據中原來就有"這樣的字符,在將"反轉義爲"的時候,容易把這些原有的字符也轉化了。所以&符號也需要轉義。
現在將2個方法折衷,即CSV數據以半角逗號分隔,以"包圍。數據中的&,"符號進行轉義。
這樣的處理,將分析數據的算法難度降低,同時也解決了數據中含有半角逗號,引號的問題。

經過以上的分析,我們可以寫CSV生成分析文件的類了。
首先,寫出簡單的轉意靜態方法。

public static String CSVEncode(String in) {
 if (in == null)
  return "";
 in.replaceAll("&", "&amp;");
 in.replaceAll("/"", "&quot;");
 return in;
}

public static String CSVDecode(String in) {
 if (in == null)
  return "";
 in.replaceAll("&quot;", "/"");
 in.replaceAll("&amp;", "&");
 return in;
}

CSV文件生成類:

package com.vtradex.swms.server.service.receiving.pojo;

import java.io.FileOutputStream;
import java.io.IOException;
import com.vogoal.util.UtilCla;

/**
 * @author SinNeR http://bbs.blueidea.com
 *
 * CSVCreater
 */
public class CSVCreater {
 private FileOutputStream fos = null;

 private StringBuffer sb = null;

 private boolean convertFlag = false;

 public static final String DEL_CHAR = ",";

 public static final String AV_CHAR = "/"";

 public CSVCreater(String arg) throws IOException {
  fos = new FileOutputStream(arg, false);
  sb = new StringBuffer();
 }

 public void setData(String data) {
  if (convertFlag)
   data = UtilCla.CSVEncode(data);
  sb.append(AV_CHAR);
  sb.append(data);
  sb.append(AV_CHAR);
  sb.append(DEL_CHAR);
 }

 public void setConvertFlag(boolean b) {
  convertFlag = b;
 }

 public void writeLine() {
  if (sb.charAt(sb.length() - 1) == ',')
   sb.delete(sb.length() - 1, sb.length());
  sb.append("/r/n");
 }

 public void writeDataByLine(String[] args) {
  for (int i = 0; i < args.length; i++)
   setData(args[i]);
  writeLine();
 }

 public void close() throws IOException {
  try {
   fos.write(sb.toString().getBytes());
  } catch (IOException e) {
   throw e;
  } finally {
   fos.close();
  }
 }

 public static void main(String[] args) {
  try {
   CSVCreater csvCre = new CSVCreater("C://test.csv");
   csvCre.setConvertFlag(true);
   csvCre.setData("aaa");
   csvCre.setData("aa,a");
   csvCre.writeLine();
   csvCre.setData("aa/"a");
   csvCre.setData("aa,a");
   csvCre.setData("aa,a");
   csvCre.writeLine();
   csvCre.setData("aa/"a");
   csvCre.setData("aa,/"a");
   csvCre.setData("aa,/"a");
   csvCre.setData("aa,/"a");
   csvCre.setData("aa,/"a");
   csvCre.writeLine();
   csvCre.close();
  } catch (IOException e) {
   e.printStackTrace();
  }
 }
}

CSV文件分析類:

package com.vogoal.util.csv;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import com.vogoal.util.UtilCla;
/**
* @author SinNeR
* http://bbs.blueidea.com
*
* CSVAnalysis
*/
public class CSVAnalysis {
 private InputStreamReader fr = null;

 private boolean convertFlag = false;

 private ArrayList dataContainer = new ArrayList();

 public static final String DEL_CHAR = ",";

 public static final String AV_CHAR = "/"";

 public CSVAnalysis(String f) throws IOException {
  fr = new InputStreamReader(new FileInputStream(f));
 }

 public void setConvertFlag(boolean b) {
  convertFlag = b;
 }

 public ArrayList analysis() throws IOException {
  BufferedReader br = new BufferedReader(fr);
  String rec = null;
  try {
   while ((rec = br.readLine()) != null) {
    ArrayList alLine = analysisLine(rec);
    dataContainer.add(alLine);
   }
  } catch (IOException e) {
   throw e;
  } finally {
   br.close();
  }
  return dataContainer;
 }

 private ArrayList analysisLine(String strLine) {
  System.out.println(strLine);
  ArrayList al = new ArrayList();
  String[] dataArr = strLine.split(AV_CHAR);
  for (int i = 1; i < dataArr.length; i = i + 2) {
   if (convertFlag){
    al.add(UtilCla.CSVDecode(dataArr[i]));
   }   
   else
    al.add(dataArr[i]);
  }
  return al;
 }

 public void close() throws IOException {
  fr.close();
 }

 public static void main(String[] args) {
  try {
   CSVAnalysis csvAna = new CSVAnalysis("C://test.csv");
   csvAna.setConvertFlag(true);
   ArrayList al = csvAna.analysis();
   for (int i = 0; i < al.size(); i++) {
    ArrayList al1 = (ArrayList) al.get(i);
    for (int j = 0; j < al1.size(); j++) {
     System.out.println(al1.get(j));
    }
   }
   csvAna.close();
  } catch (IOException e) {
   e.printStackTrace();
  }
 }
}

寫好這些類之後,就可以開始着手測試了。
寫一個測試CSV文件生成的jsp文件。如:

<%@ page contentType="text/html" import="com.vogoal.util.*,com.vogoal.util.csv.*,java.io.IOException" %>
create a csv file
<%
try {
CSVCreater csvCre = new CSVCreater("C://test.csv");
csvCre.setConvertFlag(true);
csvCre.setData("aaa");
csvCre.setData("aa,a");
csvCre.writeLine();
csvCre.setData("aa/"a");
csvCre.setData("aa,a");
csvCre.setData("aa,a");
csvCre.writeLine();
csvCre.setData("aa/"a");
csvCre.setData("aa,/"a");
csvCre.setData("aa,/"a");
csvCre.setData("aa,/"a");
csvCre.setData("aa,/"a");
csvCre.writeLine();
csvCre.close();
} catch (IOException e) {
e.printStackTrace();
}
%>

寫一個測試CSV文件分析的jsp文件。如:

<%@ page contentType="text/html" import="com.vogoal.util.*,com.vogoal.util.csv.*,java.io.IOException,
java.util.ArrayList" %>
analysis a csv file<br>
<%
try {
CSVAnalysis csvAna = new CSVAnalysis("C://test.csv");
csvAna.setConvertFlag(true);
ArrayList al = csvAna.analysis();
for (int i = 0; i < al.size(); i++) {
out.println( (i + 1) + " line start<br>");
ArrayList al1 = (ArrayList) al.get(i);
for (int j = 0; j < al1.size(); j++) {
out.println(al1.get(j));
out.println("<br>");
}
out.println( (i + 1) + " line end<br>");
}
csvAna.close();
} catch (IOException e) {
e.printStackTrace();
}
%>

將編譯後的class拷貝到TOMCAT自己的應用的WEB-INF下。將jsp文件放到自己的應用下。
然後啓動TOMCAT,訪問jsp文件,當訪問creCSV.jsp的時候,正常情況下可以看到C盤根目錄下生成了一個test.csv文件。然後訪問anaCSV.jsp文件,可以看到分析後的數據被打印出來。

至此,csv生成,分析類做成。

使用幫助:

CSVCreater.java類,用來生成CSV文件的類。
構造函數public CSVCreater(String arg) throws IOException
參數:arg 要生成的csv文件的絕對路徑
使用例CSVCreater csvCre = new CSVCreater("C://test.csv");

public void setConvertFlag(boolean b)
是否轉義設定函數(將半角雙引號進行轉義處理)
參數:true 需要轉義(推薦) false 不轉義

public void setData(String data)
添加單個數據的函數

public void writeLine()
結束換行函數

public void writeDataByLine(String[] args)
將一個數組的元素添加到一行並換行函數

public void close() throws IOException
必須調用的函數,寫入文件並關閉文件處理的對象。

例子可參考jsp文件中的代碼,但是jsp文件中的close()方法是在try塊中執行的,不推薦,使用的時候還是在finally塊中執行要安全一些。
這個類涉及到了文件的操作,有可能出現IOException,出現異常的時候會拋出給使用者。

CSVAnalysis.java
構造函數 public CSVAnalysis(String f) throws IOException
參數是要分析的文件的絕對路徑。

public ArrayList analysis() throws IOException
分析流處理的方法,返回ArrayList對象。返回的ArrayList的數據格式是
ArrayList中存放着ArrayList對象,存放的每個ArrayList對象對應csv文件的一行。
一行對應的ArrayList中存放着String對象,爲該行中所有的數據。

public void close() throws IOException
必須調用的函數,關閉文件處理的對象。
同樣,這個方法應在finally塊中執行要安全一些。

源碼下載:CSVproc.zip
源碼爲上面提到過的所有的類和jsp文件

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