我希望我的数据库不填满 RAM。如何处理当前未被查看的图像?

I want my Database to not fill the RAM. What to do with images that are currently not being viewed?

我的程序是做什么的?

截至 2021 年 5 月 5 日

此程序是使用语言 VB.Net、.NET Framework 4.8 和 Visual Studio 2019 CE 开发的。这个程序的重点是 运行 一个基本的数据库。该视图类似于 classic Internet 论坛 - 有线程,线程中有不同数量的 postings,每个 post 中有不同数量的图片和长文本.如果线程是使用 ComboBox select 编辑的,所有 post 及其图像和文本将一个接一个地显示。当您单击特定 post 时,仅显示 图像。由于该数据库仅适用于公司的产品,因此决定不使用类别(例如图像与视频与非主题,因为它没有任何意义)和子类别(例如电子产品与木制品)。

程序关闭时,会询问是否保存数据。 (仍处于测试版)。这些数据在程序加载时被读入。如果找不到图像,它们的路径将显示在 window.

用户还可以选择搜索所有线程并使用各种排序选项查看结果。在这种情况下,只有找到的 post 列在列表框中,在这里,用户也可以 select 个 post 并将它们放大显示。

程序启动时读入用户数据。用户可以登录,并根据其角色具有一定的决策权。 “普通”用户可以创建主题和 posts,但只有管理员或版主可以编辑和删除 posts;并阻止用户。如果您未登录或被锁定,则只能阅读线程和 posts.

每个用户的贡献数量。以后应该可以给一个用户star。

关于classes

Form1.vb class 和其他三个重要的 classes:Class_ForumClass_PostClass_Thread。还有就是Class_Userclass。如果一个新的 post 被创建,这个 Class_Post 的实例被添加到一个 List(of Class_Post),它位于 Class_Thread(“线程知道哪个 posts它有”)。 Class_Post 有一个成员‘Made_by’,它是 Class_Users 的一个实例(“每个 post 都知道是哪个用户创建的”)。 Class_Post 包含成员‘Bilder’ (=Images),这是一个 List(Of Bitmap)。也就是说,class_post 的每个实例都有一个 List(of Bitmap)。

还有几种形式1)编辑、删除posts,2)屏蔽或解屏蔽用户,3)显示放大图片,4)登录,5)当图片时显示发现没有加载,6)打开线程,7)到post.

程序启动时,即读入数据时,会创建线程实例和post个实例。

待办事项: 1.) 但是,我希望只有图像在 RAM 中,它们属于使用 combobox1 编辑的线程 select。我的问题是:我是否必须丢弃所有不需要的图像并在需要时再次读取它们?我们内置了吗?

这是我从格式化的 txt 文件加载数据的代码。我有一种感觉,在这里的某个地方,或者紧接在这里之后,我必须做点什么。

