




Начиная с .NET 4.5, есть Stream.CopyToAsync метод
input.CopyToAsync(output);
Это вернет Task, который можно продолжить после завершения, например:
await input.CopyToAsync(output)
// Code from here on will be run in a continuation.
Обратите внимание, что в зависимости от того, где выполняется вызов CopyToAsync, следующий код может продолжаться или не продолжаться в том же потоке, который его вызвал.
SynchronizationContext, который был захвачен при вызове await, будет определять, в каком потоке будет выполняться продолжение.
Кроме того, этот вызов (и это деталь реализации, которая может быть изменена) по-прежнему выполняет последовательность операций чтения и записи (он просто не тратит впустую потоки, блокирующие завершение ввода-вывода).
Начиная с .NET 4.0, есть Stream.CopyTo метод
input.CopyTo(output);
Для .NET 3.5 и ранее
В фреймворк нет ничего, что могло бы помочь в этом; вам нужно скопировать контент вручную, например:
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);
}
}
Примечание 1: этот метод позволит вам сообщать о прогрессе (x байт прочитано на данный момент ...)
Примечание 2: Почему следует использовать фиксированный размер буфера, а не input.Length? Потому что эта длина может быть недоступна! Из документы:
If a class derived from Stream does not support seeking, calls to Length, SetLength, Position, and Seek throw a NotSupportedException.
Учтите, что это не самый быстрый способ сделать это. В предоставленном фрагменте кода вам нужно дождаться завершения записи, прежде чем будет прочитан новый блок. При выполнении асинхронного чтения и записи это ожидание исчезнет. В некоторых случаях это сделает копию вдвое быстрее. Однако это значительно усложнит код, поэтому, если скорость не является проблемой, оставьте его простым и используйте этот простой цикл. В этом вопросе на StackOverflow есть код, который иллюстрирует асинхронное чтение / запись: stackoverflow.com/questions/1540658/… С уважением, Себастьян
32 768 - рекомендуемый размер байта? Раньше я видел меньшие суммы, например 4096.
@Josh 32,768 - это копия размером 32 КБ. 4096 использует блоки размером 4 КБ. Это будет немного медленнее, но потребует меньше памяти.
FWIW: input.CopyTo (output) делает то же самое, что и этот метод CopyStream, за исключением того, что он использует блоки размером 4K и выполняет некоторую защитную проверку ввода вокруг состояния потока. О, и, как предположил @Jon, они использовали идиому while ((bytes = this.Read (...))! = 0).
Было бы целесообразно проверить наличие input.CanRead и output.CanWrite?
FWIW, в своем тестировании я обнаружил, что 4096 на самом деле быстрее 32 КБ. Что-то связано с тем, как CLR выделяет фрагменты определенного размера. Из-за этого реализация Stream.CopyTo .NET, по-видимому, использует 4096.
Если у вас проблемы, как у меня. добавьте это перед строкой copyTo (). sourceStream.Seek (0, SeekOrigin.Begin);
Если вы хотите узнать, как реализован CopyToAsync, или внести изменения, как это сделал я (мне нужно было указать максимальное количество байтов для копирования), тогда он доступен как CopyStreamToStreamAsync в разделе «Примеры параллельного программирования с .NET Framework» code.msdn.microsoft.com/ParExtSamples
@ThiagoPXP У меня также были проблемы, когда input был MemoryStream - не заметил вашего комментария, но смог исправить с помощью .WriteTo, а не .CopyTo. Я думаю, что с вашим предложением вы также должны проверить .CanSeek, прежде чем пытаться искать.
FIY, оптимальный размер буфера - 81920 байта, а не 32768
@Jeff последняя ссылка показывает, что он фактически использует буфер 81920 байт.
Разве await input.CopyToAsync(output) не должен быть await input.CopyToAsync(output).ContinueWith(t => /*do something with output*/);? Я думаю, что это имеет больше смысла, потому что, вероятно, разработчик хочет работать с выходным потоком.
К сожалению, действительно простого решения не существует. Вы можете попробовать что-то вроде этого:
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();
Но проблема в том, что другая реализация класса Stream может вести себя по-разному, если читать нечего. Поток, читающий файл с локального жесткого диска, вероятно, будет заблокирован до тех пор, пока операция чтения не прочитает достаточно данных с диска, чтобы заполнить буфер, и вернет меньше данных, только если он достигнет конца файла. С другой стороны, поток, считываемый из сети, может возвращать меньше данных, даже если для получения осталось больше данных.
Всегда проверяйте документацию конкретного класса потока, который вы используете, прежде чем использовать универсальное решение.
Общее решение здесь будет работать - ответ Ника хороший. Размер буфера, конечно, произвольный, но 32 КБ звучит разумно. Я думаю, что решение Ника - правильное решение - не закрывать потоки - оставьте это владельцу.
Основные вопросы, которые отличают реализации CopyStream:
Ответы на эти вопросы приводят к совершенно разным реализациям CopyStream и зависят от того, какие потоки у вас есть и что вы пытаетесь оптимизировать. «Лучшая» реализация даже должна знать, на каком конкретном оборудовании потоки читают и записывают.
... или лучшая реализация могла бы иметь перегрузки, чтобы вы могли указать размер буфера, размер записи и разрешены ли потоки?
Возможно, есть способ сделать это более эффективно, в зависимости от того, с каким потоком вы работаете. Если вы можете преобразовать один или оба своих потока в MemoryStream, вы можете использовать метод GetBuffer для работы непосредственно с массивом байтов, представляющим ваши данные. Это позволяет вам использовать такие методы, как Array.CopyTo, которые абстрагируют все проблемы, поднятые fryguybob. Вы можете просто доверять .NET, чтобы знать оптимальный способ копирования данных.
если вы хотите, чтобы процедура скопировала поток в другой, тот, который опубликовал ник, в порядке, но в нем отсутствует сброс позиции, он должен быть
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
}
но если он находится во время выполнения, не используя процедуру, которую вы используете, используя поток памяти
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
Вы не должны изменять положение входного потока, потому что не все потоки допускают произвольный доступ. В сетевом потоке, например, вы не можете изменить позицию, только чтение и / или запись.
На самом деле существует менее жесткий способ копирования потока. Однако обратите внимание, что это означает, что вы можете хранить весь файл в памяти. Не пытайтесь использовать это, если вы работаете с файлами, размер которых превышает сотни мегабайт, без осторожности.
public static void CopySmallTextStream(Stream input, Stream output)
{
using (StreamReader reader = new StreamReader(input))
using (StreamWriter writer = new StreamWriter(output))
{
writer.Write(reader.ReadToEnd());
}
}
ПРИМЕЧАНИЕ. Также могут возникнуть проблемы с двоичными данными и кодировками символов.
Конструктор по умолчанию для StreamWriter создает поток UTF8 без спецификации (msdn.microsoft.com/en-us/library/fysy0a4b.aspx), поэтому нет опасности возникновения проблем с кодированием. Двоичные данные почти наверняка не следует копировать таким образом.
Можно легко возразить, что загрузка «всего файла в память» вряд ли считается «менее жесткой».
я получаю исключение outmemory из-за этого
Это поток нет для потоковой передачи. reader.ReadToEnd() помещает все в оперативную память
Я переименовал метод с CopyStream () в CopySmallTextStream (). Возможно, это поможет сделать недостатки этого решения более очевидными в последующих базах кода.
Я использую следующие методы расширения. Они оптимизировали перегрузки для случаев, когда одним потоком является 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);
}
MemoryStream имеет .WriteTo(outstream);
а .NET 4.0 имеет .CopyTo в обычном потоковом объекте.
.NET 4.0:
instream.CopyTo(outstream);
Я не вижу много примеров в Интернете, использующих эти методы. Это потому, что они довольно новые, или есть какие-то ограничения?
Это потому, что они впервые появились в .NET 4.0. Stream.CopyTo () в основном делает то же самое, что и утвержденный ответ, с некоторыми дополнительными проверками работоспособности. Размер буфера по умолчанию - 4096, но есть также перегрузка, чтобы указать больший размер.
После копирования поток нужно перемотать: instream.Position = 0;
В дополнение к перемотке входного потока я также обнаружил необходимость перемотать выходной поток: outstream.Position = 0;
Поскольку ни один из ответов не охватывал асинхронный способ копирования из одного потока в другой, вот шаблон, который я успешно использовал в приложении переадресации портов для копирования данных из одного сетевого потока в другой. В нем отсутствует обработка исключений, чтобы подчеркнуть шаблон.
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);
}
Конечно, если второе чтение завершится до первой записи, тогда вы запишете содержимое bufferForWrite из первого чтения, прежде чем оно будет записано.
.NET Framework 4 представляет новый метод CopyTo класса Stream пространства имен System.IO. Используя этот метод, мы можем скопировать один поток в другой поток другого класса.
Вот пример для этого.
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/>");
Для .NET 3.5 и до этого попробуйте:
MemoryStream1.WriteTo(MemoryStream2);
Это работает, только если вы имеете дело с MemoryStreams.
Легко и безопасно - создайте новый поток из первоисточника:
MemoryStream source = new MemoryStream(byteArray);
MemoryStream copy = new MemoryStream(byteArray);
Следующий код для решения проблемы скопируйте Stream в MemoryStream с помощью CopyTo
Stream stream = new MemoryStream();
// любая функция требует ввода потока. В моем случае, чтобы сохранить файл PDF как поток document.Save (поток);
MemoryStream newMs = (MemoryStream)stream;
byte[] getByte = newMs.ToArray();
// Примечание - разместите поток в блоке finally, а не в блоке using, так как он выдаст ошибку «Доступ запрещен, поскольку поток закрыт»
Добро пожаловать в Stack Overflow. Добавляя ответ на вопрос одиннадцатилетней давности с двенадцатью существующими ответами, включая принятый ответ, очень важно указать, к какому новому аспекту вопроса относится ваш ответ. Используйте форматирование кода для кода. Ответы лучше, если они включают объяснение того, как и почему они работают.
Может быть, еще более важно на этом этапе, как вы копируете содержимое "в потоковом режиме", что означает, что он копирует только исходный поток, поскольку что-то потребляет поток назначения ...?