VB Visual Basic 2026 中文教程
第 17 课 · 持久保存资料

文件读写

List(Of T) 可以在程序运行期间保存多项数据,但程序关闭后,内存中的资料会消失。文件让我们把资料写到磁盘,并在下次启动程序时重新读取。本课从最简单的文本文件开始,逐步学习保存、读取、追加、检查文件是否存在,以及把 Student 对象列表保存成多行文字。

适合:完成第 16 课的初学者建议时间:60–75 分钟实作:StudentFileManager2026

本课学习目标

  • 理解内存资料与文件资料的区别。
  • 认识 System.IO 命名空间。
  • 使用 File.WriteAllText 写入文本文件。
  • 使用 File.ReadAllText 读取整个文本文件。
  • 使用 File.AppendAllText 追加内容。
  • 使用 File.Exists 检查文件是否存在。
  • 使用 Path.Combine 安全组合文件路径。
  • 使用 WriteAllLinesReadAllLines 处理多行资料。
  • List(Of Student) 保存到文件,并重新读取为对象。
  • 完成学生文件管理器。

17.1 内存与文件有什么不同?

变量、数组与 List(Of T) 通常存在内存中;程序结束后,这些数据不会自动保留下来。文件则保存在磁盘中,可以跨越多次程序运行继续使用。

内存中的 List(Of Student)

速度快,适合程序运行期间操作;关闭程序后资料通常消失。

磁盘中的 students.txt

可以长期保存,并在下一次启动程序时重新读取。

用户输入Form1
建立对象Student
加入列表List(Of Student)
写入文件students.txt
下次读取恢复列表

17.2 System.IO 命名空间

.NET 的文件与路径功能主要位于 System.IO 命名空间。

在 Form1.vb 最上方加入:

Imports System.IO

这样就可以直接写:

File.WriteAllText(...)
Path.Combine(...)

而不必每次写完整名称:

System.IO.File.WriteAllText(...)
System.IO.Path.Combine(...)

17.3 File.WriteAllText:写入整个文本文件

最简单的文件写入方式:

Dim filePath As String =
    Path.Combine(
        Environment.GetFolderPath(
            Environment.SpecialFolder.MyDocuments),
        "vb2026-note.txt")

File.WriteAllText(
    filePath,
    "欢迎学习 Visual Basic 2026。")

执行后,程序会在用户的 Documents 文件夹中建立或覆盖:

vb2026-note.txt
My Documents + vb2026-note.txt Path.Combine(...)
WriteAllText 会覆盖原内容 如果文件已经存在,旧内容会被新的字符串取代。需要保留旧内容时,应考虑 AppendAllText。

17.4 File.ReadAllText:读取整个文件

If File.Exists(filePath) Then

    Dim content As String =
        File.ReadAllText(filePath)

    txtOutput.Text = content

Else

    txtOutput.Text = "文件不存在。"

End If

ReadAllText 会把整个文本文件读成一个 String。

17.5 File.Exists:先检查文件

读取文件之前最好先确认它存在:

If File.Exists(filePath) Then
    txtOutput.Text = File.ReadAllText(filePath)
Else
    MessageBox.Show("找不到文件。")
End If

这样可以避免程序直接尝试读取不存在的文件。

17.6 File.AppendAllText:在文件末尾加入内容

File.AppendAllText(
    filePath,
    "第二行内容" & Environment.NewLine)

与 WriteAllText 不同,AppendAllText 不会先清除旧内容,而是把新内容加到文件末尾。

WriteAllText

重新写入

文件存在时覆盖原内容。

AppendAllText

追加内容

保留旧内容,在末尾继续加入。

ReadAllText

整个读取

一次把文件内容读成 String。

17.7 为什么使用 Path.Combine?

不要手工拼接:

folderPath & "\" & fileName

更推荐:

Dim filePath As String =
    Path.Combine(folderPath, fileName)

Path.Combine 会帮你正确处理路径分隔符,代码也更容易阅读。

17.8 使用 Documents 文件夹

可以通过 .NET 取得用户 Documents 文件夹:

Dim documentsFolder As String =
    Environment.GetFolderPath(
        Environment.SpecialFolder.MyDocuments)

再组合完整路径:

Dim filePath As String =
    Path.Combine(
        documentsFolder,
        "students.txt")
为什么不直接保存到程序安装目录? 某些目录可能需要额外写入权限。初学项目把个人资料文件放到用户 Documents 文件夹通常更容易理解和测试。

17.9 WriteAllLines 与 ReadAllLines

如果资料天然是一行一条记录,可以使用:

