读取 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 中的属性允许 PriceCost 存储为 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

如果成本或价格更新,它永远不会“陈旧”。

如图所示,使用生成的集合可以非常容易地显示给用户。给定 DataSourceDataGridView 将创建列并填充行。

正确的方法

String.Split(c) 一个非常糟糕的主意 因为如果产品是: "Hose, Small Green" 它会将其切碎并将其视为 2 个字段。有许多工具可以为您完成几乎所有工作:

  1. 读取文件
  2. 解析行
  3. 将 CSV 数据映射到 class
  4. 将文本转换为正确的数据类型
  5. 创建一个经济的集合

除了 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 字段一一呈现给您。它不会转换任何日期或价格,但也不会因缺少数据元素而阻塞。

资源

警告: 如果 CustomerNameProductName 值可以包含逗号 (.i.e. CustomerName = "Callaway , Mark") 您不能使用 String.Split() 方法。最好搜索第三方 csv 解析器,或者您可以使用 TextFieldParser Class --> MSDN article

我的回答是假设缺少的字段 总是从行的右侧开始,并且字段值不包含逗号 (否则 @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... CatchNull检查