天天看點

支援各種特殊字元的 CSV 解析類 (.net 實作)(C#讀寫CSV檔案)

CSV是一種十分簡潔的資料結構,在DOTNET平台實際使用中發現微軟官方并沒有提供預設的方法,而網上好多例子發現實作并不嚴謹甚至一些含有明顯錯誤,是以後面自己實作了一個讀寫工具類,這裡發出來希望友善後面朋友(難免還是會有考慮不到的地方,可随時郵件聯系)
 使用該工具可對csv檔案進行讀寫(甚至不用去了解CSV的各種規範)
 直接以List<List<string>> 形式輸出,友善進一步處理
 因為工具類需要讀取檔案資源讀取完畢後如果确認不會再次讀取,建議立即Dispose,以釋放檔案句柄(寫入使用該類的靜态資源,Dispose不會對其有影響)
        

   讀

CsvFileHelper myCsv = new CsvFileHelper(@"C:\Users\administer\Desktop\my6.csv", Encoding.UTF8);
var myData = myCsv.GetListCsvData();
//其他的操作
myCsv.Dispose();       
CsvFileHelper.SaveCsvFile(@"C:\Users\administer\Desktop\my9.csv", myData, true, new System.Text.UTF8Encoding(false));      
單個元素支援包括tab,換行回車(\r\n),空内容等在内的所有文本字元 (在使用時請确定檔案的編碼方式)
 可指定元素分割符,行分隔符官方必須為\r\n(\r\n可以作為内容出現在元素中),轉義字元必須為".
 轉義所有的引号必須出現在首尾(如果不在首尾,則不會按轉義符處理,直接作為引号處理)[excel可以讀取轉義出現在中間的情況,而本身存儲不會使用這種方式,儲存時并會強制修複這種異常,是以這裡遇到中間轉義的情況直接抛出指定異常]
 如果在被轉義的情況下需要出現引号,則使用2個引号代替(如果需要在首部使用雙引号,則需要轉義該元素,其他地方可直接使用)(excel對所有雙引号都進行轉義,無論其出現位置,對于儲存方式可以選擇是否按excel的方式進行儲存)
 每一行的結尾是補需要逗号結束的,如果多加一個逗号則辨別該行會多一個空元素
 使用問題或疑問可通過[email protected]進行聯系      

下面是有關CSV的介紹(若僅需要使用可忽略不用理會)

csv(Comma Separated Values)逗号分隔值,有時也稱為字元分隔值,因為分隔字元也可以不是逗号),其檔案以純文字形式存儲表格資料(數字和文本)。純文字意味着該檔案是一個字元序列,不含必須象二進制數字那樣被解讀的資料。CSV檔案由任意數目的記錄組成,記錄間以某種換行符分隔;每條記錄由字段組成,字段間的分隔符是其它字元或字元串,最常見的是逗号或制表符。通常,所有記錄都有完全相同的字段序列。CSV是一種Excel表格的導出格式,在Excel表格的菜單欄中點選檔案->另存為會彈出一個檔案夾浏覽視窗,在下拉框中可以選擇儲存格式,其中有一個就是.CSV(逗号分隔符)選項。

CSV是一種通用的、相對簡單的檔案格式,被使用者、商業和科學廣泛應用。最廣泛的應用是在程式之間轉移表格資料。因為大量程式都支援某種CSV變體,至少是作為一種可選擇的輸入/輸出格式。例如,一個使用者可能需要交換資訊,從一個以私有格式存儲資料的資料庫程式,到一個資料格式完全不同的電子表格。最可能的情況是,該資料庫程式可以導出資料為“CSV”,然後被導出的CSV檔案可以被電子表格程式導入。

“CSV”并不是一種單一的、定義明确的格式(盡管RFC 4180有一個被通常使用的定義)。是以在實踐中,術語“CSV”泛指具有以下特征的任何檔案:

  • 純文字,使用某個字元集,比如ASCII、Unicode、EBCDIC或GB2312;
  • 由記錄組成(典型的是每行一條記錄);
  • 每條記錄被分隔符分隔為字段(典型分隔符有逗号、分号或制表符;有時分隔符可以包括可選的空格);
  • 每條記錄都有同樣的字段序列。