Dim lines() As String = {
    "李明|Visual Basic 2026|88",
    "王芳|C# Programming|76",
    "陈伟|Python Programming|91"
}

File.WriteAllLines(filePath, lines)

读取:

Dim lines() As String =
    File.ReadAllLines(filePath)

然后逐行处理:

For Each line As String In lines
    txtOutput.Text &=
        line & Environment.NewLine
Next

17.10 简单记录格式

为了让本课专注于文件读写,我们暂时使用竖线 | 分隔每个字段:

李明|Visual Basic 2026|88

它代表:

姓名|课程|成绩

读取后使用 Lesson 11 的 Split:

Dim parts() As String =
    line.Split("|"c)

然后:

Dim studentName As String = parts(0)
Dim course As String = parts(1)
Dim scoreText As String = parts(2)
这只是教学用的简单格式 如果姓名或课程本身包含 | 字符,解析就会产生问题。真正复杂的数据交换通常会使用 CSV、JSON 或数据库;JSON 会在第 33 课学习。

17.11 把 List(Of Student) 写入文件

假设 Form1 已有:

Private ReadOnly students As New List(Of Student)()

可以先把每个 Student 转成一行文字:

Dim lines As New List(Of String)()

For Each student As Student In students

    Dim line As String =
        $"{student.Name}|{student.Course}|{student.Score}"

    lines.Add(line)

Next

File.WriteAllLines(
    studentFilePath,
    lines)

17.12 从文件重新建立 Student 对象

students.Clear()

Dim lines() As String =
    File.ReadAllLines(studentFilePath)

For Each line As String In lines

    Dim parts() As String =
        line.Split("|"c)

    If parts.Length = 3 Then

        Dim score As Double

        If Double.TryParse(parts(2), score) Then

            Dim student As New Student(
                parts(0),
                parts(1),
                score)

            students.Add(student)

        End If

    End If

Next

这里把之前学过的 String、数组、TryParse、Student 类和 List(Of Student) 全部组合起来。

17.13 实作:学生文件管理器

建立新的 Windows Forms 项目:

StudentFileManager2026

项目保留 Student.vb,Form1 负责新增学生、显示列表、保存到文件和从文件读取。

Student File Manager 2026
学生姓名:
李明
课程:
Visual Basic 2026
成绩:
88
操作:
加入学生显示列表保存文件读取文件
文件:Documents\students2026.txt 学生人数:2 李明 | Visual Basic 2026 | 88 王芳 | C# Programming | 76

Form1 建议加入:

控件NameText / 设置
TextBoxtxtStudentName输入学生姓名
TextBoxtxtCourse输入课程名称
TextBoxtxtScore输入 0–100
ButtonbtnAddStudentText = 加入学生
ButtonbtnShowAllText = 显示列表
ButtonbtnSaveFileText = 保存文件
ButtonbtnLoadFileText = 读取文件
TextBoxtxtOutputMultiline = True;ReadOnly = True;ScrollBars = Vertical

Form1.vb 顶部

Imports System.IO

Public Class Form1

    Private ReadOnly students As New List(Of Student)()

    Private ReadOnly studentFilePath As String =
        Path.Combine(
            Environment.GetFolderPath(
                Environment.SpecialFolder.MyDocuments),
            "students2026.txt")

加入学生按钮

Private Sub btnAddStudent_Click(
    sender As Object,
    e As EventArgs) Handles btnAddStudent.Click

    Dim studentName As String =
        txtStudentName.Text.Trim()

    Dim course As String =
        txtCourse.Text.Trim()

    Dim score As Double

    If String.IsNullOrWhiteSpace(studentName) Then
        MessageBox.Show("请输入学生姓名。")
        txtStudentName.Focus()
        Exit Sub
    End If

    If String.IsNullOrWhiteSpace(course) Then
        MessageBox.Show("请输入课程名称。")
        txtCourse.Focus()
        Exit Sub
    End If

    If Not Double.TryParse(txtScore.Text, score) Then
        MessageBox.Show("请输入有效成绩。")
        txtScore.Focus()
        Exit Sub
    End If

    If score < 0 OrElse score > 100 Then
        MessageBox.Show("成绩必须介于 0 与 100。")
        txtScore.Focus()
        Exit Sub
    End If

    students.Add(
        New Student(
            studentName,
            course,
            score))

    txtStudentName.Clear()
    txtCourse.Clear()
    txtScore.Clear()
    txtStudentName.Focus()

    DisplayStudents()

End Sub

显示学生列表

