Vorsicht bei umfangreichen Strings
- Allgemein
Sie verwenden einen veralteten Browser (%browser%) mit Sicherheitsschwachstellen und können nicht alle Funktionen dieser Webseite nutzen.
Hier erfahren Sie, wie einfach Sie Ihren Browser aktualisieren können.
Hier erfahren Sie, wie einfach Sie Ihren Browser aktualisieren können.
Es gibt 32 Antworten in diesem Thema. Der letzte Beitrag () ist von Bartosz.
-
-
Bartosz schrieb:
Die Listbox zeigt den langen String nicht an.
ich mein, ein String-Array aus 800KB kann sehr unterschiedlich sein:
Das kann ein String der Länge 800.000 sein,
oder 100 Strings, davon 30 mit einer Länge von über 10.000
oder 5000 Strings, davon 50 mit einer Länge von über 4.000
also gib mal eine ungefähre Vorstellung: Wie lang sind die längsten, und wieviele gibts davon?
Zum andern scheint mir das möglich, dass viele überlange Strings die Listbox-Performance kaputtmachen - ich hatte das noch nie, aber denkbar ist sowas.
Da kann man sich DatenObjekte bauen, mit einer Kurz- und einer Lang-Version. Als Kurzversion nimmste einfach die ersten 50Zch des STrings, und LangVersion den String selbst.
Dann kannste mit Databinding die Listbox anweisen, nur die Kurz-Version anzuzeigen.
Die Langversion kann man dann - ebenfalls mit Databinding in einer Textbox anzeigen, welche nur einen String anzeigt, nämlich die Langversion des in der Listbox (als Kurzversion) angewählten Datenobjektes.
Das einfachste verwendbare DatenObjekt wäre Tuple(Of String, String), das hat die Properties Item1 und Item2 As String.
Ebensogut verwendbar wäre KeyValuePair(Of String, String), das hat die Properties Key und Value As String.
Also mach aus deinerPrivate ReadOnly ListOfUserData As New List(Of String)
eineList(Of Tuple(Of String, String))
, weise die der Listbox als DataSource zu - anstatt des.AddRange()
.
Und setzelb.DisplayMember
auf "Item1", und.ValueMember
auf "Item2".
Wenn du mir garnet folgen kannst, stell ein lauffähiges Testprojekt ein, dann kann man das gschwind hinbasteln.
-
-
-
-
-
-
Bartosz schrieb:
Das Problem ist die Übertragung an die Oberfläche. Die Listbox zeigt den langen String nicht an.
Stimmt:
List boxes store all the strings in the list box in one globally allocated segment. Windows limits the total amount of text in a list box to 64 kilobytes (K).
Mehr als 64K zeigt die Listbox insgesamt nicht an.
If $"{Convert.ToChar(Data(i))}{Convert.ToChar(Data(i + 1))}{Convert.ToChar(Data(i + 2))}{Convert.ToChar(Data(i + 3))}" = "udta" Then
Du erzeugst hier Millionen von Strings, wenn die Datei 1 MB hat. Überlegt mal, für jedes Byte in dem Array erzeugst du ein 4 Zeichen langen String, um dann zu schauen, ob dieser = "udta" ist. Natürlich kann dir hier alles explodieren. Das ist doch nicht nötig, wenn du das z.B. einfach so abfragst:
VB.NET-Quellcode
Und das gleiche nochmal in deinemGet_UserData
. Hier kannst du optimieren. -
hier mein Versuch, entsprechend der Skizze aus post#22:
VB.NET-Quellcode
- Public Class Form1
- Private Data As Byte()
- Private ReadOnly Latin1 As System.Text.Encoding = System.Text.Encoding.GetEncoding("iso-8859-1")
- Private ReadOnly ListOfUserData As New List(Of Tuple(Of String, String))
- Private Const _256_3 As UInt32 = 16777216UI
- Private Const _256_2 As UInt32 = 65536UI
- Public Sub New()
- InitializeComponent()
- ListBox1.DisplayMember = "Item1"
- ListBox1.ValueMember = "Item2"
- End Sub
- Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
- Dim path As String = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\Testdatei.txt"
- path = "..\..\Testdatei.txt"
- If Not System.IO.File.Exists(path) Then
- Return
- End If
- Me.Data = System.IO.File.ReadAllBytes(path)
- ListOfUserData.Clear()
- For i As Integer = 0 To Data.Length - 4 Step 1
- If $"{Convert.ToChar(Data(i))}{Convert.ToChar(Data(i + 1))}{Convert.ToChar(Data(i + 2))}{Convert.ToChar(Data(i + 3))}" = "udta" Then
- Get_UserData(i)
- End If
- Next
- 'for test: create dummi-userdata
- Me.ListOfUserData.Add(Tuple.Create("dummi1", String.Concat(Enumerable.Repeat("dummi1 ", 100))))
- Me.ListOfUserData.Add(Tuple.Create("dummi2", String.Concat(Enumerable.Repeat("dummi2_", 100))))
- ListBox1.DataSource = Nothing : ListBox1.DataSource = ListOfUserData
- End Sub
- Private Sub Get_UserData(i As Integer)
- Dim boxSize0 As UInt32 = Data(i - 4) * _256_3 + Data(i - 3) * _256_2 + Data(i - 2) * 256UI + Data(i - 1)
- For j As Integer = i + 8 To i + CInt(boxSize0) - 8 Step 1 ' nicht 4
- If Latin1.GetString({Data(j), Data(j + 1), Data(j + 2), Data(j + 3)}) = "data" Then
- Dim boxSize1 As UInt32 = Data(j - 4) * _256_3 + Data(j - 3) * _256_2 + Data(j - 2) * 256UI + Data(j - 1)
- 'Dim processedData As Byte() = New Byte(CInt(boxSize1) - 8 - 1) {}
- ''Array.Copy(Data, j + 4, processedData, 0, processedData.Length)
- 'Using ms As New IO.MemoryStream(Me.Data)
- ' ms.Seek(j + 4, IO.SeekOrigin.Begin)
- ' ms.Read(processedData, 0, processedData.Length)
- 'End Using
- ''Dim processedData = Me.Data.Skip(j + 4).Take(CInt(boxSize1) - 8).ToArray
- 'Dim obtainedString As String = System.Text.Encoding.UTF8.GetString(processedData).TrimStart({NullChar, Convert.ToChar(1), Convert.ToChar(24)})
- ' UTF8 according to the standard
- Dim obtainedString = System.Text.Encoding.UTF8.GetString(Me.Data, j + 4, CInt(boxSize1) - 8).TrimStart({NullChar, Convert.ToChar(1), Convert.ToChar(24)})
- Dim itm1 = New String(obtainedString.Take(50).ToArray)
- Me.ListOfUserData.Add(Tuple.Create(itm1, obtainedString))
- j += CInt(boxSize1) - 1 ' Do NOT write "Exit For" because these are single items
- End If
- Next
- End Sub
- Private Sub ListBox1_SelectedValueChanged(sender As Object, e As EventArgs) Handles ListBox1.SelectedValueChanged
- RichTextBox1.Text = ListBox1.SelectedValue?.ToString
- End Sub
- End Class
Die vollständigen Strings sind dann in beigeordneter Richtextbox angezeigt (zeile #55)
beachte auch, dass der ganze MemoryStream-Quack üflüssig ist (auskommentiert), wenn man die geeignete Encoding.GetString-Überladung verwendet - zeile #46.Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „ErfinderDesRades“ ()
-
-
Bartosz schrieb:
Das Problem ist die Übertragung an die Oberfläche.Warum willst du das an die Oberfläche übertragen? Das ist doch gar nicht lesbar.War noch auf Seite 1 hängengeblieben
-
ich hab den Code von post#29 nochma überarbeitet, weil im Auslesen hast du glaub einen Lese-Fehler drin:
Wenn innerhalb eines "data"-Datenblocks in den RohDaten zufällig mal die Byte-Folge für "udta" oder "data" auftaucht, so wird das bei dir als weiterer Datenblock aufgefasst, und dann iwelcher Mist eingelesen.
Daher habich jetzt mal was mit ArraySegments gebastelt, wo ein ArraySegment einen DatenContent repräsentiert. So kann man einen Datenblock auslesen, und wenn wolle den nächsten dahinter.
Und das Lesen des hinteren fängt auch erst danach an - sodass obige Mis-Interpretation vermieden wird.
Weil das Datenformat der Rohdaten ist ganz pfiffig: Es gibt Datenblöcke mit je einem Header und Content. Der Header besteht aus LängenAngabe (4 Bytes) und Signatur ("udta" oder "data").
So kann man mehrere gleichartige Datenblöcke hintereinander lesen, aber es können auch innerhalb des einen Datenblocks Datenblöcke eingeschachtelt sein mit anderer Signatur (nämlich zB ein "udta"-Datenblock kann viele "data" enthalten)
Schlanker und effizienter ist der Code dabei auch geworden:
VB.NET-Quellcode
- Public Class Form1
- Private Shared ReadOnly readIntBuffer(3) As Byte
- 'Private ReadOnly Latin1 As System.Text.Encoding = System.Text.Encoding.GetEncoding("iso-8859-1")
- Private Shared ReadOnly Utf8 As System.Text.Encoding = System.Text.Encoding.UTF8
- Private ReadOnly ListOfUserData As New List(Of Tuple(Of String, String))
- Public Sub New()
- InitializeComponent()
- ListBox1.DisplayMember = "Item1"
- ListBox1.ValueMember = "Item2"
- End Sub
- Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
- Dim path As String = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\Testdatei.txt"
- path = "..\..\Testdatei.txt"
- If Not System.IO.File.Exists(path) Then
- Return
- End If
- Dim fileBytes = System.IO.File.ReadAllBytes(path)
- ListOfUserData.Clear()
- Dim frame = New ArraySegment(Of Byte)(fileBytes)
- Dim udta As ArraySegment(Of Byte) = GetFirstContent(frame, "udta")
- While udta <> Nothing
- Dim data As ArraySegment(Of Byte) = GetFirstContent(udta, "data")
- While data <> Nothing
- Dim obtainedString = Utf8.GetString(fileBytes, data.Offset, data.Count).TrimStart({NullChar, Convert.ToChar(1), Convert.ToChar(24)})
- Dim itm1 = New String(obtainedString.Take(50).ToArray)
- Me.ListOfUserData.Add(Tuple.Create(itm1, obtainedString))
- data = GetNextContent(udta, "data", data)
- End While
- udta = GetNextContent(frame, "udta", udta)
- End While
- 'for test: create dummi-userdata
- Me.ListOfUserData.Add(Tuple.Create("dummi1", String.Concat(Enumerable.Repeat("dummi1 ", 100))))
- Me.ListOfUserData.Add(Tuple.Create("dummi2", String.Concat(Enumerable.Repeat("dummi2_", 100))))
- ListBox1.DataSource = Nothing : ListBox1.DataSource = ListOfUserData
- End Sub
- Private Sub ListBox1_SelectedValueChanged(sender As Object, e As EventArgs) Handles ListBox1.SelectedValueChanged
- RichTextBox1.Text = ListBox1.SelectedValue?.ToString
- End Sub
- ''' <summary>liest aus arr einen Integer an Position i</summary>
- Private Shared Function ReadInt(arr As Byte(), i As Integer) As Integer
- Array.Copy(arr, i, readIntBuffer, 0, 4)
- Array.Reverse(readIntBuffer)
- Return BitConverter.ToInt32(readIntBuffer, 0)
- End Function
- ''' <summary>sucht in frame nach dem ersten Datenblock, dessen Header auf key matcht.</summary>
- Private Shared Function GetFirstContent(frame As ArraySegment(Of Byte), key As String) As ArraySegment(Of Byte)
- Return GetNextContent(frame, key, New ArraySegment(Of Byte)(frame.Array, frame.Offset, 0))
- End Function
- ''' <summary>sucht in frame nach dem ersten Datenblock, dessen Header auf key matcht, und der hinter previous liegt.</summary>
- Private Shared Function GetNextContent(frame As ArraySegment(Of Byte), key As String, previous As ArraySegment(Of Byte)) As ArraySegment(Of Byte)
- Dim arr = frame.Array
- Dim pattern = Utf8.GetBytes(key)
- For i = previous.Offset + previous.Count + 4 To frame.Offset + frame.Count
- If pattern.SequenceEqual(Enumerable.Range(i, pattern.Length).Select(Function(x) arr(x))) Then 'pattern-match?
- Dim contentSize = ReadInt(arr, i - 4) - (4 + pattern.Length)
- Return New ArraySegment(Of Byte)(arr, i + pattern.Length, contentSize)
- End If
- Next
- Return Nothing
- End Function
- End Class
Dieser Beitrag wurde bereits 4 mal editiert, zuletzt von „ErfinderDesRades“ ()
-
Vielen lieben Dank für deine Ausarbeitung.
ArraySegment
ist neu für mich. Danke dafür. Mir fällt auch auf, dass man nicht mehr auf Indizes (Data(i)
) angewiesen ist und damit eine Datei, die größer als 2GB ist, lesen kann. Man bräuchte dann nur eine Implementierung für alle Boxen, also nicht nur udta. Aber darum geht es hier nicht.
Ich setze den Thread auf erledigt.
-
Ähnliche Themen
-
Mete1997 - - Off-Topic
-
7 Benutzer haben hier geschrieben
- Bartosz (14)
- ErfinderDesRades (8)
- Haudruferzappeltnoch (6)
- Bluespide (2)
- slice (1)
- oobdoo (1)
- DTF (1)