在這些正常的限制條件下,存在着許多CSV變體,故CSV檔案并不完全互通

逗号分隔列(CSL)是一種資料格式,起初在最古老的簡單電腦中被稱為逗号分隔值(CSV)。

CSL/CSV被用來作為簡單的資料庫。一些早期的軟體應用,比如文字處理器,允許一系列“變量資料”在兩個檔案之間被合并:一個是模闆檔案,一個是包含姓名、位址和其它資料字段的CSL資料庫。許多應用程式仍然有這種能力。

逗号分隔列過去和現在都被用于在兩個不同架構的機器之間交換資料庫資訊。純文字的CSV檔案大幅避免了不相容性,比如位元組順序和字長。這些檔案大部分是可讀的,是以在沒有完美的文檔或通訊的情況下仍然很容易處理。

最後附上工具類代碼如下:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;


/*******************************************************************************
* Copyright (c) 2016 lijie
* All rights reserved.
* 
* 檔案名稱: 
* 内容摘要: [email protected]
* 
* 曆史記錄:
* 日      期:   201601010          建立人: lulianqi
* 描    述: 建立
*******************************************************************************/

namespace MyCommonHelper.FileHelper
{
    /// <summary>
    /// 單個元素支援包括tab,換行回車(\r\n),空内容等在内的所有文本字元 (在使用時請确定檔案的編碼方式)
    /// 可指定元素分割符,換行官方必須為\r\n(\r\n可以作為内容出現在元素中),轉義字元必須為".
    /// 轉義所有的引号必須出現在首尾(如果不在首尾,則不會按轉義符處理,直接作為引号處理)[excel可以讀取轉義出現在中間的情況,而本身存儲不會使用這種方式,儲存時并會強制修複這種異常,是以這裡遇到中間轉義的情況直接抛出指定異常]
    /// 如果在被轉義的情況下需要出現引号,則使用2個引号代替(如果需要在首部使用雙引号,則需要轉義該元素,其他地方可直接使用)(excel對所有雙引号都進行轉義,無論其出現位置,對于儲存方式可以選擇是否按excel的方式進行儲存)
    /// 每一行的結尾是不需要逗号結束的,如果多加一個逗号則辨別該行會多一個空元素
    /// 空行也是一個空元素,一個逗号是2個空元素,是以不可能出現有的行元素為空
    /// 使用問題或疑問可通過[email protected]進行聯系
    /// </summary>
    public sealed class CsvFileHelper : IDisposable
    {

        #region Members

        //private FileStream _fileStream;
        private Stream _stream;
        private StreamReader _streamReader;
        //private StreamWriter _streamWriter;
        //private Stream _memoryStream;
        private Encoding _encoding;
        //private readonly StringBuilder _columnBuilder = new StringBuilder(100);
        private Type _type = Type.File;
        private bool _trimColumns = false;

        private char _csvSeparator = ',';


        #endregion Members

        #region Properties

        /// <summary>
        /// Gets or sets whether column values should be trimmed
        /// </summary>
        public bool TrimColumns
        {
            get { return _trimColumns; }
            set { _trimColumns = value; }
        }

        public Type DataSouceType
        {
            get{ return _type;}
        }

        /// <summary>
        /// get or set Csv Separator (Default Values is ,)
        /// </summary>
        public char CsvSeparator
        {
            get { return _csvSeparator; }
            set { _csvSeparator = value; }
        }
        #endregion Properties

        #region Enums

        /// <summary>
        /// Type enum
        /// </summary>
        public enum Type
        {
            File,
            Stream
        }

        #endregion Enums

        #region Methods
        
        /// <summary>
        /// Initialises the reader to work from a file
        /// </summary>
        /// <param name="filePath">File path</param>
        public CsvFileHelper(string filePath):this(filePath, Encoding.Default)
        {
        }

