Benutzer mit den meisten Antworten
Array dynamisch vergrößern

Frage
-
Hallo;
ich möchte ganze Verzeichnise (inkl. Dateien) kopieren. Vor dem Kopieren möchte ich die Datei-Namen (inkl. Pfade) in ein Array kopieren.
Die Länge des Arrays ist logischerweise bei der Definition nicht bekannt und kann varieren.
Mit dem Array (Aufbau: SourceDatei, TargetDatei) als InputParameter möchte ich die eigentlichte Copy-Funktion starten.
Die eigentliche Frage: da ich die ArrayLänge nicht kenne, wie lege ich die zur Laufzeit fest??
Ich habe mit ArrayList gespielt, aber später müßte ich ja die Daten aus der ArrayList sowieso in ein Array übernehmen-die Länge hätte ich: ArrayList.Count.
Oder kann ich das Problem doch ohne Umwege mit dem Array lösen?
Danke für Eure Hilfe.
Antworten
-
Hi,
ich möchte ganze Verzeichnise (inkl. Dateien) kopieren. Vor dem Kopieren möchte ich die Datei-Namen (inkl. Pfade) in ein Array kopieren.
Die Länge des Arrays ist logischerweise bei der Definition nicht bekannt und kann varieren.
Sinnvoller als sowas wäre eher eine List<T> oder ähnliches. Was genau in Frage kommt, hängt davon ab, was Du letztendlich machen willst.
Mit dem Array (Aufbau: SourceDatei, TargetDatei) als InputParameter möchte ich die eigentlichte Copy-Funktion starten.
Da würde ich wirklich eine List<T> nehmen. Du baust eine eigene Klasse mit zwei Properties (Source, Target) und erzeugst dann eine List<DeineKlasse>.
Gruß, Stefan
Microsoft MVP - Visual Developer ASP/ASP.NET
http://www.asp-solutions.de/ - Consulting, Development
http://www.aspnetzone.de/ - ASP.NET Zone, die ASP.NET Community- Als Antwort markiert Purclot Mittwoch, 13. April 2011 07:42
-
Hallo P.,
ich gehe mal stärker auf Deine Original-Titel-Frage ein, die da lautet: "Array dynamisch vergrößern" (Die GetFiles-Methode ist ja eh sicher bekannt und Verfahren zum Kopieren von Verzeichnissen in C#). Für dynamische Vergrösserung sind Klassen wie List<string>, oder StringCollection mit der Add etc. empfehlenswert.
List<string> dateiNamen = new List<string>(); dateiNamen.Add("Eins"); dateiNamen.Add("Zwei"); MessageBox.Show("List<string>: " + String.Join(", ", dateiNamen)); StringCollection dateiNamen2 = new StringCollection(); dateiNamen2.Add("Eins"); dateiNamen2.Add("Zwei"); MessageBox.Show("StringCollection: " + dateiNamen2[0] + ", " + dateiNamen2[1]); ArrayList dateiNamen3 = new ArrayList(); dateiNamen3.Add("Eins"); dateiNamen3.Add("Zwei"); MessageBox.Show("ArrayList: " + String.Join(", ", dateiNamen3.ToArray()));
Bei Array's ist die Erhöhung der Capacity manuell zu implementieren - etwa:
string[] dateiNamen4 = new string[2]; dateiNamen4[0] = "Eins"; dateiNamen4[1] = "Zwei"; var tmp = new string[dateiNamen4.Length+1]; Array.Copy(dateiNamen4, tmp, dateiNamen4.Length); tmp[tmp.Length - 1] = "Drei"; dateiNamen4 = tmp; MessageBox.Show("Array: " + String.Join(", ", dateiNamen4));
Die List<T> Klassen (u.a.) machen das intern letztlich auch (ähnlich) in bestimmten heuristisch vorteilhaften Schritten (zuerst 4, dann 'items.Lenght*2') der Vor-Allokierung.
Man kann eine ArrayList o.ä. auch am Ende wieder in ein Array umwandlen - etwa:
string[] array3 = new string[dateiNamen3.Count]; dateiNamen3.CopyTo(array3);
oder bei List<string> :string[] array2 = dateiNamen.ToArray<string>();
- Als Antwort markiert Purclot Mittwoch, 13. April 2011 07:42
-
Hallo Purclot,
> Die eigentliche Frage: da ich die ArrayLänge nicht kenne, wie lege ich die zur Laufzeit fest??
Wenn ich Dich richtig verstehe, möchtest Du ein zweidimensionales Array vom Typ string[,] an eine Kopierfunktion übergeben, z.B. void Copy(string[,]). Vorher möchtest du dieses zweidimensionale Array nach Bedarf dimensionieren können.
Lass uns zunächst einmal klären was Neudimensionierung in C# bedeutet: Da Arrayelemente auf dem Heap kompakt (am Stück) und ohne Lücken gespeichert werden, bedeutet Neudimensionieren dass zunächst ein neues Array erstellt werden muss und anschließend alle Elemente des alten Arrays in das neue Array kopiert werden müssen. Da diese Operation an sich schon O(n) ist und Dateisysteme dazu neigen, in manchen Verzeichnissen sehr viele, in andere hingegen sehr wenige Dateien zu enthalten, würde das fortwährende Neudimensionieren des Arrays ernsthafte Performanzeinbußen mit sich bringen. Nun könnte man sicherlich nicht verwaltete Funktionen wie etwa CopyMemory einsetzen, um das Ganze ein klein bischen zu beschleunigen, aber richtig Abhilfe schaftt auch das nicht.Ich stelle mir vor, dass Du Arrays verwenden möchtest, weil sie in der Alloziierung und in der Zugriffszeit so performant sind. Wenn Du sie aber mit jedem neuen Verzeichnis das Du verarbeitest erweitern mußt, mußt Du auch einen Performanzpreis zahlen. Freilich könnte man die Erweiterung in chunks (blockweise) vornehmen, oder gar einen adaptiven Algorithmus benutzen, der die neu zu allozierende Blockgröße anhand der vorhergehenden "Erfahrungen" bestimmt.
Aber steht das alles nicht schon zur Verfügung? List<T> speichert seine Elemente intern in einem T[]-Array. Sobald man die Methode List<T>.Add() aufruft, wird intern die Methode EnsureCapacity(int min) aufgerufen, die List<T>.Capacity auf T[].Length * 2 setzt. Ein eher grober adaptiver Algorithmus, aber wenn die Speichergröße kein Problem darstellt, sehr pragmatisch. Und wenn man die Größe des internen Arrays auf die tatsächliche Anzahl der Elemente reduzieren will, kann man noch immer List<T>.TrimExcess() aufrufen.Aber Du wolltest nicht nur die Quelldateien zu einer Auflistung hinzufügen, sondern auch gleich die Zieldateien. Du könntest als T in List<T> entweder eine Klasse/Struktur verwenden, oder Du nimmst gleich ein Dictionary<string, string> oder ein StringDictionary, die ähnlich wie List<T> funktionieren, Dir aber ermöglichen, Listenelemente als Paar hinzuzufügen. Wenn Du C# 4.0 verwendest, könntest Du auch ein Tuple dazu einsetzen.
So könnte das Ganze - skizziert - aussehen:
using System; using System.Collections.Generic; using System.IO; namespace WindowsFormsApplication33 { static class Program { [STAThread] static void Main() { string originalFile = @"C:\Quelldatei.txt"; string targetFile = @"C:\Zieldatei.txt"; Dictionary<string, string> filesToCopy = new Dictionary<string, string>(); filesToCopy.Add(originalFile, targetFile); Copy(filesToCopy); } private static void Copy(Dictionary<string, string> filesToCopy) { foreach (KeyValuePair<string, string> filePair in filesToCopy) { File.Copy(filePair.Key, filePair.Value); } } } }
Gruß
Marcel- Als Antwort markiert Purclot Mittwoch, 13. April 2011 07:42
Alle Antworten
-
Hi,
ich möchte ganze Verzeichnise (inkl. Dateien) kopieren. Vor dem Kopieren möchte ich die Datei-Namen (inkl. Pfade) in ein Array kopieren.
Die Länge des Arrays ist logischerweise bei der Definition nicht bekannt und kann varieren.
Sinnvoller als sowas wäre eher eine List<T> oder ähnliches. Was genau in Frage kommt, hängt davon ab, was Du letztendlich machen willst.
Mit dem Array (Aufbau: SourceDatei, TargetDatei) als InputParameter möchte ich die eigentlichte Copy-Funktion starten.
Da würde ich wirklich eine List<T> nehmen. Du baust eine eigene Klasse mit zwei Properties (Source, Target) und erzeugst dann eine List<DeineKlasse>.
Gruß, Stefan
Microsoft MVP - Visual Developer ASP/ASP.NET
http://www.asp-solutions.de/ - Consulting, Development
http://www.aspnetzone.de/ - ASP.NET Zone, die ASP.NET Community- Als Antwort markiert Purclot Mittwoch, 13. April 2011 07:42
-
Hallo P.,
ich gehe mal stärker auf Deine Original-Titel-Frage ein, die da lautet: "Array dynamisch vergrößern" (Die GetFiles-Methode ist ja eh sicher bekannt und Verfahren zum Kopieren von Verzeichnissen in C#). Für dynamische Vergrösserung sind Klassen wie List<string>, oder StringCollection mit der Add etc. empfehlenswert.
List<string> dateiNamen = new List<string>(); dateiNamen.Add("Eins"); dateiNamen.Add("Zwei"); MessageBox.Show("List<string>: " + String.Join(", ", dateiNamen)); StringCollection dateiNamen2 = new StringCollection(); dateiNamen2.Add("Eins"); dateiNamen2.Add("Zwei"); MessageBox.Show("StringCollection: " + dateiNamen2[0] + ", " + dateiNamen2[1]); ArrayList dateiNamen3 = new ArrayList(); dateiNamen3.Add("Eins"); dateiNamen3.Add("Zwei"); MessageBox.Show("ArrayList: " + String.Join(", ", dateiNamen3.ToArray()));
Bei Array's ist die Erhöhung der Capacity manuell zu implementieren - etwa:
string[] dateiNamen4 = new string[2]; dateiNamen4[0] = "Eins"; dateiNamen4[1] = "Zwei"; var tmp = new string[dateiNamen4.Length+1]; Array.Copy(dateiNamen4, tmp, dateiNamen4.Length); tmp[tmp.Length - 1] = "Drei"; dateiNamen4 = tmp; MessageBox.Show("Array: " + String.Join(", ", dateiNamen4));
Die List<T> Klassen (u.a.) machen das intern letztlich auch (ähnlich) in bestimmten heuristisch vorteilhaften Schritten (zuerst 4, dann 'items.Lenght*2') der Vor-Allokierung.
Man kann eine ArrayList o.ä. auch am Ende wieder in ein Array umwandlen - etwa:
string[] array3 = new string[dateiNamen3.Count]; dateiNamen3.CopyTo(array3);
oder bei List<string> :string[] array2 = dateiNamen.ToArray<string>();
- Als Antwort markiert Purclot Mittwoch, 13. April 2011 07:42
-
Hallo Purclot,
> Die eigentliche Frage: da ich die ArrayLänge nicht kenne, wie lege ich die zur Laufzeit fest??
Wenn ich Dich richtig verstehe, möchtest Du ein zweidimensionales Array vom Typ string[,] an eine Kopierfunktion übergeben, z.B. void Copy(string[,]). Vorher möchtest du dieses zweidimensionale Array nach Bedarf dimensionieren können.
Lass uns zunächst einmal klären was Neudimensionierung in C# bedeutet: Da Arrayelemente auf dem Heap kompakt (am Stück) und ohne Lücken gespeichert werden, bedeutet Neudimensionieren dass zunächst ein neues Array erstellt werden muss und anschließend alle Elemente des alten Arrays in das neue Array kopiert werden müssen. Da diese Operation an sich schon O(n) ist und Dateisysteme dazu neigen, in manchen Verzeichnissen sehr viele, in andere hingegen sehr wenige Dateien zu enthalten, würde das fortwährende Neudimensionieren des Arrays ernsthafte Performanzeinbußen mit sich bringen. Nun könnte man sicherlich nicht verwaltete Funktionen wie etwa CopyMemory einsetzen, um das Ganze ein klein bischen zu beschleunigen, aber richtig Abhilfe schaftt auch das nicht.Ich stelle mir vor, dass Du Arrays verwenden möchtest, weil sie in der Alloziierung und in der Zugriffszeit so performant sind. Wenn Du sie aber mit jedem neuen Verzeichnis das Du verarbeitest erweitern mußt, mußt Du auch einen Performanzpreis zahlen. Freilich könnte man die Erweiterung in chunks (blockweise) vornehmen, oder gar einen adaptiven Algorithmus benutzen, der die neu zu allozierende Blockgröße anhand der vorhergehenden "Erfahrungen" bestimmt.
Aber steht das alles nicht schon zur Verfügung? List<T> speichert seine Elemente intern in einem T[]-Array. Sobald man die Methode List<T>.Add() aufruft, wird intern die Methode EnsureCapacity(int min) aufgerufen, die List<T>.Capacity auf T[].Length * 2 setzt. Ein eher grober adaptiver Algorithmus, aber wenn die Speichergröße kein Problem darstellt, sehr pragmatisch. Und wenn man die Größe des internen Arrays auf die tatsächliche Anzahl der Elemente reduzieren will, kann man noch immer List<T>.TrimExcess() aufrufen.Aber Du wolltest nicht nur die Quelldateien zu einer Auflistung hinzufügen, sondern auch gleich die Zieldateien. Du könntest als T in List<T> entweder eine Klasse/Struktur verwenden, oder Du nimmst gleich ein Dictionary<string, string> oder ein StringDictionary, die ähnlich wie List<T> funktionieren, Dir aber ermöglichen, Listenelemente als Paar hinzuzufügen. Wenn Du C# 4.0 verwendest, könntest Du auch ein Tuple dazu einsetzen.
So könnte das Ganze - skizziert - aussehen:
using System; using System.Collections.Generic; using System.IO; namespace WindowsFormsApplication33 { static class Program { [STAThread] static void Main() { string originalFile = @"C:\Quelldatei.txt"; string targetFile = @"C:\Zieldatei.txt"; Dictionary<string, string> filesToCopy = new Dictionary<string, string>(); filesToCopy.Add(originalFile, targetFile); Copy(filesToCopy); } private static void Copy(Dictionary<string, string> filesToCopy) { foreach (KeyValuePair<string, string> filePair in filesToCopy) { File.Copy(filePair.Key, filePair.Value); } } } }
Gruß
Marcel- Als Antwort markiert Purclot Mittwoch, 13. April 2011 07:42
-
Hallo Stefan, Frank, Marcel,
vielen Dank für Eure Antworten-sorry, ich melde mich erst jetzt (Erkältung...).
Der wichtigste Hintergrund für mich war, ein ProgressBar während des Kopiervorgangs zu implementieren. Daher vor der Implementierung mußte ich die Anzahl der zu kopierenden Dateien wissen (pBar1. Lenght=Anzahl..).
Ich habe das Problem nach Euren Vorschlägen gelöst und zwar so:private ArrayList myAl = new ArrayList(); private bool KopierenMitProgressBar() { bool result = true; try { Object[] toCopy= myAl.ToArray(); pBar1.Visible = true; pBar1.Minimum = 1; pBar1.Maximum = toCopy.Length; pBar1.Value = 1; pBar1.Step = 1; for (int x = 0; x < toCopy.Length; x++) { string input = toCopy[x].ToString().Substring(0, toCopy[x].ToString().IndexOf(",")); string target = toCopy[x].ToString().Substring(toCopy[x].ToString().IndexOf(",") + 1); File.Copy(input, target, true); pBar1.PerformStep(); } } catch (Exception ex) { MessageBox.Show("Fehler beim Kopieren der Installdateien." + ex.Message, "Fehler beim Kopieren.", MessageBoxButtons.OK, MessageBoxIcon.Error); result = false; } return result; } //zu kopierenden Daten sammeln und in der Variable myAl speichern... public bool LWKopieren(ref ArrayList myAl, ref string dirCopy, string versionnr, string servicepack, string dirCopyTarget)public { ....... dirCopySource = "Verzeichnis1"; myAl = CopyDirectoryWithIncludedFiles (dirCopySource, dirCopyTarget); dirCopySource = "Verzeichnis2"; myAl = CopyDirectoryWithIncludedFiles(dirCopySource, dirCopyTarget); } //und die Methode, die rekursiv alle Verzeichnisse/Dateien "abklappert".. public ArrayList CopyDirectoryWithIncludedFiles(string dirCopySource, string dirCopyTarget) { if (dirCopySource != String.Empty) { try { // alle Unterverzeichnisse des aktuellen Verzeichnisses ermitteln string[] subDirectories = Directory.GetDirectories(dirCopySource); // Zielpfad erzeugen StringBuilder newTargetPath = new StringBuilder(); { newTargetPath.Append(dirCopyTarget); newTargetPath.Append(dirCopySource.Substring(dirCopySource.LastIndexOf(@"\"))); } // wenn aktueller Ordner nicht existiert -> ersstellen if (!Directory.Exists(newTargetPath.ToString())) Directory.CreateDirectory(newTargetPath.ToString()); // Unterverzeichnise durchlaufen und Funktion mit dazu gehörigen Zielpfad erneut aufrufen (Rekursion) foreach (string subDirectory in subDirectories) { string newDirectoryPath = subDirectory; // wenn ''/'' an letzter Stelle dann entfernen if (newDirectoryPath.LastIndexOf(@"\") == (newDirectoryPath.Length - 1)) newDirectoryPath = newDirectoryPath.Substring(0, newDirectoryPath.Length - 1); // rekursiever Aufruf CopyDirectoryWithIncludedFiles(newDirectoryPath, newTargetPath.ToString()); } // alle enthaltenden Dateien des aktuellen Verzeichnisses ermitteln string[] fileNames = Directory.GetFiles(dirCopySource); foreach (string fileSource in fileNames) { // Zielpfad + Dateiname StringBuilder fileTarget = new StringBuilder(); { fileTarget.Append(newTargetPath); fileTarget.Append(fileSource.Substring(fileSource.LastIndexOf(@"\"))); } // Datei-Namen mit Pfad ins ArrayList kopieren myAl.Add(fileSource + ", " + fileTarget.ToString()); } } catch (Exception ex) { MessageBox.Show("Fehler beim Kopieren der Installationsdateien: " + ex.Message); } } else { MessageBox.Show("Falsche Eingabe für die Installation."); } return myAl; }