У меня есть объект StreamReader, который я инициализировал потоком, теперь я хочу сохранить этот поток на диск (поток может быть .gif, .jpg или .pdf).
Существующий код:
StreamReader sr = new StreamReader(myOtherObject.InputStream);
У меня также есть тип кодировки, который мне понадобится, если я сохраню его на SQL Server, правильно?





Вы не должен используете StreamReader для двоичных файлов (например, gif или jpgs). StreamReader предназначен для данных текст. Вы почти потеряете безусловно, если будете использовать его для произвольных двоичных данных. (Если вы используете Encoding.GetEncoding (28591), вероятно, все будет в порядке, но какой в этом смысл?)
Зачем вообще нужно использовать StreamReader? Почему бы просто не сохранить двоичные данные в виде и записать их обратно на диск (или в SQL) как двоичные данные?
Обновлено: Поскольку это похоже на то, что люди хотят видеть ... если вы делать просто хотите скопировать один поток в другой (например, в файл), используйте что-то вроде этого:
/// <summary>
/// Copies the contents of input to output. Doesn't close either stream.
/// </summary>
public static void CopyStream(Stream input, Stream output)
{
byte[] buffer = new byte[8 * 1024];
int len;
while ( (len = input.Read(buffer, 0, buffer.Length)) > 0)
{
output.Write(buffer, 0, len);
}
}
Чтобы использовать его для сброса потока в файл, например:
using (Stream file = File.Create(filename))
{
CopyStream(input, file);
}
Обратите внимание, что Stream.CopyTo был введен в .NET 4 и служит в основном той же цели.
Это похоже на такой частый случай, я удивлен, что его нет в .NET. Я вижу, как люди создают байтовые массивы размером с весь файл, что может вызвать проблемы с большими файлами.
@Tilendor: он присутствует как метод расширения в .NET 4. (CopyTo)
Я не думаю, что это метод расширения, но он новый в классе Stream.
@Kugel: Вы правы, извините. я имел его как метод расширения в служебной библиотеке, но теперь, когда он находится в самом Stream, мой метод расширения не вызывается.
Почему вы устанавливаете размер буфера 8192? Спасибо
@ Флориан: Это достаточно произвольно - достаточно маленькое значение, чтобы не занимать слишком много памяти, и достаточно большое, чтобы передавать разумный кусок за раз. Было бы хорошо быть 16 КБ, может быть, 32 КБ - я просто был бы осторожен, чтобы не попасть в кучу больших объектов.
@JonSkeet: как это можно использовать, когда вводом является HttpWebResponse (файл, возвращаемый из приложения веб-API)?
В WPF я не мог выполнить File.Create, но мог сделать оператор using, похожий на using (FileStream fs = new FileStream(destPath, FileMode.CreateNew, FileAccess.Write), и добавить в него CopyStream(stream, fs);.
@vapcguy: вы сможете без проблем использовать File.Create из WPF.
@JonSkeet Вы правы. Просто попробовал снова, сегодня, и теперь красная волнистая линия на Create исчезает, поэтому я не мог сказать вам, в чем была моя ошибка, но это не давало мне интеллекта, когда я затем вводил первую скобку после нее, прежде , ни показания мне при наведении курсора на File. Думаю, это была временная ошибка VS.
@JonSkeet Нет ... это было. :) Теперь он работает, и ничего не делал, кроме открытия / закрытия / повторного открытия файла в VS, поэтому не могу вам сказать ...
Отличный ответ. В большинстве других ответов используется CopyTo, доступный не во всех версиях .Net.
@JonSkeet Я вижу, что размер буфера по умолчанию для Stream.CopyTo - это _DefaultCopyBufferSize = 81920;, поэтому, вероятно, можно с уверенностью предположить, что его можно использовать, как new byte[81920];, везде и даже в Java, например new byte[65536]; (также кратно 4096)?
@Pierre: Множество 4K, вероятно, не имеют большого значения, хотя это будет зависеть от потока. (И Java не имеет ограничения в 64 КБ для байтовых массивов ... Я не уверен, почему вы предлагаете здесь разницу.) Но, как всегда, вам нужно сбалансировать выделение памяти с меньшим количеством копий. Учитывая, что это не долгоживущий буфер, это, вероятно, не имеет большого значения, но может зависеть от вашего приложения.
Этот код у меня не сработал. Stream.CopyTo - сработало.
@Yaron: Есть еще какая-то информация, кроме «у меня не работает»? В каком способ не заработало?
@JonSkeet Я загрузил файл (документ Word) из Microsoft OneDrive через Graph API. Когда я сохранил файл локально с помощью описанного выше метода CopyStream (..) и попытался открыть его, я получил сообщение об ошибке «Word обнаружил нечитаемое содержимое». При переходе на Stream.CopyTo (..) - файл открылся правильно.
@Yaron: Есть много диагностической информации, которую я хотел бы спросить об этом ... Я предлагаю вам задать новый вопрос с минимальный воспроизводимый пример.
@JonSkeet Stream.CopyTo тоже не работал. у меня работает byte [] ReadAsBytes (Stream input) {using (var ms = new MemoryStream ()) {input.CopyTo (ms); return ms.ToArray (); }} Я правда еще не нашел времени, чтобы это проанализировать.
@Yaron: Опять же, я предлагаю вам задать новый вопрос с минимальный воспроизводимый пример, а не добавлять по каплям все еще недостаточные кусочки информации в комментарии к ответу, которому более 12 лет.
Почему бы не использовать объект FileStream?
public void SaveStreamToFile(string fileFullPath, Stream stream)
{
if (stream.Length == 0) return;
// Create a FileStream object to write a stream to a file
using (FileStream fileStream = System.IO.File.Create(fileFullPath, (int)stream.Length))
{
// Fill the bytes[] array with the stream data
byte[] bytesInStream = new byte[stream.Length];
stream.Read(bytesInStream, 0, (int)bytesInStream.Length);
// Use FileStream object to write to the specified file
fileStream.Write(bytesInStream, 0, bytesInStream.Length);
}
}
что, если входной поток имеет длину 1 ГБ - этот код попытается выделить буфер размером 1 ГБ :)
Это не работает с потоком ответа, потому что его длина неизвестна.
Хотя это правда, что у вас должна быть память для byte[], я думаю, что будет редко, когда вы будете транслировать большой двоичный объект размером 1 ГБ + в файл ... если у вас нет сайта, который хранит торренты DVD ... К тому же, в наши дни на большинстве компьютеров доступно как минимум 2 ГБ ОЗУ ... Предостережение справедливо, но я думаю, что это тот случай, когда он, вероятно, «достаточно хорош» для большинства рабочих мест.
Веб-серверы вообще не потерпят такого случая, если только на веб-сайте не будет одновременно активен только один пользователь.
Как подчеркнул Tilendor в ответе Джона Скита, потоки имеют метод CopyTo, начиная с .NET 4.
var fileStream = File.Create("C:\\Path\\To\\File");
myOtherObject.InputStream.Seek(0, SeekOrigin.Begin);
myOtherObject.InputStream.CopyTo(fileStream);
fileStream.Close();
Или с синтаксисом using:
using (var fileStream = File.Create("C:\\Path\\To\\File"))
{
myOtherObject.InputStream.Seek(0, SeekOrigin.Begin);
myOtherObject.InputStream.CopyTo(fileStream);
}
Обратите внимание, что вам нужно вызвать myOtherObject.InputStream.Seek(0, SeekOrigin.Begin), если вы еще не в начале, иначе вы не будете копировать весь поток.
Если этот входной поток получен из HTTP-соединения, он будет буферизироваться и загружаться, а затем записывать все байты из источника ?????
Я создал программу просмотра PDF, в которой я использую поток. Как только я привяжу поток и сохраню файл PDF, используя тот же поток, без использования «Seek (0, SeekOrigin.Begin)», я не смогу сохранить правильный документ. так что +1 за упоминание об этом "Seek (0, SeekOrigin.Begin)"
myOtherObject.InputStream.CopyTo (fileStream); эта строка выдает ошибку: доступ запрещен.
@sulhadin это просто означает, что у вас нет разрешения на запись на fileStream
//If you don't have .Net 4.0 :)
public void SaveStreamToFile(Stream stream, string filename)
{
using(Stream destination = File.Create(filename))
Write(stream, destination);
}
//Typically I implement this Write method as a Stream extension method.
//The framework handles buffering.
public void Write(Stream from, Stream to)
{
for(int a = from.ReadByte(); a != -1; a = from.ReadByte())
to.WriteByte( (byte) a );
}
/*
Note, StreamReader is an IEnumerable<Char> while Stream is an IEnumbable<byte>.
The distinction is significant such as in multiple byte character encodings
like Unicode used in .Net where Char is one or more bytes (byte[n]). Also, the
resulting translation from IEnumerable<byte> to IEnumerable<Char> can loose bytes
or insert them (for example, "\n" vs. "\r\n") depending on the StreamReader instance
CurrentEncoding.
*/
Побайтовое копирование потока (с использованием ReadByte / WriteByte) будет намного медленнее, чем копирование буфера за буфером (с использованием Read (byte [], int, int) / Write (byte [], int, int)).
public void testdownload(stream input)
{
byte[] buffer = new byte[16345];
using (FileStream fs = new FileStream(this.FullLocalFilePath,
FileMode.Create, FileAccess.Write, FileShare.None))
{
int read;
while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
{
fs.Write(buffer, 0, read);
}
}
}
Подача буферизованного входного потока прямо на FileStream - хорошо!
public void CopyStream(Stream stream, string destPath)
{
using (var fileStream = new FileStream(destPath, FileMode.Create, FileAccess.Write))
{
stream.CopyTo(fileStream);
}
}
Вероятно, вам не стоит помещать объект stream в скобку using(){}. Ваш метод не создавал поток, поэтому он не должен его удалять.
Вместо этого вам нужно использовать FileStream, иначе он будет открыт до тех пор, пока не будет собран сборщиком мусора.
Все прошло нормально, но я получил результат в 0 КБ. Вместо этого мне пришлось сделать это для правильного вывода: File.WriteAllBytes(destinationFilePath, input.ToArray());. В моем случае input - это MemoryStream, поступающий из ZipArchive.
private void SaveFileStream(String path, Stream stream)
{
var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write);
stream.CopyTo(fileStream);
fileStream.Dispose();
}
Все прошло нормально, но я получил результат в 0 КБ. Вместо этого мне пришлось сделать это для правильного вывода: File.WriteAllBytes(destinationFilePath, input.ToArray());. В моем случае input - это MemoryStream, поступающий из ZipArchive.
Это помогло мне понять, что я делаю не так. Однако не забудьте перейти в начало потока: stream.Seek(0, SeekOrigin.Begin);
Другой вариант - передать поток на byte[] и использовать File.WriteAllBytes. Это должно делать:
using (var stream = new MemoryStream())
{
input.CopyTo(stream);
File.WriteAllBytes(file, stream.ToArray());
}
Оборачивание его в метод расширения дает ему лучшее именование:
public void WriteTo(this Stream input, string file)
{
//your fav write method:
using (var stream = File.Create(file))
{
input.CopyTo(stream);
}
//or
using (var stream = new MemoryStream())
{
input.CopyTo(stream);
File.WriteAllBytes(file, stream.ToArray());
}
//whatever that fits.
}
Если ввод слишком велик, вы получите исключение нехватки памяти. Вариант копирования содержимого из входного потока в файловый поток намного лучше
Я не получаю все ответы, используя CopyTo, где, возможно, системы, использующие приложение, могли не быть обновлены до .NET 4.0+. Я знаю, что некоторые хотели бы заставить людей обновиться, но совместимость тоже хороша.
Другое дело, я вообще не использую поток для копирования из другого потока. Почему бы просто не сделать:
byte[] bytes = myOtherObject.InputStream.ToArray();
Получив байты, вы можете легко записать их в файл:
public static void WriteFile(string fileName, byte[] bytes)
{
string path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
if (!path.EndsWith(@"\")) path += @"\";
if (File.Exists(Path.Combine(path, fileName)))
File.Delete(Path.Combine(path, fileName));
using (FileStream fs = new FileStream(Path.Combine(path, fileName), FileMode.CreateNew, FileAccess.Write))
{
fs.Write(bytes, 0, (int)bytes.Length);
//fs.Close();
}
}
Этот код работает так же, как я тестировал его с файлом .jpg, хотя я признаю, что использовал его только с небольшими файлами (менее 1 МБ). Один поток, без копирования между потоками, без кодирования, просто напишите байты! Нет необходимости усложнять вещи с StreamReader, если у вас уже есть поток, который вы можете преобразовать в bytes напрямую с .ToArray()!
Единственные потенциальные недостатки, которые я вижу в этом способе, - это наличие у вас большого файла, использование его в виде потока и использование .CopyTo() или его эквивалента позволяет FileStream передавать его в потоковом режиме вместо использования массива байтов и чтения байтов один за другим. В результате это может быть медленнее. Но он не должен задыхаться, поскольку метод .Write() в FileStream обрабатывает запись байтов и делает это только по одному байту за раз, поэтому он не забивает память, за исключением у вас должно быть достаточно памяти для хранения потока как объекта byte[]. В моей ситуации, когда я использовал это, получая OracleBlob, мне пришлось перейти к byte[], он был достаточно маленьким, и, кроме того, мне все равно не было потоковой передачи, поэтому я просто отправил свои байты в свою функцию, описанную выше.
Другой вариант, используя поток, - это использовать его с функцией CopyStream Джона Скита, которая была в другом сообщении - здесь просто используется FileStream для получения входного потока и создания файла напрямую из него. Он не использует File.Create, как он (что сначала показалось мне проблематичным, но позже выяснилось, что это, скорее всего, просто ошибка VS ...).
/// <summary>
/// Copies the contents of input to output. Doesn't close either stream.
/// </summary>
public static void CopyStream(Stream input, Stream output)
{
byte[] buffer = new byte[8 * 1024];
int len;
while ( (len = input.Read(buffer, 0, buffer.Length)) > 0)
{
output.Write(buffer, 0, len);
}
}
public static void WriteFile(string fileName, Stream inputStream)
{
string path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
if (!path.EndsWith(@"\")) path += @"\";
if (File.Exists(Path.Combine(path, fileName)))
File.Delete(Path.Combine(path, fileName));
using (FileStream fs = new FileStream(Path.Combine(path, fileName), FileMode.CreateNew, FileAccess.Write)
{
CopyStream(inputStream, fs);
}
inputStream.Close();
inputStream.Flush();
}
Нет необходимости звонить в Close из-за using()
@ Alex78191 Если вы говорите о inputStream.Close(), посмотрите еще раз - inputStream отправляется как переменная. using находится в выходном потоке path+filename. Если вы говорили о fs.Close() в середине using, извините, вы были правы, и я удалил это.
Должен смыться перед закрытием. Хотя близко тоже следует промыть.
@Andrew Я думаю, что именно поэтому я сделал их в том порядке, в котором я делал - потому что я не думаю, что вы можете выполнить .Close() в потоке, который был сброшен, потому что .Flush() тоже закрывает его, и я хотел выполнить обе команды.
Вот пример правильного использования и реализации idisposable:
static void WriteToFile(string sourceFile, string destinationfile, bool append = true, int bufferSize = 4096)
{
using (var sourceFileStream = new FileStream(sourceFile, FileMode.OpenOrCreate))
{
using (var destinationFileStream = new FileStream(destinationfile, FileMode.OpenOrCreate))
{
while (sourceFileStream.Position < sourceFileStream.Length)
{
destinationFileStream.WriteByte((byte)sourceFileStream.ReadByte());
}
}
}
}
... и еще это
public static void WriteToFile(Stream stream, string destinationFile, int bufferSize = 4096, FileMode mode = FileMode.OpenOrCreate, FileAccess access = FileAccess.ReadWrite, FileShare share = FileShare.ReadWrite)
{
using (var destinationFileStream = new FileStream(destinationFile, mode, access, share))
{
while (stream.Position < stream.Length)
{
destinationFileStream.WriteByte((byte)stream.ReadByte());
}
}
}
Ключевым моментом является понимание правильного использования using (которое должно быть реализовано при создании экземпляра объекта, реализующего idisposable, как показано выше), и хорошее представление о том, как свойства работают для потоков. Позиция - это буквально индекс в потоке (который начинается с 0), за которым следует каждый байт, считанный с использованием метода readbyte. В этом случае я, по сути, использую его вместо переменной цикла for и просто позволяю ему пройти весь путь до длины, которая БУКВАЛЬНО является концом всего потока (в байтах). Игнорируйте в байтах, потому что это практически то же самое, и у вас будет что-то простое и элегантное, подобное этому, которое решает все чисто.
Также имейте в виду, что метод ReadByte просто преобразует байт в int в процессе и может быть просто преобразован обратно.
Я собираюсь добавить еще одну реализацию, которую я недавно написал, чтобы создать своего рода динамический буфер, чтобы обеспечить последовательную запись данных, чтобы предотвратить массовую перегрузку.
private void StreamBuffer(Stream stream, int buffer)
{
using (var memoryStream = new MemoryStream())
{
stream.CopyTo(memoryStream);
var memoryBuffer = memoryStream.GetBuffer();
for (int i = 0; i < memoryBuffer.Length;)
{
var networkBuffer = new byte[buffer];
for (int j = 0; j < networkBuffer.Length && i < memoryBuffer.Length; j++)
{
networkBuffer[j] = memoryBuffer[i];
i++;
}
//Assuming destination file
destinationFileStream.Write(networkBuffer, 0, networkBuffer.Length);
}
}
}
Объяснение довольно простое: мы знаем, что нам нужно иметь в виду весь набор данных, которые мы хотим записать, а также то, что мы хотим записать только определенные суммы, поэтому нам нужен первый цикл с последним параметром пустым (так же, как и while ). Затем мы инициализируем буфер массива байтов, для которого установлен размер переданного, и во втором цикле мы сравниваем j с размером буфера и размером исходного, и если он больше размера оригинала байтовый массив, завершите запуск.
Что такое myOtherObject?