        /// <summary>
        /// Initialises the reader to work from a file
        /// </summary>
        /// <param name="filePath">File path</param>
        /// <param name="encoding">Encoding</param>
        public CsvFileHelper(string filePath, Encoding encoding)
        {
            _type = Type.File;
            if (!File.Exists(filePath))
            {
                throw new FileNotFoundException(string.Format("The file '{0}' does not exist.", filePath));
            }
            //_stream = File.OpenRead(filePath); //return a FileStream   (OpenRead 源碼就是 return new FileStream(path, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None);)
            _stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
            _stream.Position = 0;
            _encoding = (encoding ?? Encoding.Default);
            _streamReader = new StreamReader(_stream, _encoding);
        }

       
        /// <summary>
        /// Initialises the reader to work from an existing stream
        /// </summary>
        /// <param name="stream">Stream  ( new MemoryStream(Encoding.Default.GetBytes(csvString ?? "")))</param>
        public CsvFileHelper(Stream stream):this(stream, Encoding.Default)
        {
        }

        /// <summary>
        /// Initialises the reader to work from an existing stream
        /// </summary>
        /// <param name="stream">Stream</param>
        /// <param name="encoding">Encoding</param>
        public CsvFileHelper(Stream stream, Encoding encoding)
        {
            _type = Type.Stream;
            if (stream == null)
            {
                throw new ArgumentNullException("The supplied stream is null.");
            }
            _stream = stream;
            _stream.Position = 0;
            _encoding = (encoding ?? Encoding.Default);
            _streamReader = new StreamReader(_stream, _encoding);
        }

        /// <summary>
        /// Initialises the reader to work from an existing stream (with the Separator char)
        /// </summary>
        /// <param name="stream">Stream</param>
        /// <param name="encoding">Encoding</param>
        /// <param name="yourSeparator"> the Separator char</param>
        public CsvFileHelper(Stream stream, Encoding encoding, char yourSeparator): this(stream, encoding)
        {
            CsvSeparator = yourSeparator;
        }

        /// <summary>
        /// Initialises the reader to work from an existing string
        /// </summary>
        /// <param name="useStringCsv">just set it null</param>
        /// <param name="csvString">csv string</param>
        public CsvFileHelper(object useStringCsv , string csvString)
            : this(new MemoryStream(Encoding.Default.GetBytes(csvString ?? "")), Encoding.Default)
        {
            
        }

        /// <summary>
        /// Initialises the reader to work from an existing string
        /// </summary>
        /// <param name="useStringCsv">just set it null</param>
        /// <param name="csvString">csv string</param>
        /// <param name="yourSeparator"></param>
        public CsvFileHelper(object useStringCsv,string csvString, char yourSeparator)
            : this(new MemoryStream(Encoding.Default.GetBytes(csvString ?? "")), Encoding.Default)
        {
            CsvSeparator = yourSeparator;
        }


