首先看一下csv文件的規則:
csv(Comma Separate Values)文件即逗號分隔符文件,它是一種文本文件,可以直接以文本打開,以逗號分隔。windows默認用excel打開。它的格式包括以下幾點(它的格式最好就看excel是如何解析的。):
①每條記錄占一行;
②以逗號為分隔符;
③逗號前后的空格會被忽略;
④字段中包含有逗號,該字段必須用雙引號括起來;
⑤字段中包含有換行符,該字段必須用雙引號括起來;
⑥字段前后包含有空格,該字段必須用雙引號括起來;
⑦字段中的雙引號用兩個雙引號表示;
⑧字段中如果有雙引號,該字段必須用雙引號括起來;
⑨第一條記錄,可以是字段名;
⑩以上提到的逗號和雙引號均為半角字符。
下面給出一種解析方法,來自:http://blog.csdn.net/studyvcmfc/article/details/6232770,原文中有一些bug,經過修改,測試ok的代碼如下:
該解析算法的解析規則與excel或者wps大致相同。另外包含去掉注釋的方法。
構建方法該類包含一個構建方法,參數為要讀取的csv文件的文件名(包含絕對路徑)。
普通方法:
① getVContent():一個得到當前行的值向量的方法。如果調用此方法前未調用readCSVNextRecord方法,則將返回Null。
② getLineContentVector():一個得到下一行值向量的方法。如果該方法返回Null,則說明已經讀到文件末尾。
③ close():關閉流。該方法為調用該類后應該被最后調用的方法。
④ readCSVNextRecord():該方法讀取csv文件的下一行,如果該方法已經讀到了文件末尾,則返回false;
⑤ readAtomString(String):該方法返回csv文件邏輯一行的第一個值,和該邏輯行第一個值后面的內容,如果該內容以逗號開始,則已經去掉了該逗號。這兩個值以一個二維數組的方法返回。
⑥ isQuoteAdjacent(String):判斷一個給定字符串的引號是否兩兩相鄰。如果兩兩相鄰,返回真。如果該字符串不包含引號,也返回真。
⑦ readCSVFileTitle():該方法返回csv文件中的第一行——該行不以#號開始(包括正常解析后的#號),且該行不為空
解析接口代碼:
- import java.io.BufferedReader;
- import java.io.FileNotFoundException;
- import java.io.FileReader;
- import java.io.IOException;
- import java.util.Vector;
- public class CsvParse {
- //聲明讀取流
- private BufferedReader inStream = null;
- //聲明返回向量
- private Vector<String> vContent = null;
- /**
- * 構建方法,參數為csv文件名<br>
- * 如果沒有找到文件,則拋出異常<br>
- * 如果拋出異常,則不能進行頁面的文件讀取操作
- */
- public CsvParse(String csvFileName) throws FileNotFoundException {
- inStream = new BufferedReader(new FileReader(csvFileName));
- }
- /**
- * 返回已經讀取到的一行的向量
- * @return vContent
- */
- public Vector<String> getVContent() {
- return this.vContent;
- }
- /**
- * 讀取下一行,並把該行的內容填充入向量中<br>
- * 返回該向量<br>
- * @return vContent 裝載了下一行的向量
- * @throws IOException
- * @throws Exception
- */
- public Vector<String> getLineContentVector() throws IOException, Exception {
- if (this.readCSVNextRecord()) {
- return this.vContent;
- }
- return null;
- }
- /**
- * 關閉流
- */
- public void close() {
- if (inStream != null) {
- try {
- inStream.close();
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- }
- /**
- * 調用此方法時應該確認該類已經被正常初始化<br>
- * 該方法用於讀取csv文件的下一個邏輯行<br>
- * 讀取到的內容放入向量中<br>
- * 如果該方法返回了false,則可能是流未被成功初始化<br>
- * 或者已經讀到了文件末尾<br>
- * 如果發生異常,則不應該再進行讀取
- * @return 返回值用於標識是否讀到文件末尾
- * @throws Exception
- */
- public boolean readCSVNextRecord() throws IOException, Exception {
- //如果流未被初始化則返回false
- if (inStream == null) {
- return false;
- }
- //如果結果向量未被初始化,則初始化
- if (vContent == null) {
- vContent = new Vector<String>();
- }
- //移除向量中以前的元素
- vContent.removeAllElements();
- //聲明邏輯行
- String logicLineStr = “”;
- //用於存放讀到的行
- StringBuilder strb = new StringBuilder();
- //聲明是否為邏輯行的標志,初始化為false
- boolean isLogicLine = false;
- try {
- while (!isLogicLine) {
- String newLineStr = inStream.readLine();
- if (newLineStr == null) {
- strb = null;
- vContent = null;
- isLogicLine = true;
- break;
- }
- if (newLineStr.startsWith(“#”)) {
- // 去掉注釋
- continue;
- }
- if (!strb.toString().equals(“”)) {
- strb.append(“/r/n”);
- }
- strb.append(newLineStr);
- String oldLineStr = strb.toString();
- if (oldLineStr.indexOf(“,”) == -1) {
- // 如果該行未包含逗號
- if (containsNumber(oldLineStr, “\”") % 2 == 0) {
- // 如果包含偶數個引號
- isLogicLine = true;
- break;
- } else {
- if (oldLineStr.startsWith(“\”")) {
- if (oldLineStr.equals(“\”")) {
- continue;
- } else {
- String tempOldStr = oldLineStr.substring(1);
- if (isQuoteAdjacent(tempOldStr)) {
- // 如果剩下的引號兩兩相鄰,則不是一行
- continue;
- } else {
- // 否則就是一行
- isLogicLine = true;
- break;
- }
- }
- }
- }
- } else {
- // quotes表示復數的quote
- String tempOldLineStr = oldLineStr.replace(“\”\”", “”);
- int lastQuoteIndex = tempOldLineStr.lastIndexOf(“\”");
- if (lastQuoteIndex == 0) {
- continue;
- } else if (lastQuoteIndex == -1) {
- isLogicLine = true;
- break;
- } else {
- tempOldLineStr = tempOldLineStr.replace(“\”,\”", “”);
- lastQuoteIndex = tempOldLineStr.lastIndexOf(“\”");
- if (lastQuoteIndex == 0) {
- continue;
- }
- if (tempOldLineStr.charAt(lastQuoteIndex - 1) == ’,') {
- continue;
- } else {
- isLogicLine = true;
- break;
- }
- }
- }
- }
- } catch (IOException ioe) {
- ioe.printStackTrace();
- //發生異常時關閉流
- if (inStream != null) {
- inStream.close();
- }
- throw ioe;
- } catch (Exception e) {
- e.printStackTrace();
- //發生異常時關閉流
- if (inStream != null) {
- inStream.close();
- }
- throw e;
- }
- if (strb == null) {
- // 讀到行尾時為返回
- return false;
- }
- //提取邏輯行
- logicLineStr = strb.toString();
- if (logicLineStr != null) {
- //拆分邏輯行,把分離出來的原子字符串放入向量中
- while (!logicLineStr.equals(“”)) {
- String[] ret = readAtomString(logicLineStr);
- String atomString = ret[0];
- logicLineStr = ret[1];
- vContent.add(atomString);
- }
- }
- return true;
- }
- /**
- * 讀取一個邏輯行中的第一個字符串,並返回剩下的字符串<br>
- * 剩下的字符串中不包含第一個字符串后面的逗號<br>
- * @param lineStr 一個邏輯行
- * @return 第一個字符串和剩下的邏輯行內容
- */
- public String[] readAtomString(String lineStr) {
- String atomString = “”;//要讀取的原子字符串
- String orgString = “”;//保存第一次讀取下一個逗號時的未經任何處理的字符串
- String[] ret = new String[2];//要返回到外面的數組
- boolean isAtom = false;//是否是原子字符串的標志
- String[] commaStr = lineStr.split(“,”);
- while (!isAtom) {
- for (String str : commaStr) {
- if (!atomString.equals(“”)) {
- atomString = atomString + “,”;
- }
- atomString = atomString + str;
- orgString = atomString;
- if (!isQuoteContained(atomString)) {
- // 如果字符串中不包含引號,則為正常,返回
- isAtom = true;
- break;
- } else {
- if (!atomString.startsWith(“\”")) {
- // 如果字符串不是以引號開始,則表示不轉義,返回
- isAtom = true;
- break;
- } else if (atomString.startsWith(“\”")) {
- // 如果字符串以引號開始,則表示轉義
- if (containsNumber(atomString, “\”") % 2 == 0) {
- // 如果含有偶數個引號
- String temp = atomString;
- if (temp.endsWith(“\”")) {
- temp = temp.replace(“\”\”", “”);
- if (temp.equals(“”)) {
- // 如果temp為空
- atomString = “”;
- isAtom = true;
- break;
- } else {
- // 如果temp不為空,則去掉前后引號
- temp = temp.substring(1, temp
- .lastIndexOf(“\”"));
- if (temp.indexOf(“\”") > -1) {
- // 去掉前后引號和相鄰引號之后,若temp還包含有引號
- // 說明這些引號是單個單個出現的
- temp = atomString;
- temp = temp.substring(1);
- temp = temp.substring(0, temp
- .indexOf(“\”"))
- + temp.substring(temp
- .indexOf(“\”") + 1);
- atomString = temp;
- isAtom = true;
- break;
- } else {
- // 正常的csv文件
- temp = atomString;
- temp = temp.substring(1, temp
- .lastIndexOf(“\”"));
- temp = temp.replace(“\”\”", “\”");
- atomString = temp;
- isAtom = true;
- break;
- }
- }
- } else {
- // 如果不是以引號結束,則去掉前兩個引號
- temp = temp.substring(1, temp.indexOf(‘\“‘, 1))
- + temp
- .substring(temp
- .indexOf(‘\”‘, 1) + 1);
- atomString = temp;
- isAtom = true;
- break;
- }
- } else {
- // 如果含有奇數個引號
- // TODO 處理奇數個引號的情況
- if (!atomString.equals(“\“”)) {
- String tempAtomStr = atomString.substring(1);
- if (!isQuoteAdjacent(tempAtomStr)) {
- // 這里做的原因是,如果判斷前面的字符串不是原子字符串的時候就讀取第一個取到的字符串
- // 后面取到的字符串不計入該原子字符串
- tempAtomStr = atomString.substring(1);
- int tempQutoIndex = tempAtomStr
- .indexOf(“\”");
- // 這里既然有奇數個quto,所以第二個quto肯定不是最后一個
- tempAtomStr = tempAtomStr.substring(0,
- tempQutoIndex)
- + tempAtomStr
- .substring(tempQutoIndex + 1);
- atomString = tempAtomStr;
- isAtom = true;
- break;
- }
- }
- }
- }
- }
- }
- }
- //先去掉之前讀取的原字符串的母字符串
- if (lineStr.length() > orgString.length()) {
- lineStr = lineStr.substring(orgString.length());
- } else {
- lineStr = “”;
- }
- //去掉之后,判斷是否以逗號開始,如果以逗號開始則去掉逗號
- if (lineStr.startsWith(“,”)) {
- if (lineStr.length() > 1) {
- lineStr = lineStr.substring(1);
- } else {
- lineStr = “”;
- }
- }
- ret[0] = atomString;
- ret[1] = lineStr;
- return ret;
- }
- /**
- * 該方法取得父字符串中包含指定字符串的數量<br>
- * 如果父字符串和字字符串任意一個為空值,則返回零
- * @param parentStr
- * @param parameter
- * @return
- */
- public int containsNumber(String parentStr, String parameter) {
- int containNumber = 0;
- if (parentStr == null || parentStr.equals(“”)) {
- return 0;
- }
- if (parameter == null || parameter.equals(“”)) {
- return 0;
- }
- for (int i = 0; i < parentStr.length(); i++) {
- i = parentStr.indexOf(parameter, i);
- if (i > -1) {
- i = i + parameter.length();
- i–;
- containNumber = containNumber + 1;
- } else {
- break;
- }
- }
- return containNumber;
- }
- /**
- * 該方法用於判斷給定的字符串中的引號是否相鄰<br>
- * 如果相鄰返回真,否則返回假<br>
- *
- * @param p_String
- * @return
- */
- public boolean isQuoteAdjacent(String p_String) {
- boolean ret = false;
- String temp = p_String;
- temp = temp.replace(“\”\”", “”);
- if (temp.indexOf(“\”") == -1) {
- ret = true;
- }
- // TODO 引號相鄰
- return ret;
- }
- /**
- * 該方法用於判斷給定的字符串中是否包含引號<br>
- * 如果字符串為空或者不包含返回假,包含返回真<br>
- *
- * @param p_String
- * @return
- */
- public boolean isQuoteContained(String p_String) {
- boolean ret = false;
- if (p_String == null || p_String.equals(“”)) {
- return false;
- }
- if (p_String.indexOf(“\”") > -1) {
- ret = true;
- }
- return ret;
- }
- /**
- * 讀取文件標題
- *
- * @return 正確讀取文件標題時返回 true,否則返回 false
- * @throws Exception
- * @throws IOException
- */
- public boolean readCSVFileTitle() throws IOException, Exception {
- String strValue = “”;
- boolean isLineEmpty = true;
- do {
- if (!readCSVNextRecord()) {
- return false;
- }
- if (vContent.size() > 0) {
- strValue = (String) vContent.get(0);
- }
- for (String str : vContent) {
- if (str != null && !str.equals(“”)) {
- isLineEmpty = false;
- break;
- }
- }
- // csv 文件中前面幾行以 # 開頭為注釋行
- } while (strValue.trim().startsWith(“#”) || isLineEmpty);
- return true;
- }
- }
其實呢。。。。作為一個標准,CSV的解析應該更簡單,這里大放送,JVM的CSV解析結合:
http://ostermiller.org/utils/CSV.html
經測試,如下實例是最好用的:
https://github.com/segasai/SAI-CAS/tree/master/src/sai_cas/input/csv
from: http://jacoxu.com/?p=1490