读取 CSV 文件一些缺失的列
Reading CSV file some missing columns
我正在尝试使用以下代码将 CSV 文件读入我的 VB.net 应用程序:
While Not EOF(1)
Input(1, dummy)
Input(1, phone_number)
Input(1, username)
Input(1, product_name)
Input(1, wholesale_cost)
Input(1, dummy)
Input(1, dummy)
End While
我的 CSV 文件(文本)如下所示:
Customer Name,Phone Number,Username,Product,Wholesale Cost,Sales Price,Gross Profit, Customer Reference
,00000000000,00000000000,Product Name,25.00,35.00,10.00,
,00000000000,00000000000,Product Name,1.00,1.40,0.40,
如您所见,并非所有字段都包含在内,因此在读取文件时会显示错误,因为它无法到达行尾。
我该如何处理这种类型的文件?
有时字段会出现在某些行上,而其他行则不会。
更新
我已经尝试了 Zenacity 提供的答案,但是当尝试在循环中使用 sArray(1)
进行阅读时,它 returns
Index was outside the bounds of the array
通过使用以下函数,您可以逐行评估文件内容并采取适当的措施。
Imports System.IO
Private Sub ParseCSVFile(psFile As String)
Dim sArray() As String
Dim Customer_Name As String = String.Empty
Dim Phone_Number As String = String.Empty
Dim Username As String = String.Empty
Dim Product As String = String.Empty
Dim Wholesale_Cost As String = String.Empty
Dim Sales_Price As String = String.Empty
Dim Gross_Profit As String = String.Empty
Dim Customer_Reference As String = String.Empty
Try
Using objStreamReader As StreamReader = New StreamReader(psFile) 'should be full path
Dim sLine As String = String.Empty
Do
sLine = objStreamReader.ReadLine()
If sLine <> Nothing Then
sArray = Split(sLine, ",")
Customer_Name = sArray(0)
Phone_Number = sArray(1)
Username = sArray(2)
Product = sArray(3)
Wholesale_Cost = sArray(4)
Sales_Price = sArray(5)
Gross_Profit = sArray(6)
Customer_Reference = sArray(7)
Debug.Print(Customer_Name & "," & Phone_Number & "," & Username & "," & Product & "," & Wholesale_Cost & "," & Sales_Price & "," & Gross_Profit & "," & Customer_Reference)
End If
Loop Until sLine Is Nothing
End Using
Catch
'log error
End Try
End Sub
您应该了解的一件事是,那些 Filexxxx
方法几乎已正式弃用。使用它们时,Intellisense 会弹出:
...The My feature gives you better productivity and performance in file I/O operations than FileOpen. For more information, see Microsoft.VisualBasic.FileIO.FileSystem.
他们在谈论 My.Computer.FileSystem
但还有一些更有用的 NET 方法。
post 没有透露数据将如何存储,但如果它是任何类型的数组 and/or 结构,那么即使不是过时的,也至少是次优的。这会将其存储在 class 中,以便可以将数字数据存储为数字,并且将使用 List
代替数组。
我用一些随机数据制作了一个类似于您的快速文件:{"CustName", "Phone", "UserName", "Product", "Cost", "Price", "Profit", "SaleDate", "RefCode"}
:
- 70% 的时间出现 CustName
- 用户名从未出现
- RefCode 在 30% 的时间内出现
- 我加了一个SaleDate来说明数据转换
Ziggy Aurantium,132-5562,,Cat Food,8.26,9.95,1.69,08/04/2016,
Catrina Caison,899-8599,,Knife Sharpener,4.95,6.68,1.73,10/12/2016,X-873-W3
,784-4182,,Vapor Compressor,11.02,12.53,1.51,09/12/2016,
解析 CSV 的代码
注意:这种解析 CSV 的方法很糟糕。这样做会出现很多问题;再加上它需要更多的代码。之所以提供它,是因为它是一种不必处理缺失字段的简单方法。请参见 正确的方法
' form/class level var:
Private SalesItems As List(Of SaleItem)
SaleItem
就是简单的class来存储你关心的元素。 SalesItems
是一个集合,只能存储SaleItem
个对象。 class 中的属性允许 Price 和 Cost 存储为 Decimal
,日期存储为 DateTime
.
' temp var
Dim item As SaleItem
' create the collection
SalesItems = New List(Of SaleItem)
' load the data....all of it
Dim data = File.ReadAllLines("C:\Temp\custdata.csv")
' parse data lines
' Start at 1 to skip a Header
For n As Int32 = 0 To data.Length - 1
Dim split = data(n).Split(","c)
' check if it is a good line
If split.Length = 9 Then
' create a new item
item = New SaleItem
' store SOME data to it
item.CustName = split(0)
item.Phone = split(1)
' dont care anout user name (2)
item.Product = split(3)
' convert numbers
item.Price = Convert.ToDecimal(split(4))
item.Cost = Convert.ToDecimal(split(5))
' dont use the PROFIT, calculate it in the class (6)
' convert date
item.SaleDate = Convert.ToDateTime(split(7))
' ignore nonexistant RefCode (8)
' add new item to collection
' a List sizes itself as needed!
SalesItems.Add(item)
Else
' To Do: make note of a bad line format
End If
Next
' show in DGV for approval/debugging
dgvMem.DataSource = SalesItems
结果:
备注
存储可以简单计算的东西通常不是一个好主意。所以 Profit
属性 是:
Public ReadOnly Property Profit As Decimal
Get
Return (Cost - Price)
End Get
End Property
如果成本或价格更新,它永远不会“陈旧”。
如图所示,使用生成的集合可以非常容易地显示给用户。给定 DataSource
,DataGridView
将创建列并填充行。
正确的方法
String.Split(c)
是 一个非常糟糕的主意 因为如果产品是: "Hose, Small Green"
它会将其切碎并将其视为 2 个字段。有许多工具可以为您完成几乎所有工作:
- 读取文件
- 解析行
- 将 CSV 数据映射到 class
- 将文本转换为正确的数据类型
- 创建一个经济的集合
除了 class 之外,以上所有内容都可以使用 CSVHelper:
在短短几行中完成
Private CustData As List(Of SaleItem)
...
Using sr As New StreamReader("C:\Temp\custdata.csv", False),
csv = New CsvReader(sr)
csv.Configuration.HasHeaderRecord = True
CustData = csv.GetRecords(Of SaleItem)().ToList()
End Using
两三行代码即可读取、解析和创建包含 250 项的集合。
即使您出于某种原因想要手动执行,CSVHelper 也可以提供帮助。您可以使用它来读取和解析数据,而不是为您创建 List(Of SaleItem)
:
... like above
csv.Configuration.HasHeaderRecord = True
Do Until csv.Read() = False
For n As Int32 = 0 To csv.Parser.FieldCount - 1
DoSomethingWith(csv.GetField(n))
Next
Loop
这将 return 字段一一呈现给您。它不会转换任何日期或价格,但也不会因缺少数据元素而阻塞。
资源
- CSVHelper
- Class vs Structure
- Eric Lippert's Thoughts on the matter
- File Class 有很多有用的方法
警告: 如果 CustomerName
或 ProductName
值可以包含逗号
(.i.e. CustomerName = "Callaway , Mark"
) 您不能使用 String.Split()
方法。最好搜索第三方 csv 解析器,或者您可以使用 TextFieldParser
Class --> MSDN article
您可以按照此 link 了解如何使用 TextFieldParser
导入 csv
早些时候我在使用 SQL 服务器集成服务导入 Csv 文件时遇到问题(字段包含分隔符)你可以看一下(代码在 Vb.net ):
我的回答是假设缺少的字段 总是从行的右侧开始,并且字段值不包含逗号 (否则 @Plutonix 答案就是你要找的)
使用此代码,您将能够导入缺少字段的行。
您必须从 csv 文件中读取每一行,使用以下代码计算该行中 ","
的出现次数
Line.Count(Function(c As Char) c = ",")
如果计数小于 7
(8 列),您将添加缺少的 ","
String.PadRight((7 - intCommaCount), ",")
注意: 如果左侧缺少逗号,您可以使用 String.PadLeft((7 - intCommaCount), ",")
并将该行拆分为项目属性
我创建了以下 Item
Class
Public Class MyItem
Public Property CustomerName As String
Public Property PhoneNumber As String
Public Property Username As String
Public Property Product As String
Public Property WholesaleCost As String
Public Property SalesPrice As String
Public Property GrossProfit As String
Public Property CustomerReference As String
Public Shared Function CreateObjectFromLine(ByVal Line As String) As MyItem
'Count Comma occurence in Line
Dim intCommaCount As Integer = Line.Count(Function(c As Char) c = CChar(","))
Dim strTemp = Line
'Add missing comma's
If intCommaCount < 7 Then
strTemp = strTemp.PadRight((7 - intCommaCount), ",")
End If
'Split Line and return MyItem Class
Dim str() As String = strTemp.Split(",")
Return New MyItem With {.CustomerName = str(0),
.PhoneNumber = str(1),
.Username = str(2),
.Product = str(3),
.WholesaleCost = str(4),
.SalesPrice = str(5),
.GrossProfit = str(6),
.CustomerReference = str(7)}
End Function
End Class
我使用以下代码从 CSV 文件导入数据
Dim SalesItems As New List(Of MyItem)
Dim csvFile As String = "C:.csv"
Using csvStreamReader As New IO.StreamReader(csvFile)
While Not csvStreamReader.EndOfStream
Dim strLine as string = csvStreamReader.ReadLine
' Skip Header
If strLine.StartsWith("Customer Name") Then Continue While
Dim item As MyItem = MyItem.CreateObjectFromLine(strLine)
SalesItems.Add(item)
End While
End Using
'Showing Result in a DataGridView
dgvItems.DataSource = SalesItems
注意: 这是一个简单的例子,需要添加错误处理Try... Catch
,Null
检查
我正在尝试使用以下代码将 CSV 文件读入我的 VB.net 应用程序:
While Not EOF(1)
Input(1, dummy)
Input(1, phone_number)
Input(1, username)
Input(1, product_name)
Input(1, wholesale_cost)
Input(1, dummy)
Input(1, dummy)
End While
我的 CSV 文件(文本)如下所示:
Customer Name,Phone Number,Username,Product,Wholesale Cost,Sales Price,Gross Profit, Customer Reference
,00000000000,00000000000,Product Name,25.00,35.00,10.00,
,00000000000,00000000000,Product Name,1.00,1.40,0.40,
如您所见,并非所有字段都包含在内,因此在读取文件时会显示错误,因为它无法到达行尾。
我该如何处理这种类型的文件?
有时字段会出现在某些行上,而其他行则不会。
更新
我已经尝试了 Zenacity 提供的答案,但是当尝试在循环中使用 sArray(1)
进行阅读时,它 returns
Index was outside the bounds of the array
通过使用以下函数,您可以逐行评估文件内容并采取适当的措施。
Imports System.IO
Private Sub ParseCSVFile(psFile As String)
Dim sArray() As String
Dim Customer_Name As String = String.Empty
Dim Phone_Number As String = String.Empty
Dim Username As String = String.Empty
Dim Product As String = String.Empty
Dim Wholesale_Cost As String = String.Empty
Dim Sales_Price As String = String.Empty
Dim Gross_Profit As String = String.Empty
Dim Customer_Reference As String = String.Empty
Try
Using objStreamReader As StreamReader = New StreamReader(psFile) 'should be full path
Dim sLine As String = String.Empty
Do
sLine = objStreamReader.ReadLine()
If sLine <> Nothing Then
sArray = Split(sLine, ",")
Customer_Name = sArray(0)
Phone_Number = sArray(1)
Username = sArray(2)
Product = sArray(3)
Wholesale_Cost = sArray(4)
Sales_Price = sArray(5)
Gross_Profit = sArray(6)
Customer_Reference = sArray(7)
Debug.Print(Customer_Name & "," & Phone_Number & "," & Username & "," & Product & "," & Wholesale_Cost & "," & Sales_Price & "," & Gross_Profit & "," & Customer_Reference)
End If
Loop Until sLine Is Nothing
End Using
Catch
'log error
End Try
End Sub
您应该了解的一件事是,那些 Filexxxx
方法几乎已正式弃用。使用它们时,Intellisense 会弹出:
...The My feature gives you better productivity and performance in file I/O operations than FileOpen. For more information, see Microsoft.VisualBasic.FileIO.FileSystem.
他们在谈论 My.Computer.FileSystem
但还有一些更有用的 NET 方法。
post 没有透露数据将如何存储,但如果它是任何类型的数组 and/or 结构,那么即使不是过时的,也至少是次优的。这会将其存储在 class 中,以便可以将数字数据存储为数字,并且将使用 List
代替数组。
我用一些随机数据制作了一个类似于您的快速文件:{"CustName", "Phone", "UserName", "Product", "Cost", "Price", "Profit", "SaleDate", "RefCode"}
:
- 70% 的时间出现 CustName
- 用户名从未出现
- RefCode 在 30% 的时间内出现
- 我加了一个SaleDate来说明数据转换
Ziggy Aurantium,132-5562,,Cat Food,8.26,9.95,1.69,08/04/2016,
Catrina Caison,899-8599,,Knife Sharpener,4.95,6.68,1.73,10/12/2016,X-873-W3
,784-4182,,Vapor Compressor,11.02,12.53,1.51,09/12/2016,
解析 CSV 的代码
注意:这种解析 CSV 的方法很糟糕。这样做会出现很多问题;再加上它需要更多的代码。之所以提供它,是因为它是一种不必处理缺失字段的简单方法。请参见 正确的方法
' form/class level var:
Private SalesItems As List(Of SaleItem)
SaleItem
就是简单的class来存储你关心的元素。 SalesItems
是一个集合,只能存储SaleItem
个对象。 class 中的属性允许 Price 和 Cost 存储为 Decimal
,日期存储为 DateTime
.
' temp var
Dim item As SaleItem
' create the collection
SalesItems = New List(Of SaleItem)
' load the data....all of it
Dim data = File.ReadAllLines("C:\Temp\custdata.csv")
' parse data lines
' Start at 1 to skip a Header
For n As Int32 = 0 To data.Length - 1
Dim split = data(n).Split(","c)
' check if it is a good line
If split.Length = 9 Then
' create a new item
item = New SaleItem
' store SOME data to it
item.CustName = split(0)
item.Phone = split(1)
' dont care anout user name (2)
item.Product = split(3)
' convert numbers
item.Price = Convert.ToDecimal(split(4))
item.Cost = Convert.ToDecimal(split(5))
' dont use the PROFIT, calculate it in the class (6)
' convert date
item.SaleDate = Convert.ToDateTime(split(7))
' ignore nonexistant RefCode (8)
' add new item to collection
' a List sizes itself as needed!
SalesItems.Add(item)
Else
' To Do: make note of a bad line format
End If
Next
' show in DGV for approval/debugging
dgvMem.DataSource = SalesItems
结果:
备注
存储可以简单计算的东西通常不是一个好主意。所以 Profit
属性 是:
Public ReadOnly Property Profit As Decimal
Get
Return (Cost - Price)
End Get
End Property
如果成本或价格更新,它永远不会“陈旧”。
如图所示,使用生成的集合可以非常容易地显示给用户。给定 DataSource
,DataGridView
将创建列并填充行。
正确的方法
String.Split(c)
是 一个非常糟糕的主意 因为如果产品是: "Hose, Small Green"
它会将其切碎并将其视为 2 个字段。有许多工具可以为您完成几乎所有工作:
- 读取文件
- 解析行
- 将 CSV 数据映射到 class
- 将文本转换为正确的数据类型
- 创建一个经济的集合
除了 class 之外,以上所有内容都可以使用 CSVHelper:
在短短几行中完成Private CustData As List(Of SaleItem)
...
Using sr As New StreamReader("C:\Temp\custdata.csv", False),
csv = New CsvReader(sr)
csv.Configuration.HasHeaderRecord = True
CustData = csv.GetRecords(Of SaleItem)().ToList()
End Using
两三行代码即可读取、解析和创建包含 250 项的集合。
即使您出于某种原因想要手动执行,CSVHelper 也可以提供帮助。您可以使用它来读取和解析数据,而不是为您创建 List(Of SaleItem)
:
... like above
csv.Configuration.HasHeaderRecord = True
Do Until csv.Read() = False
For n As Int32 = 0 To csv.Parser.FieldCount - 1
DoSomethingWith(csv.GetField(n))
Next
Loop
这将 return 字段一一呈现给您。它不会转换任何日期或价格,但也不会因缺少数据元素而阻塞。
资源
- CSVHelper
- Class vs Structure
- Eric Lippert's Thoughts on the matter
- File Class 有很多有用的方法
警告: 如果 CustomerName
或 ProductName
值可以包含逗号
(.i.e. CustomerName = "Callaway , Mark"
) 您不能使用 String.Split()
方法。最好搜索第三方 csv 解析器,或者您可以使用 TextFieldParser
Class --> MSDN article
您可以按照此 link 了解如何使用
导入 csvTextFieldParser
早些时候我在使用 SQL 服务器集成服务导入 Csv 文件时遇到问题(字段包含分隔符)你可以看一下(代码在 Vb.net ):
我的回答是假设缺少的字段 总是从行的右侧开始,并且字段值不包含逗号 (否则 @Plutonix 答案就是你要找的)
使用此代码,您将能够导入缺少字段的行。
您必须从 csv 文件中读取每一行,使用以下代码计算该行中 ","
的出现次数
Line.Count(Function(c As Char) c = ",")
如果计数小于 7
(8 列),您将添加缺少的 ","
String.PadRight((7 - intCommaCount), ",")
注意: 如果左侧缺少逗号,您可以使用 String.PadLeft((7 - intCommaCount), ",")
并将该行拆分为项目属性
我创建了以下 Item
Class
Public Class MyItem
Public Property CustomerName As String
Public Property PhoneNumber As String
Public Property Username As String
Public Property Product As String
Public Property WholesaleCost As String
Public Property SalesPrice As String
Public Property GrossProfit As String
Public Property CustomerReference As String
Public Shared Function CreateObjectFromLine(ByVal Line As String) As MyItem
'Count Comma occurence in Line
Dim intCommaCount As Integer = Line.Count(Function(c As Char) c = CChar(","))
Dim strTemp = Line
'Add missing comma's
If intCommaCount < 7 Then
strTemp = strTemp.PadRight((7 - intCommaCount), ",")
End If
'Split Line and return MyItem Class
Dim str() As String = strTemp.Split(",")
Return New MyItem With {.CustomerName = str(0),
.PhoneNumber = str(1),
.Username = str(2),
.Product = str(3),
.WholesaleCost = str(4),
.SalesPrice = str(5),
.GrossProfit = str(6),
.CustomerReference = str(7)}
End Function
End Class
我使用以下代码从 CSV 文件导入数据
Dim SalesItems As New List(Of MyItem)
Dim csvFile As String = "C:.csv"
Using csvStreamReader As New IO.StreamReader(csvFile)
While Not csvStreamReader.EndOfStream
Dim strLine as string = csvStreamReader.ReadLine
' Skip Header
If strLine.StartsWith("Customer Name") Then Continue While
Dim item As MyItem = MyItem.CreateObjectFromLine(strLine)
SalesItems.Add(item)
End While
End Using
'Showing Result in a DataGridView
dgvItems.DataSource = SalesItems
注意: 这是一个简单的例子,需要添加错误处理Try... Catch
,Null
检查