        private List<string> ParseLine(string line)
        {
            StringBuilder _columnBuilder = new StringBuilder();
            List<string> Fields = new List<string>();
            bool inColumn = false;  //是否是在一個列元素裡
            bool inQuotes = false;  //是否需要轉義
            bool isNotEnd = false;  //讀取完畢未結束轉義
            _columnBuilder.Remove(0, _columnBuilder.Length);


            // Iterate through every character in the line
            for (int i = 0; i < line.Length; i++)
            {
                char character = line[i];

                // If we are not currently inside a column
                if (!inColumn)
                {
                    // If the current character is a double quote then the column value is contained within
                    // double quotes, otherwise append the next character
                    inColumn = true;
                    if (character == '"')
                    {
                        inQuotes = true;
                        continue;
                    }
                    
                }

                // If we are in between double quotes
                if (inQuotes)
                {
                    if ((i + 1) == line.Length)//這個字元已經結束了整行
                    {
                        if (character == '"') //正常轉義結束,且該行已經結束
                        {
                            inQuotes = false;
                            continue;     //目前字元不用添加,跳出後直結束後會添加該元素
                        }
                        else //異常結束,轉義未收尾
                        {
                            isNotEnd = true;
                        }
                    }
                    else if (character == '"' && line[i + 1] == _csvSeparator) //結束轉義,且後面有可能還有資料
                    {
                        inQuotes = false;
                        inColumn = false;
                        i++; //跳過下一個字元
                    }
                    else if (character == '"' && line[i + 1] == '"') //雙引号轉義
                    {
                        i++; //跳過下一個字元
                        if (line.Length - 1 == i)//異常結束,轉義未收尾
                        {
                            isNotEnd = true;
                        }
                    }
                    else if (character == '"') //雙引号單獨出現(這種情況實際上已經是格式錯誤,為了相容可暫時不處理)
                    {
                        throw new Exception(string.Format("[{0}]:格式錯誤,錯誤的雙引号轉義 near [{1}] ","ParseLine", line));
                    }
                    //其他情況直接跳出,後面正常添加

                }
                else if (character == _csvSeparator)
                    inColumn = false;

                // If we are no longer in the column clear the builder and add the columns to the list
                if (!inColumn) //結束該元素時inColumn置為false,并且不處理目前字元,直接進行Add
                {
                    Fields.Add(TrimColumns ? _columnBuilder.ToString().Trim() : _columnBuilder.ToString());
                    _columnBuilder.Remove(0, _columnBuilder.Length);
                   
                }
                else // append the current column
                    _columnBuilder.Append(character);
            }

            // If we are still inside a column add a new one (标準格式一行結尾不需要逗号結尾,而上面for是遇到逗号才添加的,為了相容最後還要添加一次)
            if (inColumn)
            {
                if (isNotEnd)
                {
                    _columnBuilder.Append("\r\n");
                }
                Fields.Add(TrimColumns ? _columnBuilder.ToString().Trim() : _columnBuilder.ToString());
            }
            //如果inColumn為false,說明已經添加,因為最後一個字元為分隔符,是以後面要加上一個空元素
            //另外一種情況是line為""空行,(空行也是一個空元素,一個逗号是2個空元素),正好inColumn為預設值false,在此處添加一空元素
            else  
            {
                Fields.Add("");
            }


            return Fields;
        }

        /// <summary>
        /// 處理未完成的Csv單行
        /// </summary>
        /// <param name="line">資料源</param>
        /// <returns>元素清單</returns>
        private List<string> ParseContinueLine(string line)
        {
            StringBuilder _columnBuilder = new StringBuilder();
            List<string> Fields = new List<string>();
            _columnBuilder.Remove(0, _columnBuilder.Length);
            if (line == "")
            {
                Fields.Add("\r\n");
                return Fields;
            }

            for (int i = 0; i < line.Length; i++)
            {
                char character = line[i];

                if ((i + 1) == line.Length)//這個字元已經結束了整行
                {
                    if (character == '"') //正常轉義結束,且該行已經結束
                    {
                        Fields.Add(TrimColumns ? _columnBuilder.ToString().TrimEnd() : _columnBuilder.ToString());
                        return Fields;
                    }
                    else //異常結束,轉義未收尾
                    {
                        _columnBuilder.Append("\r\n");
                        Fields.Add(_columnBuilder.ToString());
                        return Fields;
                    }
                }
                else if (character == '"' && line[i + 1] == _csvSeparator) //結束轉義,且後面有可能還有資料
                {
                    Fields.Add(TrimColumns ? _columnBuilder.ToString().TrimEnd() : _columnBuilder.ToString());
                    i++; //跳過下一個字元
                    Fields.AddRange(ParseLine(line.Remove(0, i+1)));
                    break;
                }
                else if (character == '"' && line[i + 1] == '"') //雙引号轉義
                {
                    i++; //跳過下一個字元
                    if (line.Length - 1 == i)//異常結束,轉義未收尾
                    {
                        _columnBuilder.Append(character);
                        _columnBuilder.Append("\r\n");
                        Fields.Add(_columnBuilder.ToString());
                        return Fields;
                    }
                    
                }
                else if (character == '"') //雙引号單獨出現(這種情況實際上已經是格式錯誤,轉義用雙引号一定是【,"】【",】形式,包含在裡面的雙引号需要使用一對雙引号進行轉義)
                {
                    throw new Exception(string.Format("[{0}]:格式錯誤,錯誤的雙引号轉義 near [{1}]", "ParseContinueLine", line));
                }
                _columnBuilder.Append(character);
            }
            return Fields;
        }

