Compression level for binary serialized objects as file - c#

In my application I have a rather large object created from some XML files. The xml file sizes something like 30MB, and my binary serialized object from this xml file will be like 8~9MB. Funny thing is if I compress this binary file with e.g. WinRar, it will be just 1~2MB.
Is there a way to increase compression level of the object itself? Or should I use another level of compression by manually write code for zipping the file after saving or unzip before loading back into the program?
In case, this is the code I use to save my object as file:
public static bool SaveProject(Project proj, string pathAndName)
{
bool success = true;
proj.FileVersion = CurrentFileVersion;
try
{
IFormatter formatter = new BinaryFormatter();
Stream stream = new FileStream(pathAndName, FileMode.Create, FileAccess.Write, FileShare.None);
formatter.Serialize(stream, proj);
stream.Close();
}
catch (Exception e)
{
MessageBox.Show("Can not save project!" + Environment.NewLine + "Reason: ", "Error",
MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
success = false;
}
return success;
}
UPDATE
I tried to change my code by adding a GZIPSTREAM but it seems that it does not do anything! Or maybe my implementation is wrong?
public static bool SaveProject(Project proj, string pathAndName)
{
bool success = true;
proj.FileVersion = CurrentFileVersion;
try
{
IFormatter formatter = new BinaryFormatter();
var stream = new FileStream(pathAndName, FileMode.Create, FileAccess.Write, FileShare.None);
var gZipStream = new GZipStream(stream, CompressionMode.Compress);
formatter.Serialize(stream, proj);
stream.Close();
gZipStream.Close();
}
catch (Exception e)
{
MessageBox.Show("Can not save project!" + Environment.NewLine + "Reason: ", "Error",
MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
success = false;
}
return success;
}
public static Project LoadProject(string path)
{
IFormatter formatter = new BinaryFormatter();
Stream stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
var gZipStream = new GZipStream(stream, CompressionMode.Decompress);
var obj = (Project)formatter.Deserialize(gZipStream);
stream.Close();
gZipStream.Close();
if (obj.FileVersion != CurrentFileVersion)
{
throw new InvalidFileVersionException("File version belongs to an older version of the program.");
}
return obj;
}

Wrap your FileStream in a DeflateStream with CompressionMode.Compress - pass that to the serializer. Then to deserialize, wrap a FileStream in a DeflateStream with CompressionMode.Decompress.
Note that instead of calling Close explicitly, you should use a using statement, e.g.
using (FileStream fileStream = ...)
using (DeflateStream deflateStream = new DeflateStream(fileStream,
CompressionMode.Compress))
{
formatter.Serialize(deflateStream, proj);
}
You can use GZipStream in the same way - try both to see which tends to give you better compression (or better performance, if you care about that).
Note how this approach separates the serialization aspect from the compression aspect, composing the two while keeping good separation of concerns. The serialization code just writes to a stream without caring what happens to the data, and the compression code just compresses what it's given without caring what the data means.

Related

Writing a memory stream to a file

I have tried retrieving data in the json format as a string and writing it to a file and it worked great. Now I am trying to use MemoryStream to do the same thing but nothing gets written to a file - merely [{},{},{},{},{}] without any actual data.
My question is - how can I check if data indeed goes to memory stream correctly or if the problem occurs somewhere else. I do know that myList does contain data.
Here is my code:
MemoryStream ms = new MemoryStream();
DataContractJsonSerializer dcjs = new DataContractJsonSerializer(typeof(List<myClass>));
dcjs.WriteObject(ms, myList);
using (FileStream fs = new FileStream(Path.Combine(Application.StartupPath,"MyFile.json"), FileMode.OpenOrCreate))
{
ms.Position = 0;
ms.Read(ms.ToArray(), 0, (int)ms.Length);
fs.Write(ms.ToArray(), 0, ms.ToArray().Length);
ms.Close();
fs.Flush();
fs.Close();
}
There is a very handy method, Stream.CopyTo(Stream).
using (MemoryStream ms = new MemoryStream())
{
StreamWriter writer = new StreamWriter(ms);
writer.WriteLine("asdasdasasdfasdasd");
writer.Flush();
//You have to rewind the MemoryStream before copying
ms.Seek(0, SeekOrigin.Begin);
using (FileStream fs = new FileStream("output.txt", FileMode.OpenOrCreate))
{
ms.CopyTo(fs);
fs.Flush();
}
}
Also, you don't have to close fs since it's in a using statement and will be disposed at the end.
using (var memoryStream = new MemoryStream())
{
...
var fileName = $"FileName.xlsx";
string tempFilePath = Path.Combine(Path.GetTempPath() + fileName );
using (var fs = new FileStream(tempFilePath, FileMode.Create, FileAccess.Write))
{
memoryStream.WriteTo(fs);
}
}
//reset the position of the stream
ms.Position = 0;
//Then copy to filestream
ms.CopyTo(fileStream);
The issue is nothing to do with your file stream/ memory stream. The problem is that DataContractJsonSerializer is an OPT IN Serializer. You need to add [DataMemberAttribute] to all the properties that you need to serialize on myClass.
[DataContract]
public class myClass
{
[DataMember]
public string Foo { get; set; }
}
This line looks problematic:
ms.Read(ms.ToArray(), 0, (int)ms.Length);
You shouldn't need to read anything into the memory stream at this point, particularly when you're code is written to read ms into ms.
I'm pretty confident that simply removing this line will fix your problem.

Strange error: "The input stream is not a valid binary format"

I have the following code to serialize and deserialize data:
static public void Serialize(List<Access> accesos, Stream stream)
{
IFormatter formatter = new BinaryFormatter();
formatter.Serialize(stream, accesos);
}
static public List<Access> Deserialize(Stream stream)
{
try
{
IFormatter formatter = new BinaryFormatter();
List<Access> data = formatter.Deserialize(stream) as List<Access>;
return data;
}
catch
{
return null;
}
}
The problem is that when I serialize an List<> to a file, and immediately try to deserialize, the error
"The input stream is not a valid binary format"
is thrown in formatter.Deserialize(stream) line.
On serialization, the stream is being opened with:
Stream stream = File.Open(GetConfigurationFilePath(), FileMode.Create);
On deserializarion, the stream, is being opened with:
Stream stream = File.Open(GetConfigurationFilePath(), FileMode.Open);
What may be happening here? the binary format is not changed in any way.
EDIT: This is how I call both static methods:
using (Stream stream = File.Open(GetConfigurationFilePath(), FileMode.Create))
{
this.Accesos = frm.Accesos;
Serializer.Serialize(this.Accesos, stream);
stream.Close();
}
using (Stream stream = File.Open(GetConfigurationFilePath(), FileMode.Open))
{
this.Accesos = Serializer.Deserialize(stream);
stream.Close();
}
private string GetConfigurationFilePath()
{
string path = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
if (path.Last() != '\\')
path += '\\';
path += CONFIG_FILE;
return path;
}
when I serialize a List<Access> to a file, and immediately try to deserialize ...
The most likely problem here is that the program has not finished writing to the stream at the time you started deserializing file's content. The formatter did finish its work, but part of the data stays buffered in memory. This may happen because your code does not close the file stream, explicitly or by disposing the stream.
Adding using around your streams should fix the problem:
using (Stream stream = File.Open(GetConfigurationFilePath(), FileMode.Create)) {
... // Serialization code
}
using (Stream stream = File.Open(GetConfigurationFilePath(), FileMode.Open)) {
... // Deserialization code
}

Load File Not working - The magic number in GZip header is not correct

I am attempting to create a Save/Load class that has the option for saving & load files compressed files. Below is what I have so far. Stepping through it seems to work just fine, except that I get a "The magic number in GZip header is not correct" exception. I don't understand how this can be as I am checking to make sure that the number is there before I pass it over, and I have verified via an external program that it is a GZip file.
Any assistance in finding out where I went wrong would be appreciated. Constructive criticism of my code is always welcome - Thanks!
public static class SaveLoad
{
public static void Save(string fileName, object savefrom, bool compress)
{
FileStream stream = new FileStream(fileName, FileMode.Create);
BinaryFormatter formatter = new BinaryFormatter();
if (compress)
{
GZipStream compressor = new GZipStream(stream, CompressionMode.Compress);
formatter.Serialize(compressor, savefrom);
compressor.Close();
}
else { formatter.Serialize(stream, savefrom); }
stream.Close();
}
public static object Load(string fileName)
{
object loadedObject = null;
try
{
FileStream stream = new FileStream(fileName, FileMode.Open);
BinaryFormatter formatter = new BinaryFormatter();
if (stream.Length > 4)
{
byte[] data = new byte[4];
stream.Read(data, 0, 4);
if (BitConverter.ToUInt16(data, 0) == 0x8b1f) //GZIP_LEAD_BYTES == 0x8b1f
{
GZipStream decompressor = new GZipStream(stream, CompressionMode.Decompress);
loadedObject = formatter.Deserialize(decompressor); //Exception
decompressor.Close();
}
else { loadedObject = formatter.Deserialize(stream); }
}
stream.Close();
}
catch (Exception e)
{
Logger.StaticLog.AddEvent(new Logger.lEvent(null, Logger.lEvent.EventTypes.Warning, "Failed to load file: " + fileName, e)
{
SendingObject = "SaveLoad"
});
Logger.StaticLog.WriteLog();
throw;
}
return loadedObject;
}
}
It seems that you read the magic number before passing the stream to decompressor (which won't read the magic number then, because you've already read it).
Use stream.Seek(0,SeekOrigin.Begin) before you decompress.

BinaryWriter in loop causes "file is being used by another process" error even though it is closed and 'using'

Title sais it all really.
private bool addToBinary(byte[] msg, string filepath)
{
bool succ = false;
do
{
try
{
using (Stream fileStream = new FileStream(filepath, FileMode.Append, FileAccess.Write, FileShare.None))
{
using (BinaryWriter bw = new BinaryWriter(fileStream))
{
bw.Write(msg);
bw.Flush();
fileStream.Flush();
bw.Close();
}
}
succ = true;
}
catch (IOException ex) { Console.WriteLine("Write Exception (addToBinary) : " + ex.Message); }
catch (Exception ex) { Console.WriteLine("Some Exception occured (addToBinary) : " + ex.Message); return false; }
} while (!succ);
return true;
}
(bw.close also closes the underlying stream)
Using this in any loop causes an output such as;
A first chance exception of type 'System.IO.IOException' occurred in mscorlib.dll
Write Exception (addToBinary) : The process cannot access the file 'C:\test\test.png' because it is being used by another process.
The bigger the file gets, the more of these errors pop up. It does get through eventually but it significantly reduces file writing speed. It's the Stream fileStream = bit that causes the exception.
What did I do wrong?
Example usage;
do
{
serverStream = clientSocket.GetStream();
bytesRead = serverStream.Read(inStream, 0, buffSize); //How many bytes did we just read from the stream?
recstrbytes = new byte[bytesRead]; //Final byte array
Array.Copy(inStream, recstrbytes, bytesRead); //Copy from inStream to the final byte array
addToBinary(recstrbytes, #"C:\test\test.png"); //Append final byte array to binary
received += recstrbytes.Length; //Increment bytes received
}while (received < filesize);
You need to first check if you can access the file before using Stream to read the file.
You can have a look at this link :
Best way to handle errors when opening file
Have a look at the answers
Although I posted my answer
https://stackoverflow.com/a/9503939/448407 but you can look at the post marked as answer.
Only read the file contents if you can access the file and I think it will then work.
Some good advice style wise for those stacked using statements. When you start using more than one it is often neater to use the following style:
using (Stream fileStream = new FileStream(filepath, FileMode.Append, FileAccess.Write, FileShare.None))
using (BinaryWriter bw = new BinaryWriter(fileStream))
{
bw.Write(msg);
bw.Flush();
fileStream.Flush();
bw.Close();
}
I'm afraid I can't solve your question though, but I'm not sure how much of a good an idea it is to repeatedly try and write to the stream if it isn't successful the first time round.

C# close stream and delete file if something failed

I'm working with a file stream in C#. It's a storage cache, so if something goes bad writing the file (corrupted data, ...), I need to delete the file and rethrow the exception to report the problem. I'm thinking on how to implement it in the best way. My first attempt was:
Stream fileStream = null;
try
{
fileStream = new FileStream(GetStorageFile(),
FileMode.Create, FileAccess.Write, FileShare.Write);
//write the file ...
}
catch (Exception ex)
{
//Close the stream first
if (fileStream != null)
{
fileStream.Close();
}
//Delete the file
File.Delete(GetStorageFile());
//Re-throw exception
throw;
}
finally
{
//Close stream for the normal case
if (fileStream != null)
{
fileStream.Close();
}
}
As you will see, if something goes bad writing the file, the fileStream will be closed twice. I know that it works, but I don't think that is the best implementation.
I think that I could remove the finally block, and close the stream in the try block, but I have posted this here because you guys are experts and I want to hear the voice of an expert.
If you put the fileStream in a using block you don't need to worry about closing it, and then just leave the cleaning up (deleting of the file in the catch block.
try
{
using (FileStream fileStream = new FileStream(GetStorageFile(),
FileMode.Create, FileAccess.Write, FileShare.Write))
{
//write the file ...
}
}
catch (Exception ex)
{
File.Delete(GetStorageFile());
//Re-throw exception
throw;
}
I believe what you want is this:
var fs = new FileStream(result.FilePath, FileMode.Open, FileAccess.Read, FileShare.None, 4096, FileOptions.DeleteOnClose);
I've used it with ASP.Net to have the web server return a result to a temp file that's on disk, but to make sure it's cleaned up after the web server finishes serving it to the client.
public static IActionResult TempFile(string tempPath, string mimeType, string fileDownloadName)
{
var fs = new FileStream(tempPath, FileMode.Open, FileAccess.Read, FileShare.None, 4096, FileOptions.DeleteOnClose);
var actionResult = new FileStreamResult(fileStream: fs, contentType: mimeType)
{
FileDownloadName = fileDownloadName
};
return actionResult;
}

Categories