Jaki jest najlepszy sposób na skopiowanie zawartości jednego strumienia do drugiego? Czy istnieje standardowa metoda użyteczności?
Jaki jest najlepszy sposób na skopiowanie zawartości jednego strumienia do drugiego? Czy istnieje standardowa metoda użyteczności?
Odpowiedzi:
Począwszy od .NET 4.5 istnieje Stream.CopyToAsync
metoda
input.CopyToAsync(output);
Zwróci wartość, Task
którą można kontynuować po zakończeniu, np .:
await input.CopyToAsync(output)
// Code from here on will be run in a continuation.
Zauważ, że w zależności od tego, gdzie CopyToAsync
jest wykonywane połączenie, następujący kod może, ale nie musi, kontynuować w tym samym wątku, który go wywołał.
To, SynchronizationContext
co zostało przechwycone podczas wywoływania await
, określi, w jakim wątku będzie kontynuowana kontynuacja.
Ponadto to wywołanie (i jest to szczegół implementacji podlegający zmianom) nadal sekwencyjnie odczytuje i zapisuje (nie marnuje nic blokowania wątków po zakończeniu operacji we / wy).
Począwszy od .NET 4.0 istnieje Stream.CopyTo
metoda
input.CopyTo(output);
Dla .NET 3.5 i wcześniejszych
Nie ma w tym nic upieczonego, aby pomóc w tym; musisz skopiować zawartość ręcznie, tak jak:
public static void CopyStream(Stream input, Stream output)
{
byte[] buffer = new byte[32768];
int read;
while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
{
output.Write (buffer, 0, read);
}
}
Uwaga 1: Ta metoda pozwoli Ci raportować postęp (dotychczas odczytane bajty x ...)
Uwaga 2: Dlaczego warto używać stałego rozmiaru bufora, a nie input.Length
? Ponieważ ta długość może być niedostępna! Z dokumentów :
Jeśli klasa wywodząca się ze strumienia nie obsługuje wyszukiwania, wywołania Length, SetLength, Position i Seek generują NotSupportedException.
81920
bajty, nie32768
MemoryStream ma .WriteTo (outstream);
a .NET 4.0 ma .CopyTo na normalnym obiekcie strumieniowym.
.NET 4.0:
instream.CopyTo(outstream);
Używam następujących metod rozszerzenia. Mają zoptymalizowane przeciążenia, gdy jeden strumień jest MemoryStream.
public static void CopyTo(this Stream src, Stream dest)
{
int size = (src.CanSeek) ? Math.Min((int)(src.Length - src.Position), 0x2000) : 0x2000;
byte[] buffer = new byte[size];
int n;
do
{
n = src.Read(buffer, 0, buffer.Length);
dest.Write(buffer, 0, n);
} while (n != 0);
}
public static void CopyTo(this MemoryStream src, Stream dest)
{
dest.Write(src.GetBuffer(), (int)src.Position, (int)(src.Length - src.Position));
}
public static void CopyTo(this Stream src, MemoryStream dest)
{
if (src.CanSeek)
{
int pos = (int)dest.Position;
int length = (int)(src.Length - src.Position) + pos;
dest.SetLength(length);
while(pos < length)
pos += src.Read(dest.GetBuffer(), pos, length - pos);
}
else
src.CopyTo((Stream)dest);
}
Podstawowe pytania odróżniające implementacje „CopyStream” to:
Odpowiedzi na te pytania prowadzą do bardzo różnych implementacji CopyStream i zależą od rodzaju posiadanych strumieni i tego, co próbujesz zoptymalizować. „Najlepsza” implementacja musiałaby nawet wiedzieć, na jakim konkretnym sprzęcie czytały i zapisywały strumienie.
W rzeczywistości istnieje mniej wymagający sposób wykonywania kopii strumieniowej. Pamiętaj jednak, że oznacza to, że możesz zapisać cały plik w pamięci. Nie próbuj tego używać, jeśli pracujesz z plikami, które przechodzą do setek megabajtów lub więcej, bez zachowania ostrożności.
public static void CopyStream(Stream input, Stream output)
{
using (StreamReader reader = new StreamReader(input))
using (StreamWriter writer = new StreamWriter(output))
{
writer.Write(reader.ReadToEnd());
}
}
UWAGA: Mogą również występować pewne problemy dotyczące danych binarnych i kodowania znaków.
reader.ReadToEnd()
umieszcza wszystko w pamięci RAM
.NET Framework 4 wprowadza nową metodę „CopyTo” w klasie strumieni przestrzeni nazw System.IO. Za pomocą tej metody możemy skopiować jeden strumień do innego strumienia innej klasy strumienia.
Oto przykład tego.
FileStream objFileStream = File.Open(Server.MapPath("TextFile.txt"), FileMode.Open);
Response.Write(string.Format("FileStream Content length: {0}", objFileStream.Length.ToString()));
MemoryStream objMemoryStream = new MemoryStream();
// Copy File Stream to Memory Stream using CopyTo method
objFileStream.CopyTo(objMemoryStream);
Response.Write("<br/><br/>");
Response.Write(string.Format("MemoryStream Content length: {0}", objMemoryStream.Length.ToString()));
Response.Write("<br/><br/>");
CopyToAsync()
jest zalecane.
Niestety nie ma naprawdę prostego rozwiązania. Możesz spróbować czegoś takiego:
Stream s1, s2;
byte[] buffer = new byte[4096];
int bytesRead = 0;
while (bytesRead = s1.Read(buffer, 0, buffer.Length) > 0) s2.Write(buffer, 0, bytesRead);
s1.Close(); s2.Close();
Ale problem z tym, że inna implementacja klasy Stream może zachowywać się inaczej, jeśli nie ma nic do czytania. Strumień odczytujący plik z lokalnego dysku twardego prawdopodobnie zablokuje się, dopóki operacja odczytu nie odczyta wystarczającej ilości danych z dysku, aby wypełnić bufor i zwróci mniej danych, jeśli dotrze do końca pliku. Z drugiej strony odczyt strumienia z sieci może zwrócić mniej danych, nawet jeśli pozostało więcej danych do odebrania.
Zawsze sprawdzaj dokumentację konkretnej klasy strumienia, której używasz przed użyciem ogólnego rozwiązania.
Może istnieć sposób, aby to zrobić bardziej efektywnie, w zależności od rodzaju strumienia, z którym pracujesz. Jeśli możesz przekonwertować jeden lub oba strumienie na MemoryStream, możesz użyć metody GetBuffer do bezpośredniej pracy z tablicą bajtów reprezentującą twoje dane. Pozwala to na użycie metod takich jak Array.CopyTo, które usuwają wszystkie problemy podniesione przez fryguybob. Możesz po prostu zaufać platformie .NET, która zna optymalny sposób kopiowania danych.
jeśli chcesz, aby procedura skopiowała strumień na inny, który Nick opublikował, jest w porządku, ale brakuje mu resetowania pozycji, powinno być
public static void CopyStream(Stream input, Stream output)
{
byte[] buffer = new byte[32768];
long TempPos = input.Position;
while (true)
{
int read = input.Read (buffer, 0, buffer.Length);
if (read <= 0)
return;
output.Write (buffer, 0, read);
}
input.Position = TempPos;// or you make Position = 0 to set it at the start
}
ale jeśli jest w środowisku wykonawczym i nie korzysta z procedury, należy użyć strumienia pamięci
Stream output = new MemoryStream();
byte[] buffer = new byte[32768]; // or you specify the size you want of your buffer
long TempPos = input.Position;
while (true)
{
int read = input.Read (buffer, 0, buffer.Length);
if (read <= 0)
return;
output.Write (buffer, 0, read);
}
input.Position = TempPos;// or you make Position = 0 to set it at the start
Ponieważ żadna z odpowiedzi nie obejmowała asynchronicznego sposobu kopiowania z jednego strumienia do drugiego, oto wzór, który z powodzeniem wykorzystałem w aplikacji przekierowującej porty do kopiowania danych z jednego strumienia sieciowego do drugiego. Brakuje obsługi wyjątków, aby podkreślić wzór.
const int BUFFER_SIZE = 4096;
static byte[] bufferForRead = new byte[BUFFER_SIZE];
static byte[] bufferForWrite = new byte[BUFFER_SIZE];
static Stream sourceStream = new MemoryStream();
static Stream destinationStream = new MemoryStream();
static void Main(string[] args)
{
// Initial read from source stream
sourceStream.BeginRead(bufferForRead, 0, BUFFER_SIZE, BeginReadCallback, null);
}
private static void BeginReadCallback(IAsyncResult asyncRes)
{
// Finish reading from source stream
int bytesRead = sourceStream.EndRead(asyncRes);
// Make a copy of the buffer as we'll start another read immediately
Array.Copy(bufferForRead, 0, bufferForWrite, 0, bytesRead);
// Write copied buffer to destination stream
destinationStream.BeginWrite(bufferForWrite, 0, bytesRead, BeginWriteCallback, null);
// Start the next read (looks like async recursion I guess)
sourceStream.BeginRead(bufferForRead, 0, BUFFER_SIZE, BeginReadCallback, null);
}
private static void BeginWriteCallback(IAsyncResult asyncRes)
{
// Finish writing to destination stream
destinationStream.EndWrite(asyncRes);
}
Łatwe i bezpieczne - utwórz nowy strumień z oryginalnego źródła:
MemoryStream source = new MemoryStream(byteArray);
MemoryStream copy = new MemoryStream(byteArray);