        public List<List<string>> GetListCsvData()
        {
            _stream.Position = 0;
            List<List<string>> tempListCsvData = new List<List<string>>();
            bool isNotEndLine = false;
            //這裡的ReadLine可能把轉義的/r/n分割,需要後面單獨處理
            string tempCsvRowString = _streamReader.ReadLine();
            while (tempCsvRowString!=null)
            {
                List<string> tempCsvRowList;
                if (isNotEndLine)
                {
                    tempCsvRowList = ParseContinueLine(tempCsvRowString);
                    isNotEndLine = (tempCsvRowList.Count > 0 && tempCsvRowList[tempCsvRowList.Count - 1].EndsWith("\r\n"));
                    List<string> myNowContinueList = tempListCsvData[tempListCsvData.Count - 1];
                    myNowContinueList[myNowContinueList.Count - 1] += tempCsvRowList[0];
                    tempCsvRowList.RemoveAt(0);
                    myNowContinueList.AddRange(tempCsvRowList);
                }
                else
                {
                    tempCsvRowList = ParseLine(tempCsvRowString);
                    isNotEndLine = (tempCsvRowList.Count > 0 && tempCsvRowList[tempCsvRowList.Count - 1].EndsWith("\r\n"));
                    tempListCsvData.Add(tempCsvRowList);
                }
                tempCsvRowString = _streamReader.ReadLine();
            }
            return tempListCsvData;
        }

        public void Dispose()
        {
            if(_streamReader!=null)
            {
                _streamReader.Dispose();
            }
            if(_stream!=null)
            {
                _stream.Dispose();
            }
        }

        #endregion

        #region StaticTool
    