Private Sub Daten_laden()
        Dim Pfad As String 'file path
        Using OFD1 As New CommonOpenFileDialog
            OFD1.Title = "Textdatei auswählen"
            OFD1.Filters.Add(New CommonFileDialogFilter("Textdatei", ".txt"))
            OFD1.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)
            Dim Result As CommonFileDialogResult
            Me.Invoke(Sub() Result = OFD1.ShowDialog())
            If Result = CommonFileDialogResult.Ok Then
                Pfad = OFD1.FileName
            Else
                Return
            End If
        End Using

        Dim Pruef_Anzahl_Posts_in_dem_Thread As Integer = 0 'Check number of posts in the thread 
        Dim Liste_mit_den_Pfaden As List(Of String) 'List with file paths
        Dim Liste_mit_den_Bildern As List(Of System.Drawing.Bitmap)
        'read all Text
        Dim RAT() As String = System.IO.File.ReadAllLines(Pfad, System.Text.Encoding.UTF8)

        Pfade_von_nicht_gefundenen_Bildern = New List(Of String) ' Paths of not found images

        For i As Integer = 3 To RAT.Length - 2 Step 1
            Liste_mit_allen_Threads.Add(New Class_Thread(RAT(i)))
            Me.Invoke(Sub() RaiseEvent Es_wurde_ein_neuer_Thread_eroeffnet()) 'A new thread has been opened
            Pruef_Anzahl_Posts_in_dem_Thread = CInt(RAT(i + 1))

            For j As Integer = (i + 2) To RAT.Length - 2 Step 1
                Liste_mit_den_Pfaden = New List(Of String)
                Liste_mit_den_Bildern = New List(Of Bitmap)
                Dim Index As Integer
                Dim Die_ID_des_Nutzers_der_den_Post_erstellt_hat As ULong = CULng(RAT(j + 4)) 'The ID of the user who created the post
                For u As Integer = 0 To alle_Nutzer_Liste.Count - 1 Step 1
                    If alle_Nutzer_Liste(u).ID = Die_ID_des_Nutzers_der_den_Post_erstellt_hat Then
                        Index = u
                        Exit For
                    End If
                Next
                Dim neuerPost As New Class_Post(RAT(j), RAT(j + 1), CUShort(RAT(j + 2)), Liste_mit_den_Bildern, CDate(RAT(j + 3)), Liste_mit_den_Pfaden, alle_Nutzer_Liste(Index))
               'how many threads are there already                
                Dim wie_viele_Threads_gibt_es_bereits As Integer = Liste_mit_allen_Threads.Count
                Liste_mit_allen_Threads(wie_viele_Threads_gibt_es_bereits - 1).Posts_in_diesem_Thread.Add(neuerPost)
                ' Set the index to the last possible one in the ComboBox. This causes the program to run into the Selected Index event and SI becomes the selected index. 
                Me.Invoke(Sub() ComboBox1.SelectedIndex = Liste_mit_allen_Threads.Count - 1)
                j += 5
                Do
                    Liste_mit_den_Pfaden.Add(RAT(j))
                    If System.IO.File.Exists(RAT(j)) Then
                        Liste_mit_den_Bildern.Add(New Bitmap(RAT(j)))
                    Else
                        Pfade_von_nicht_gefundenen_Bildern.Add(RAT(j))
                    End If
                    j += 1
                Loop Until RAT(j) = "#" ' Marker: a post is over 
                If RAT(j + 1) = "" AndAlso RAT(j + 2) = "" Then ' A new thread is marked with 2 blank lines one below the other.
                    i = (j + 2)
                    Exit For
                End If
            Next
        Next
        Me.Invoke(Sub() alle_Posts_in_diesem_Thread_anzeigen()) 'show all posts in this thread
        If Pfade_von_nicht_gefundenen_Bildern.Count > 0 Then ' In Case something went wrong
            Using FBNG As New Form_Bild_nicht_gefunden
                FBNG.Datei_anzeigen(Pfade_von_nicht_gefundenen_Bildern)
                
                FBNG.ShowDialog()
            End Using
        End If
    End Sub

在此图像上,正在使用组合框切换线程,该组合框更改我经常使用的变量 SI。在此示例中,线程 Caucasian 包含 1 post,其中包含 2 个图像。 这意味着,(还在这一刻)我不需要图像形式 shepherds thread

将所有数据都保存在 RAM 中是不明智的。当您的数据库增长时,有时会出现数据过多的情况。

为了克服这个问题,人们发明了数据库:数据保存在磁盘上,只有你请求的数据才会被放入内存。智能数据库会将重要数据和常用值保存在内存中,以最大限度地减少请求时间。

如果我看你的程序,似乎你只需要在操作员输入后改变显示。操作员输入相对较慢:如果您每秒可以输入超过 3 个字符,那么您就是一名优秀的打字员。通常操作员输入后的响应时间不是问题:如果你在半秒内得到数据,没有人会抱怨。

对于现代计算机来说,半秒的时间足以检查一百万条记录。在您的应用程序中,数据库不会成为问题。

所以我的建议是:开始使用数据库并仅加载现在需要的数据,而不是在启动时读取所有数据。仅当您遇到较长的请求时间时,才考虑加载您预计很快就会需要的数据。

唉,要使用数据库,您需要学习一些新知识:至少是如何构建数据库。如果我看你的表格,我的印象是你已经掌握了这一点。此外,您还必须学习如何添加/查询/更新/删除数据。这通常使用 SQL 或支持 LINQ 的软件来完成,例如 entity framework.

在我看来,您的查询数量非常有限:您不会有数百个不同的查询。如果您已经知道 SQL,并且您认为在不久的将来不需要知道 entity framework,我会使用 SQL.

访问数据库

如果您不太了解 SQL,或者如果您需要执行大量不同的查询,请考虑使用 LINQ 访问数据库。这需要 entity framework.

如果您还没有数据库,我的第一步是使用 SQL正确:一个文件中的数据库,速度足以满足您的应用程序需求。

如果您正确地隐藏了您使用 SQL的权利,那么在需要时迁移到更智能的数据库不需要对您的应用程序进行大的更改。

class Repository
{
    public long AddPost(Post post) {...} // add Post to the database, returns the Id
    public long AddUser(User user) {...}
    ...

    // fetch all Posts of a User:
    public User FetchUserWithHisPosts(int userId);

    // fetch Posts of a User after a certain data:
    public User FetchUserWithHisPosts(int userId, DateTime startDate);

    ...
}

