如何使用 Visual C# 2005 或 Visual C# .NET 向 Excel 工作簿传输数据
2020-11-15 20:24
标签:des com http blog style class div img code java size 本文分步介绍了多种从 Microsoft Visual C# 2005 或 Microsoft Visual C# .NET 程序向
Microsoft Excel 2002 传输数据的方法。本文还提供了每种方法的优点和缺点,以便您可以选择最适合您的情况的解决方案。 最常用于向 Excel 工作簿传输数据的方法是“自动化”。利用“自动化”功能,您可以调用特定于 Excel
任务的方法和属性。“自动化”功能为您提供了指定数据在工作簿中所处的位置、将工作簿格式化以及在运行时进行各种设置的最大的灵活性。 利用“自动化”功能,您可以使用多种方法来传输数据: 还可以使用多种未必需要利用“自动化”功能来向 Excel
传输数据的方法。如果您正在运行服务器端程序,这会是一种将批量数据处理从客户端移走的好方法。 要在不使用“自动化”功能的情况下传输数据,您可以使用下列方法: 本文讨论了其中的每一种方法并提供了每一种方法的代码示例。本文的创建完整的示例
Visual C# 2005 或 Visual C# .NET 项目一节(在本文稍后部分)演示了如何创建执行每一种方法的
Visual C# .NET 程序。 利用“自动化”功能,您可以逐单元格地向工作表传输数据: 如果您具有少量的数据,则逐单元格地传输数据是可以接受的方法。您可以灵活地将数据放到工作簿中的任何地方,并可以在运行时根据条件对单元格进行格式设置。不过,如果您具有大量需要传输到
Excel
工作簿的数据,则使用这种方法不是一个好主意。您在运行时获取的每一个“Range”对象都会产生一个接口请求,这意味着数据传输速度会变得较慢。此外,Microsoft
Windows 95、Microsoft Windows 98 以及 Microsoft Windows Millennium Edition (Me)
都对接口请求有 64 KB 的限制。如果您具有 64 KB 以上的接口请求,则“自动化”服务器 (Excel)
可能会停止响应,或者您可能会收到指出内存不足的错误消息。 有关其他信息,请单击下面的文章编号,以查看 Microsoft
知识库中相应的文章: 需要再次强调的是,逐单元格地传输数据仅对少量数据而言才可以接受。如果您必须向 Excel
传输大型数据集,则应考虑使用本文中讨论的其他方法之一来批量地传输数据。 有关其他信息以及如何利用 Visual C#
.NET 自动运行 Excel 的示例,请单击下面的文章编号,以查看 Microsoft 知识库中相应的文章: 可以将数据数组一次性地传输到由多个单元格组成的区域: 如果您使用数组而不是逐单元格地传输数据,则在传输大量数据时,传输性能会大大地增强。请考虑前面代码中的下面几行,这些行将数据传输到工作表中的
300 个单元格: 上面的代码代表了两个接口请求:一个请求是针对“Range”方法返回的“Range”对象,另一个请求是针对“Resize”方法返回的“Range”对象。相比之下,逐单元格地传输数据却需要对“Range”对象发出
300 个接口请求。只要有可能,您就可以从批量地传输数据以及减少所发出的接口请求的数量当中受益。 有关通过
Excel 自动化并使用数组获取和设置区域中的值的其他信息,请单击下面的文章编号,以查看 Microsoft 知识库中相应的文章: Excel 2000、Excel 2002 和 Excel 2003
的对象模型提供了“CopyFromRecordset”方法,用于向工作表上的区域传输 ADO
记录集。下面的代码说明了如何使用“CopyFromRecordset”方法使 Excel 自动运行,以传输 Northwind
示例数据库中的“订单”表的内容: 注意:“CopyFromRecordset”只能与 ADO“Recordset”对象一起使用。使用
ADO.NET 创建的“DataSet”不能与“CopyFromRecordset”方法一起使用。以下几节中的多个示例演示了如何利用 ADO.NET 向
Excel 传输数据。 “QueryTable”对象代表了一个表,该表是用从外部数据源返回的数据生成的。当您自动运行 Excel 时,可以通过提供指向 OLE DB
或 ODBC 数据源的连接字符串和 SQL 字符串来创建“QueryTable”。Excel
将生成记录集并将该记录集插入到工作表中您所指定的位置。“QueryTable”对象提供了下列优于“CopyFromRecordset”方法的优点: 下面的代码演示了如何自动运行 Excel 2000、Excel 2002 或 Excel 2003,以便使用 Northwind
示例数据库中的数据在 Excel 工作表中创建新的“QueryTable”: 可以使用 Windows
剪贴板来向工作表传输数据。要将数据粘贴到工作表上的多个单元格中,可以复制具有以下格式的字符串:在该字符串中,列由制表符分隔,行由回车符分隔。下面的代码说明了
Visual C# .NET 如何使用 Windows 剪贴板来向 Excel 传输数据: Excel
可以打开由制表符或逗号分隔的文件并正确地将数据分析为单元格。当您希望向工作表传输大量数据而只使用少量(如果有的话)自动化功能时,可以使用此功能。这对于客户端-服务器程序而言可能是一个好方法,因为文本文件可以在服务器端生成。然后,可以在客户端根据需要使用“自动化”功能来打开文本文件。 下面的代码说明了如何从利用
ADO.NET 读取的数据生成制表符分隔的文本文件: 上述代码没有使用“自动化”功能。不过,如果您愿意,您可以按如下方式使用“自动化”功能来打开文本文件,并以 Excel
工作簿格式保存该文件: 您可以使用 Microsoft Jet OLE DB 提供程序向现有的 Excel 工作簿中的表中添加记录。Excel
中的表只是由单元格组成的区域;该区域可能具有定义的名称。通常,区域的第一行包含标题(或字段名),该区域中所有以后的行都包含记录。 下面的代码向
Book7.xls 中的表中添加了两个新记录。在此示例中,该表是 Sheet1: 当您按本示例所示的方法利用 ADO.NET
添加记录时,工作簿中的格式将被保持。添加到行中的每个记录都将继承它前面的行的格式。 有关使用 ADO.NET
的其他信息,请单击下面的文章编号,以查看 Microsoft 知识库中相应的文章: 有关如何将 Jet OLEDB 提供程序与 Excel 数据源一起使用的其他信息,请单击下面的文章编号,以查看 Microsoft
知识库中相应的文章。 有关如何将 XML 与 Excel 2002
一起使用的其他信息,请单击下面的文章编号,以查看 Microsoft 知识库中相应的文章:
注意:在 Visual
Studio 2005 中,您不需要单击“选择”。 注意:在 Visual Studio 2005
中,您不需要单击“选择”。 注意:如果您正在使用 Microsoft Excel
2002,并且尚未这样做,Microsoft 建议您下载并安装 Microsoft Office XP 主互操作 程序集 (PIA)。 有关
Office XP PIA 的更多信息,请单击下面的文章编号,以查看 Microsoft 知识库中相应的文章:
此时将创建该窗体的“Load”事件的处理程序,该处理程序出现在
Form1.cs 中。 此时将创建该按钮的“Click”事件的处理程序,该处理程序出现在 Form1.cs
中。 替换为:概述
方法
使用“自动化”功能逐单元格传输数据
1 // Start a new workbook in Excel.
2 m_objExcel = new Excel.Application();
3 m_objBooks = (Excel.Workbooks)m_objExcel.Workbooks;
4 m_objBook = (Excel._Workbook)(m_objBooks.Add(m_objOpt));
5
6 // Add data to cells in the first worksheet in the new workbook.
7 m_objSheets = (Excel.Sheets)m_objBook.Worksheets;
8 m_objSheet = (Excel._Worksheet)(m_objSheets.get_Item(1));
9 m_objRange = m_objSheet.get_Range("A1", m_objOpt);
10 m_objRange.Value = "Last Name";
11 m_objRange = m_objSheet.get_Range("B1", m_objOpt);
12 m_objRange.Value = "First Name";
13 m_objRange = m_objSheet.get_Range("A2", m_objOpt);
14 m_objRange.Value = "Doe";
15 m_objRange = m_objSheet.get_Range("B2", m_objOpt);
16 m_objRange.Value = "John";
17
18 // Apply bold to cells A1:B1.
19 m_objRange = m_objSheet.get_Range("A1", "B1");
20 m_objFont = m_objRange.Font;
21 m_objFont.Bold=true;
22
23 // Save the Workbook and quit Excel.
24 m_objBook.SaveAs(m_strSampleFolder + "Book1.xls", m_objOpt, m_objOpt,
25 m_objOpt, m_objOpt, m_objOpt, Excel.XlSaveAsAccessMode.xlNoChange,
26 m_objOpt, m_objOpt, m_objOpt, m_objOpt);
27 m_objBook.Close(false, m_objOpt, m_objOpt);
28 m_objExcel.Quit();
使用“自动化”功能将数据数组传输到工作表上的区域
1 // Start a new workbook in Excel.
2 m_objExcel = new Excel.Application();
3 m_objBooks = (Excel.Workbooks)m_objExcel.Workbooks;
4 m_objBook = (Excel._Workbook)(m_objBooks.Add(m_objOpt));
5 m_objSheets = (Excel.Sheets)m_objBook.Worksheets;
6 m_objSheet = (Excel._Worksheet)(m_objSheets.get_Item(1));
7
8 // Create an array for the headers and add it to cells A1:C1.
9 object[] objHeaders = {"Order ID", "Amount", "Tax"};
10 m_objRange = m_objSheet.get_Range("A1", "C1");
11 m_objRange.Value = objHeaders;
12 m_objFont = m_objRange.Font;
13 m_objFont.Bold=true;
14
15 // Create an array with 3 columns and 100 rows and add it to
16 // the worksheet starting at cell A2.
17 object[,] objData = new Object[100,3];
18 Random rdm = new Random((int)DateTime.Now.Ticks);
19 double nOrderAmt, nTax;
20 for(int r=0;r100;r++)
21 {
22 objData[r,0] = "ORD" + r.ToString("0000");
23 nOrderAmt = rdm.Next(1000);
24 objData[r,1] = nOrderAmt.ToString("c");
25 nTax = nOrderAmt*0.07;
26 objData[r,2] = nTax.ToString("c");
27 }
28 m_objRange = m_objSheet.get_Range("A2", m_objOpt);
29 m_objRange = m_objRange.get_Resize(100,3);
30 m_objRange.Value = objData;
31
32 // Save the Workbook and quit Excel.
33 m_objBook.SaveAs(m_strSampleFolder + "Book2.xls", m_objOpt, m_objOpt,
34 m_objOpt, m_objOpt, m_objOpt, Excel.XlSaveAsAccessMode.xlNoChange,
35 m_objOpt, m_objOpt, m_objOpt, m_objOpt);
36 m_objBook.Close(false, m_objOpt, m_objOpt);
37 m_objExcel.Quit();
1 objRange = objSheet.get_Range("A2", m_objOpt);
2 objRange = objRange.get_Resize(100,3);
3 objRange.Value = objData;
使用“自动化”功能将 ADO 记录集传输到工作表区域
1 // Create a Recordset from all the records in the Orders table.
2 ADODB.Connection objConn = new ADODB.Connection();
3 ADODB._Recordset objRS = null;
4 objConn.Open("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" +
5 m_strNorthwind + ";", "", "", 0);
6 objConn.CursorLocation = ADODB.CursorLocationEnum.adUseClient;
7 object objRecAff;
8 objRS = (ADODB._Recordset)objConn.Execute("Orders", out objRecAff,
9 (int)ADODB.CommandTypeEnum.adCmdTable);
10
11 // Start a new workbook in Excel.
12 m_objExcel = new Excel.Application();
13 m_objBooks = (Excel.Workbooks)m_objExcel.Workbooks;
14 m_objBook = (Excel._Workbook)(m_objBooks.Add(m_objOpt));
15 m_objSheets = (Excel.Sheets)m_objBook.Worksheets;
16 m_objSheet = (Excel._Worksheet)(m_objSheets.get_Item(1));
17
18 // Get the Fields collection from the recordset and determine
19 // the number of fields (or columns).
20 System.Collections.IEnumerator objFields = objRS.Fields.GetEnumerator();
21 int nFields = objRS.Fields.Count;
22
23 // Create an array for the headers and add it to the
24 // worksheet starting at cell A1.
25 object[] objHeaders = new object[nFields];
26 ADODB.Field objField = null;
27 for(int n=0;n
使用“自动化”功能在工作表上创建 QueryTable 对象
1 // Start a new workbook in Excel.
2 m_objExcel = new Excel.Application();
3 m_objBooks = (Excel.Workbooks)m_objExcel.Workbooks;
4 m_objBook = (Excel._Workbook)(m_objBooks.Add(m_objOpt));
5
6 // Create a QueryTable that starts at cell A1.
7 m_objSheets = (Excel.Sheets)m_objBook.Worksheets;
8 m_objSheet = (Excel._Worksheet)(m_objSheets.get_Item(1));
9 m_objRange = m_objSheet.get_Range("A1", m_objOpt);
10 m_objQryTables = m_objSheet.QueryTables;
11 m_objQryTable = (Excel._QueryTable)m_objQryTables.Add(
12 "OLEDB;Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" +
13 m_strNorthwind + ";", m_objRange, "Select * From Orders");
14 m_objQryTable.RefreshStyle = Excel.XlCellInsertionMode.xlInsertEntireRows;
15 m_objQryTable.Refresh(false);
16
17 // Save the workbook and quit Excel.
18 m_objBook.SaveAs(m_strSampleFolder + "Book4.xls", m_objOpt, m_objOpt,
19 m_objOpt, m_objOpt, m_objOpt, Excel.XlSaveAsAccessMode.xlNoChange, m_objOpt, m_objOpt,
20 m_objOpt, m_objOpt);
21 m_objBook.Close(false, m_objOpt, m_objOpt);
22 m_objExcel.Quit();
使用 Windows 剪贴板
1 // Copy a string to the Windows clipboard.
2 string sData = "FirstName\tLastName\tBirthdate\r\n" +
3 "Bill\tBrown\t2/5/85\r\n" +
4 "Joe\tThomas\t1/1/91";
5 System.Windows.Forms.Clipboard.SetDataObject(sData);
6
7 // Start a new workbook in Excel.
8 m_objExcel = new Excel.Application();
9 m_objBooks = (Excel.Workbooks)m_objExcel.Workbooks;
10 m_objBook = (Excel._Workbook)(m_objBooks.Add(m_objOpt));
11
12 // Paste the data starting at cell A1.
13 m_objSheets = (Excel.Sheets)m_objBook.Worksheets;
14 m_objSheet = (Excel._Worksheet)(m_objSheets.get_Item(1));
15 m_objRange = m_objSheet.get_Range("A1", m_objOpt);
16 m_objSheet.Paste(m_objRange, false);
17
18 // Save the workbook and quit Excel.
19 m_objBook.SaveAs(m_strSampleFolder + "Book5.xls", m_objOpt, m_objOpt,
20 m_objOpt, m_objOpt, m_objOpt, Excel.XlSaveAsAccessMode.xlNoChange, m_objOpt, m_objOpt,
21 m_objOpt, m_objOpt);
22 m_objBook.Close(false, m_objOpt, m_objOpt);
23 m_objExcel.Quit();
创建可由 Excel 分析为行和列的带分隔符的文本文件
1 // Connect to the data source.
2 System.Data.OleDb.OleDbConnection objConn = new System.Data.OleDb.OleDbConnection(
3 "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + m_strNorthwind + ";");
4 objConn.Open();
5
6 // Execute a command to retrieve all records from the Employees table.
7 System.Data.OleDb.OleDbCommand objCmd = new System.Data.OleDb.OleDbCommand(
8 "Select * From Employees", objConn);
9 System.Data.OleDb.OleDbDataReader objReader;
10 objReader = objCmd.ExecuteReader();
11
12 // Create the FileStream and StreamWriter object to write
13 // the recordset contents to file.
14 System.IO.FileStream fs = new System.IO.FileStream(
15 m_strSampleFolder + "Book6.txt", System.IO.FileMode.Create);
16 System.IO.StreamWriter sw = new System.IO.StreamWriter(
17 fs, System.Text.Encoding.Unicode);
18
19 // Write the field names (headers) as the first line in the text file.
20 sw.WriteLine(objReader.GetName(0) + "\t" + objReader.GetName(1) +
21 "\t" + objReader.GetName(2) + "\t" + objReader.GetName(3) +
22 "\t" + objReader.GetName(4) + "\t" + objReader.GetName(5));
23
24 // Write the first six columns in the recordset to a text file as
25 // tab-delimited.
26 while(objReader.Read())
27 {
28 for(int i=0;i5;i++)
29 {
30 if(!objReader.IsDBNull(i))
31 {
32 string s;
33 s = objReader.GetDataTypeName(i);
34 if(objReader.GetDataTypeName(i)=="DBTYPE_I4")
35 {
36 sw.Write(objReader.GetInt32(i).ToString());
37 }
38 else if(objReader.GetDataTypeName(i)=="DBTYPE_DATE")
39 {
40 sw.Write(objReader.GetDateTime(i).ToString("d"));
41 }
42 else if (objReader.GetDataTypeName(i)=="DBTYPE_WVARCHAR")
43 {
44 sw.Write(objReader.GetString(i));
45 }
46 }
47 if(i5) sw.Write("\t");
48 }
49 sw.WriteLine();
50 }
51 sw.Flush(); // Write the buffered data to the filestream.
52
53 // Close the FileStream.
54 fs.Close();
55
56 // Close the reader and the connection.
57 objReader.Close();
58 objConn.Close();
59
1 // Open the text file in Excel.
2 m_objExcel = new Excel.Application();
3 m_objBooks = (Excel.Workbooks)m_objExcel.Workbooks;
4 m_objBooks.OpenText(m_strSampleFolder + "Book6.txt", Excel.XlPlatform.xlWindows, 1,
5 Excel.XlTextParsingType.xlDelimited, Excel.XlTextQualifier.xlTextQualifierDoubleQuote,
6 false, true, false, false, false, false, m_objOpt, m_objOpt,
7 m_objOpt, m_objOpt, m_objOpt);
8
9 m_objBook = m_objExcel.ActiveWorkbook;
10
11 // Save the text file in the typical workbook format and quit Excel.
12 m_objBook.SaveAs(m_strSampleFolder + "Book6.xls", Excel.XlFileFormat.xlWorkbookNormal,
13 m_objOpt, m_objOpt, m_objOpt, m_objOpt, Excel.XlSaveAsAccessMode.xlNoChange, m_objOpt, m_objOpt,
14 m_objOpt, m_objOpt);
15 m_objBook.Close(false, m_objOpt, m_objOpt);
16 m_objExcel.Quit();
17
使用 ADO.NET 将数据传输到工作表
1 // Establish a connection to the data source.
2 System.Data.OleDb.OleDbConnection objConn = new System.Data.OleDb.OleDbConnection(
3 "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + m_strSampleFolder +
4 "Book7.xls;Extended Properties=Excel 8.0;");
5 objConn.Open();
6
7 // Add two records to the table named ‘MyTable‘.
8 System.Data.OleDb.OleDbCommand objCmd = new System.Data.OleDb.OleDbCommand();
9 objCmd.Connection = objConn;
10 objCmd.CommandText = "Insert into MyTable (FirstName, LastName)" +
11 " values (‘Bill‘, ‘Brown‘)";
12 objCmd.ExecuteNonQuery();
13 objCmd.CommandText = "Insert into MyTable (FirstName, LastName)" +
14 " values (‘Joe‘, ‘Thomas‘)";
15 objCmd.ExecuteNonQuery();
16
17 // Close the connection.
18 objConn.Close();
传输 XML 数据(Excel 2002 和 Excel 2003)
Excel 2002 和
2003 可以打开格式完好的任何 XML 文件。您可以使用“文件”菜单上的“打开”命令直接打开 XML
文件,也可以使用“Workbooks”集合的“Open”或“OpenXML”方法以编程方式打开 XML 文件。如果您创建供在 Excel 中使用的 XML
文件,您还可以创建样式表来设置数据的格式。创建完整的示例 Visual C# .NET 项目
1 private void Form1_Load(object sender, System.EventArgs e)
2 {
3
4 }
5
6 private void button1_Click(object sender, System.EventArgs e)
7 {
8
9 }
1 // Excel object references.
2 private Excel.Application m_objExcel = null;
3 private Excel.Workbooks m_objBooks = null;
4 private Excel._Workbook m_objBook = null;
5 private Excel.Sheets m_objSheets = null;
6 private Excel._Worksheet m_objSheet = null;
7 private Excel.Range m_objRange = null;
8 private Excel.Font m_objFont = null;
9 private Excel.QueryTables m_objQryTables = null;
10 private Excel._QueryTable m_objQryTable = null;
11
12 // Frequenty-used variable for optional arguments.
13 private object m_objOpt = System.Reflection.Missing.Value;
14
15 // Paths used by the sample code for accessing and storing data.
16 private object m_strSampleFolder = "C:\\ExcelData\\";
17 private string m_strNorthwind = "C:\\Program Files\\Microsoft Office\\Office10\\Samples\\Northwind.mdb";
18
19 private void Form1_Load(object sender, System.EventArgs e)
20 {
21 comboBox1.DropDownStyle = ComboBoxStyle.DropDownList;
22
23 comboBox1.Items.AddRange(new object[]{
24 "Use Automation to Transfer Data Cell by Cell ",
25 "Use Automation to Transfer an Array of Data to a Range on a Worksheet ",
26 "Use Automation to Transfer an ADO Recordset to a Worksheet Range ",
27 "Use Automation to Create a QueryTable on a Worksheet",
28 "Use the Clipboard",
29 "Create a Delimited Text File that Excel Can Parse into Rows and Columns",
30 "Transfer Data to a Worksheet Using ADO.NET "});
31 comboBox1.SelectedIndex = 0;
32 button1.Text = "Go!";
33 }
34
35 private void button1_Click(object sender, System.EventArgs e)
36 {
37 switch (comboBox1.SelectedIndex)
38 {
39 case 0 : Automation_CellByCell(); break;
40 case 1 : Automation_UseArray(); break;
41 case 2 : Automation_ADORecordset(); break;
42 case 3 : Automation_QueryTable(); break;
43 case 4 : Use_Clipboard(); break;
44 case 5 : Create_TextFile(); break;
45 case 6 : Use_ADONET(); break;
46 }
47
48 //Clean-up
49 m_objFont = null;
50 m_objRange = null;
51 m_objSheet = null;
52 m_objSheets = null;
53 m_objBooks = null;
54 m_objBook = null;
55 m_objExcel = null;
56 GC.Collect();
57
58 }
59
60 private void Automation_CellByCell()
61 {
62 // Start a new workbook in Excel.
63 m_objExcel = new Excel.Application();
64 m_objBooks = (Excel.Workbooks)m_objExcel.Workbooks;
65 m_objBook = (Excel._Workbook)(m_objBooks.Add(m_objOpt));
66
67 // Add data to cells of the first worksheet in the new workbook.
68 m_objSheets = (Excel.Sheets)m_objBook.Worksheets;
69 m_objSheet = (Excel._Worksheet)(m_objSheets.get_Item(1));
70 m_objRange = m_objSheet.get_Range("A1", m_objOpt);
71 m_objRange.set_Value(m_objOpt,"Last Name");
72 m_objRange = m_objSheet.get_Range("B1", m_objOpt);
73 m_objRange.set_Value(m_objOpt,"First Name");
74 m_objRange = m_objSheet.get_Range("A2", m_objOpt);
75 m_objRange.set_Value(m_objOpt,"Doe");
76 m_objRange = m_objSheet.get_Range("B2", m_objOpt);
77 m_objRange.set_Value(m_objOpt,"John");
78
79 // Apply bold to cells A1:B1.
80 m_objRange = m_objSheet.get_Range("A1", "B1");
81 m_objFont = m_objRange.Font;
82 m_objFont.Bold=true;
83
84 // Save the workbook and quit Excel.
85 m_objBook.SaveAs(m_strSampleFolder + "Book1.xls", m_objOpt, m_objOpt,
86 m_objOpt, m_objOpt, m_objOpt, Excel.XlSaveAsAccessMode.xlNoChange,
87 m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt);
88 m_objBook.Close(false, m_objOpt, m_objOpt);
89 m_objExcel.Quit();
90
91 }
92
93 private void Automation_UseArray()
94 {
95 // Start a new workbook in Excel.
96 m_objExcel = new Excel.Application();
97 m_objBooks = (Excel.Workbooks)m
文章标题:如何使用 Visual C# 2005 或 Visual C# .NET 向 Excel 工作簿传输数据
文章链接:http://soscw.com/essay/21512.html