        #region 編碼方式可接受值
        //請考慮讓應用程式使用 UTF-8 或 Unicode (UTF-16) 作為預設編碼。大多數其他編碼要麼不完整并将許多字元轉換為“?”,要麼在不同的平台上具有稍有不同的行為。非 Unicode 編碼通常具有多義性,應用程式則不再試圖确定合适的編碼,也不再提供使用者用來修複文本語言或編碼的更正下拉菜單。 
        /* 
        This code produces the following output.

        CodePage identifier and name     BrDisp   BrSave   MNDisp   MNSave   1-Byte   ReadOnly 
        37     IBM037                    False    False    False    False    True     True     
        437    IBM437                    False    False    False    False    True     True     
        500    IBM500                    False    False    False    False    True     True     
        708    ASMO-708                  True     True     False    False    True     True     
        720    DOS-720                   True     True     False    False    True     True     
        737    ibm737                    False    False    False    False    True     True     
        775    ibm775                    False    False    False    False    True     True     
        850    ibm850                    False    False    False    False    True     True     
        852    ibm852                    True     True     False    False    True     True     
        855    IBM855                    False    False    False    False    True     True     
        857    ibm857                    False    False    False    False    True     True     
        858    IBM00858                  False    False    False    False    True     True     
        860    IBM860                    False    False    False    False    True     True     
        861    ibm861                    False    False    False    False    True     True     
        862    DOS-862                   True     True     False    False    True     True     
        863    IBM863                    False    False    False    False    True     True     
        864    IBM864                    False    False    False    False    True     True     
        865    IBM865                    False    False    False    False    True     True     
        866    cp866                     True     True     False    False    True     True     
        869    ibm869                    False    False    False    False    True     True     
        870    IBM870                    False    False    False    False    True     True     
        874    windows-874               True     True     True     True     True     True     
        875    cp875                     False    False    False    False    True     True     
        932    shift_jis                 True     True     True     True     False    True     
        936    gb2312                    True     True     True     True     False    True     
        949    ks_c_5601-1987            True     True     True     True     False    True     
        950    big5                      True     True     True     True     False    True     
        1026   IBM1026                   False    False    False    False    True     True     
        1047   IBM01047                  False    False    False    False    True     True     
        1140   IBM01140                  False    False    False    False    True     True     
        1141   IBM01141                  False    False    False    False    True     True     
        1142   IBM01142                  False    False    False    False    True     True     
        1143   IBM01143                  False    False    False    False    True     True     
        1144   IBM01144                  False    False    False    False    True     True     
        1145   IBM01145                  False    False    False    False    True     True     
        1146   IBM01146                  False    False    False    False    True     True     
        1147   IBM01147                  False    False    False    False    True     True     
        1148   IBM01148                  False    False    False    False    True     True     
        1149   IBM01149                  False    False    False    False    True     True     
        1200   utf-16                    False    True     False    False    False    True     
        1201   unicodeFFFE               False    False    False    False    False    True     
        1250   windows-1250              True     True     True     True     True     True     
        1251   windows-1251              True     True     True     True     True     True     
        1252   Windows-1252              True     True     True     True     True     True     
        1253   windows-1253              True     True     True     True     True     True     
        1254   windows-1254              True     True     True     True     True     True     
        1255   windows-1255              True     True     True     True     True     True     
        1256   windows-1256              True     True     True     True     True     True     
        1257   windows-1257              True     True     True     True     True     True     
        1258   windows-1258              True     True     True     True     True     True     
        1361   Johab                     False    False    False    False    False    True     
        10000  macintosh                 False    False    False    False    True     True     
        10001  x-mac-japanese            False    False    False    False    False    True     
        10002  x-mac-chinesetrad         False    False    False    False    False    True     
        10003  x-mac-korean              False    False    False    False    False    True     
        10004  x-mac-arabic              False    False    False    False    True     True     
        10005  x-mac-hebrew              False    False    False    False    True     True     
        10006  x-mac-greek               False    False    False    False    True     True     
        10007  x-mac-cyrillic            False    False    False    False    True     True     
        10008  x-mac-chinesesimp         False    False    False    False    False    True     
        10010  x-mac-romanian            False    False    False    False    True     True     
        10017  x-mac-ukrainian           False    False    False    False    True     True     
        10021  x-mac-thai                False    False    False    False    True     True     
        10029  x-mac-ce                  False    False    False    False    True     True     
        10079  x-mac-icelandic           False    False    False    False    True     True     
        10081  x-mac-turkish             False    False    False    False    True     True     
        10082  x-mac-croatian            False    False    False    False    True     True     
        20000  x-Chinese-CNS             False    False    False    False    False    True     
        20001  x-cp20001                 False    False    False    False    False    True     
        20002  x-Chinese-Eten            False    False    False    False    False    True     
        20003  x-cp20003                 False    False    False    False    False    True     
        20004  x-cp20004                 False    False    False    False    False    True     
        20005  x-cp20005                 False    False    False    False    False    True     
        20105  x-IA5                     False    False    False    False    True     True     
        20106  x-IA5-German              False    False    False    False    True     True     
        20107  x-IA5-Swedish             False    False    False    False    True     True     
        20108  x-IA5-Norwegian           False    False    False    False    True     True     
        20127  us-ascii                  False    False    True     True     True     True     
        20261  x-cp20261                 False    False    False    False    False    True     
        20269  x-cp20269                 False    False    False    False    True     True     
        20273  IBM273                    False    False    False    False    True     True     
        20277  IBM277                    False    False    False    False    True     True     
        20278  IBM278                    False    False    False    False    True     True     
        20280  IBM280                    False    False    False    False    True     True     
        20284  IBM284                    False    False    False    False    True     True     
        20285  IBM285                    False    False    False    False    True     True     
        20290  IBM290                    False    False    False    False    True     True     
        20297  IBM297                    False    False    False    False    True     True     
        20420  IBM420                    False    False    False    False    True     True     
        20423  IBM423                    False    False    False    False    True     True     
        20424  IBM424                    False    False    False    False    True     True     
        20833  x-EBCDIC-KoreanExtended   False    False    False    False    True     True     
        20838  IBM-Thai                  False    False    False    False    True     True     
        20866  koi8-r                    True     True     True     True     True     True     
        20871  IBM871                    False    False    False    False    True     True     
        20880  IBM880                    False    False    False    False    True     True     
        20905  IBM905                    False    False    False    False    True     True     
        20924  IBM00924                  False    False    False    False    True     True     
        20932  EUC-JP                    False    False    False    False    False    True     
        20936  x-cp20936                 False    False    False    False    False    True     
        20949  x-cp20949                 False    False    False    False    False    True     
        21025  cp1025                    False    False    False    False    True     True     
        21866  koi8-u                    True     True     True     True     True     True     
        28591  iso-8859-1                True     True     True     True     True     True     
        28592  iso-8859-2                True     True     True     True     True     True     
        28593  iso-8859-3                False    False    True     True     True     True     
        28594  iso-8859-4                True     True     True     True     True     True     
        28595  iso-8859-5                True     True     True     True     True     True     
        28596  iso-8859-6                True     True     True     True     True     True     
        28597  iso-8859-7                True     True     True     True     True     True     
        28598  iso-8859-8                True     True     False    False    True     True     
        28599  iso-8859-9                True     True     True     True     True     True     
        28603  iso-8859-13               False    False    False    False    True     True     
        28605  iso-8859-15               False    True     True     True     True     True     
        29001  x-Europa                  False    False    False    False    True     True     
        38598  iso-8859-8-i              True     True     True     True     True     True     
        50220  iso-2022-jp               False    False    True     True     False    True     
        50221  csISO2022JP               False    True     True     True     False    True     
        50222  iso-2022-jp               False    False    False    False    False    True     
        50225  iso-2022-kr               False    False    True     False    False    True     
        50227  x-cp50227                 False    False    False    False    False    True     
        51932  euc-jp                    True     True     True     True     False    True     
        51936  EUC-CN                    False    False    False    False    False    True     
        51949  euc-kr                    False    False    True     True     False    True     
        52936  hz-gb-2312                True     True     True     True     False    True     
        54936  GB18030                   True     True     True     True     False    True     
        57002  x-iscii-de                False    False    False    False    False    True     
        57003  x-iscii-be                False    False    False    False    False    True     
        57004  x-iscii-ta                False    False    False    False    False    True     
        57005  x-iscii-te                False    False    False    False    False    True     
        57006  x-iscii-as                False    False    False    False    False    True     
        57007  x-iscii-or                False    False    False    False    False    True     
        57008  x-iscii-ka                False    False    False    False    False    True     
        57009  x-iscii-ma                False    False    False    False    False    True     
        57010  x-iscii-gu                False    False    False    False    False    True     
        57011  x-iscii-pa                False    False    False    False    False    True     
        65000  utf-7                     False    False    True     True     False    True     
        65001  utf-8                     True     True     True     True     False    True     
        65005  utf-32                    False    False    False    False    False    True     
        65006  utf-32BE                  False    False    False    False    False    True     

        */
        #endregion