存储库是某种仓库:您所知道的是您可以在其中存储项目,并在以后检索它们,即使在您的计算机重新启动后也是如此。

存储库隐藏了它是如何做到这一点的:构造函数可能会将所有内容加载到内存中(比如您当前的应用程序),也可能是存储库使用 SQLight,或者更智能的数据库,甚至 Entity Framework.

迁移的一个好方法是首先转换您当前的应用程序,这样每个人都只能使用存储库访问数据。存储库访问您的“内存数据”,它位于启动时加载的单独 class 中。

稍后您可以更改存储库,使其不再使用“内存中数据”,而是访问数据库:存储库的用户不必更改。

关于加载图片

不,您不必在启动时加载所有图片。仅在显示时加载图片足够快:毕竟,您不会一次在屏幕上显示 1000 张图片。

由于图片会占用大量内存,明智的做法是在您不再需要图片时Dispose()该图片:

Image GetImage(long imageId)
{
    Repository repository = new Repository();
    return repository.GetImageById(imageId); 
}

void DisposeImage(Image image)
{
    image.Dispose();
}

您隐藏了加载图像的方式,以及在不再需要图像后释放内存的方式。这使得更改它更容易,以后可能需要。它还使您的代码更易于阅读和单元测试。

你好@Harald Coppoolse, 我感谢您提供详细且易于理解的答案。诚然,我必须查看和学习那些 SQL (Light) 和 Entity 程序。

目前,我暂时确保只加载显示的图片并处理所有其他图片。我将其编码如下:

Private Sub ComboBox1_SelectedIndexChanged(sender As Object, e As EventArgs) Handles ComboBox1.SelectedIndexChanged
        If ComboBox1.SelectedIndex <> (-1) Then
            SI = ComboBox1.SelectedIndex
            If Programm_fertig_geladen Then
                Load_the_pictures_only_when_shown_and_free_up_memory_after_an_Image_is_not_needed()
            End If
            alle_Posts_in_diesem_Thread_anzeigen()
        End If
    End Sub

Private Sub Load_the_pictures_only_when_shown_and_free_up_memory_after_an_Image_is_not_needed()
        For g As Integer = 0 To Liste_mit_allen_Threads.Count - 1 Step 1
            If g <> SI Then 'Diese Bilder verwerfen
                For i As Integer = 0 To Liste_mit_allen_Threads(g).Posts_in_diesem_Thread.Count - 1 Step 1
                    For j As Integer = 0 To Liste_mit_allen_Threads(g).Posts_in_diesem_Thread(i).Bilder.Count - 1 Step 1
                        Liste_mit_allen_Threads(g).Posts_in_diesem_Thread(i).Bilder(j).Dispose()
                        Liste_mit_allen_Threads(g).Posts_in_diesem_Thread(i).BoolArray(j) = False
                    Next
                    Dim CNT As Integer = Liste_mit_allen_Threads(g).Posts_in_diesem_Thread(i).Bilder.Count
                    For z As Integer = (CNT - 1) To 0 Step -1
                        Liste_mit_allen_Threads(g).Posts_in_diesem_Thread(i).Bilder.RemoveAt(z)
                    Next

                Next
            Else 'Diese Bilder laden
                'für jeden Post
                For i As Integer = 0 To Liste_mit_allen_Threads(g).Posts_in_diesem_Thread.Count - 1 Step 1
                    'die Bilder in dem Post
                    For j As Integer = 0 To Liste_mit_allen_Threads(g).Posts_in_diesem_Thread(i).Pfade_der_Bilder.Count - 1 Step 1
                        Dim neu_geladenes_Bild As New Bitmap(Liste_mit_allen_Threads(g).Posts_in_diesem_Thread(i).Pfade_der_Bilder(j))
                        If Liste_mit_allen_Threads(g).Posts_in_diesem_Thread(i).BoolArray(j) = False Then
                            Liste_mit_allen_Threads(g).Posts_in_diesem_Thread(i).Bilder.Add(neu_geladenes_Bild)
                            Liste_mit_allen_Threads(g).Posts_in_diesem_Thread(i).BoolArray(j) = True
                        End If
                        'End Using
                    Next
                Next
            End If
        Next
        
    End Sub

我在 Class_Post 中构建了一个布尔数组,它的成员数与 post 中的图像和路径一样多。这样做的目的是确保我记得哪些图片有哪些没有。

  • 当处理post的图像时,相应的数组成员设置为false

  • 重新加载post的图像时,相应的数组成员设置为True

  • Dispose 是不够的,因为有趣的是, List(of Bitmap) 中的位置仍然被占用。您还必须使用 RemoveAt 完全删除图片,但是在 backward 运行 for 循环中,因为列表在删除过程中变小了。 ;)

  • Using不能用