Private Sub DisplayStudents()

    txtOutput.Clear()

    txtOutput.Text =
        $"文件:{studentFilePath}" &
        Environment.NewLine &
        $"学生人数:{students.Count}" &
        Environment.NewLine &
        Environment.NewLine

    For Each student As Student In students

        txtOutput.Text &=
            $"{student.Name} | " &
            $"{student.Course} | " &
            $"{student.Score:N0}" &
            Environment.NewLine

    Next

End Sub

显示列表按钮

Private Sub btnShowAll_Click(
    sender As Object,
    e As EventArgs) Handles btnShowAll.Click

    DisplayStudents()

End Sub

保存文件按钮

Private Sub btnSaveFile_Click(
    sender As Object,
    e As EventArgs) Handles btnSaveFile.Click

    Dim lines As New List(Of String)()

    For Each student As Student In students

        lines.Add(
            $"{student.Name}|" &
            $"{student.Course}|" &
            $"{student.Score}")

    Next

    File.WriteAllLines(
        studentFilePath,
        lines)

    MessageBox.Show(
        "学生资料已经保存。" &
        Environment.NewLine &
        studentFilePath)

End Sub

读取文件按钮

Private Sub btnLoadFile_Click(
    sender As Object,
    e As EventArgs) Handles btnLoadFile.Click

    If Not File.Exists(studentFilePath) Then
        MessageBox.Show("找不到学生资料文件。")
        Exit Sub
    End If

    students.Clear()

    Dim lines() As String =
        File.ReadAllLines(studentFilePath)

    For Each line As String In lines

        Dim parts() As String =
            line.Split("|"c)

        If parts.Length = 3 Then

            Dim score As Double

            If Double.TryParse(
                parts(2),
                score) Then

                students.Add(
                    New Student(
                        parts(0),
                        parts(1),
                        score))

            End If

        End If

    Next

    DisplayStudents()

    MessageBox.Show(
        $"已读取 {students.Count} 位学生。")

End Sub

End Class

17.14 程序执行流程

1
学生先进入 List

用户输入通过验证后,建立 Student 对象并加入 students。

2
保存时转换成文字

每个 Student 变成 Name|Course|Score 一行记录。

3
WriteAllLines 写入文件

多条记录一次保存到 students2026.txt。

4
读取时 Split

每一行重新拆成姓名、课程和成绩。

5
重新 New Student

从文本资料恢复为对象,再加入 List(Of Student)。

17.15 怎样测试文件读写?

  1. 加入两位学生。
  2. 点击“保存文件”。
  3. 到 Documents 文件夹确认 students2026.txt 已建立。
  4. 用记事本打开,确认每位学生占一行。
  5. 关闭并重新运行程序。
  6. 点击“读取文件”。
  7. 确认学生资料重新出现在列表输出中。
真正的持久化测试一定要重新启动程序 如果只在同一次运行中保存和显示,很难确认资料到底来自内存还是文件。

17.16 初学者常见错误

错误 1

读取前不检查 File.Exists

第一次运行时文件可能尚未建立,应先检查。

错误 2

误用 WriteAllText 覆盖旧内容

需要追加内容时应该使用 AppendAllText,或者先准备完整内容再一次写入。

错误 3

Split 后直接假设有三个字段

应先检查 parts.Length,避免损坏或不完整记录造成问题。

错误 4

保存对象本身的想法不清楚

文本文件保存的是文字表示;读取时需要把文字重新解析成对象。

17.17 小练习

  1. 新增“追加日志”按钮,把当前日期时间写入 activity.log,使用 AppendAllText
  2. 加入“文件是否存在”按钮,使用 File.Exists
  3. 为 Student 增加 StudentId,并把它加入保存格式。
  4. 读取文件时,遇到无效成绩记录就跳过,并统计跳过多少行。
  5. 新增“清除文件”按钮,使用 File.WriteAllText(studentFilePath, "")
  6. 尝试把课程名称包含空格的学生保存并重新读取,确认不会受影响。

本课复习

  1. 为什么 List(Of Student) 不能自动在程序关闭后保留资料?
  2. Imports System.IO 的作用是什么?
  3. WriteAllTextAppendAllText 有什么区别?
  4. 为什么读取文件前应该调用 File.Exists
  5. Path.Combine 为什么比手工拼接路径更好?
  6. WriteAllLines 适合什么数据结构?
  7. 读取 Student 记录时,为什么需要 Split?
  8. 为什么 Split 后要检查 parts.Length
  9. 文本文件中的一行怎样重新变成 Student 对象?
  10. 怎样确认“保存资料”真的具备持久化效果?