        /// <summary>
        /// 靜态構造函數隻有在靜态方法将要使用的時候才進行調用(靜态成員同理)
        /// </summary>
        static CsvFileHelper()
        {
            isSaveAsExcel = true;
            defaultEncoding = new System.Text.UTF8Encoding(false);
        }

        private static bool isSaveAsExcel ;
        private static Encoding defaultEncoding;
        private static char csvSeparator = ',';
        //private static Encoding utfBom = System.Text.Encoding.GetEncoding("GB2312");

        /// <summary>
        /// get or set Csv Separator (Default Values is ,)
        /// </summary>
        public static char DefaultCsvSeparator
        {
            get { return csvSeparator; }
            set { csvSeparator = value; }
        }

        /// <summary>
        /// get or set if save as Excel type (出現在首部的“是必須轉義的,而出現在中間的不可以不用專門轉義,而excel對所有雙引号都進行轉義,無論其出現位置)
        /// </summary>
        public static bool IsSaveAsExcel
        {
            get { return isSaveAsExcel; }
            set { isSaveAsExcel = value; }
        }

        /// <summary>
        /// get or set Default Encoding (notice : if your want the System not with bom ,you should use the relevant Encoding)
        /// </summary>
        public static Encoding DefaultEncoding
        {
            get { return defaultEncoding; }
            set { defaultEncoding = value; }
        }

        private static void WriteCsvVeiw(List<List<string>> yourListCsvData, TextWriter writer)
        {
            foreach(List<string> tempField in yourListCsvData)
            {
                if (tempField == null || tempField.Count == 0)
                {
                    continue;
                }
                WriteCsvLine(tempField, writer);
            }
        }

        private static void WriteCsvLine(List<string> fields, TextWriter writer)
        {
            if (fields == null || fields.Count == 0)
            {
                return;
            }
            StringBuilder myStrBld = new StringBuilder();
            //對于CSV資料來說不可能出現一行的資料元素的數量是0的情況,是以不用考慮fields.Count為0的情況(如果為0則為錯誤資料直接忽略)
            //foreach(string tempField in fields)  //使用foreach會産生許多不必要的string拷貝
            for (int i = 0; i < fields.Count; i++)
            {
                //通過檔案轉換出來的fields是不會為null的,為了相容外部建構資料源,可能出現null的情況,這裡強制轉換為""
                if (fields[i] == null)
                {
                    myStrBld.Append("");
                }
                else
                {
                    bool quotesRequired = (isSaveAsExcel ? (fields[i].Contains(csvSeparator) || fields[i].Contains("\r\n") || fields[i].Contains("\"")) : (fields[i].Contains(csvSeparator) || fields[i].Contains("\r\n") || fields[i].StartsWith("\"")));
                    if (quotesRequired)
                    {
                        if (fields[i].Contains("\""))
                        {
                            myStrBld.Append(String.Format("\"{0}\"", fields[i].Replace("\"", "\"\"")));
                        }
                        else
                        {
                            myStrBld.Append(String.Format("\"{0}\"", fields[i]));
                        }
                    }
                    else
                    {
                        myStrBld.Append(fields[i]);
                    }
                }

                if (i < fields.Count - 1)
                {
                    myStrBld.Append(csvSeparator);
                }
            }
            writer.WriteLine(myStrBld.ToString());
        }

        public static void SaveCsvFile(string yourFilePath,List<List<string>> yourDataSouse,bool isAppend,Encoding yourEncode)
        {
            //FileStream myCsvStream = new FileStream(yourFilePath, FileMode.Create, FileAccess.ReadWrite);
            if (isAppend && !File.Exists(yourFilePath))
            {
                throw new Exception("in Append mode the FilePath must exist");
            }
            if(!isAppend && !File.Exists(yourFilePath))
            {
                if (yourFilePath.Contains('\\'))
                {
                    if (!Directory.Exists(yourFilePath.Remove(yourFilePath.LastIndexOf('\\'))))
                    {
                        throw new Exception("the FilePath or the Directory it not exist");
                    }
                    
                }
                else
                {
                    throw new Exception("find error in your FilePath");
                }
            }
            //StreamWriter myCsvSw = new StreamWriter(yourFilePath, isAppend, yourEncode);   //isAppend對應的Stream的FileMode 為 append  ? FileMode.Append : FileMode.Create
            //檔案如果被其他任務打開并處于Write模式,此處會抛出異常(該工具也含多處異常抛出,使用時務必考慮接收這些異常)
            StreamWriter myCsvSw = new StreamWriter(new FileStream(yourFilePath, isAppend ? FileMode.Append : FileMode.Create, FileAccess.Write, FileShare.ReadWrite), yourEncode);
            if (yourDataSouse == null)
            {
                throw new Exception("your DataSouse is null");
            }
            WriteCsvVeiw(yourDataSouse, myCsvSw);
            myCsvSw.Dispose();
        }

        public static void SaveCsvFile(string yourFilePath, List<List<string>> yourDataSouse)
        {
            SaveCsvFile(yourFilePath, yourDataSouse, false, defaultEncoding);
        }

        public static Stream OpenFile(string filePath)
        {
            Stream myStream;
            try
            {
                myStream = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
            }
            catch (Exception)
            {
                return null;
            }
            return myStream;
        }

        #endregion
    }
}      

github位址: https://github.com/lulianqi/MyOutTool/blob/master/CsvFileHelper.cs   (建議直接在該位址取代碼,已經修複了幾處錯誤,部落格中的代碼可能有更新不及時的情況)

任何問題請郵件或留言