Seeking a particular value when using BinaryWriter
Asked Answered
S

1

0

I find it difficult to express myself so I'll jump right into the code

FileStream stream = new FileStream("//ignoreThis//demo.bin", 
                        FileMode.Append, FileAccess.Write, FileShare.Write));
BinaryWriter writer = new BinaryWriter(stream);
writer.Write(myNum);
writer.Write(myString);

I can call this method several times and I know that every time I do so, it will add the new data at the end of the file 'demo.bin'. Now I'm trying to write a method where I can seek to my 3rd instance(I'm not sure if I can call it that). The main problem is that the variables are of a varied size, even though I'm using this code to make the String a 'fixed' length of 20 (I know it doesn't work when someone writes a string longer than 20 but I have already written validation to make sure they don't exceed this limit)

Here's the code I'm using to make the String a constant length

public String MakeFixedSize(int length, String inputString)
{
    return inputString.PadRight(length, '\0');
}

As far as I know, int is of a fixed size as well as long which I'm using in this program (not in above code)

Suffering answered 31/10, 2015 at 18:8 Comment(9)
Have a look at MSDN - It is in German, but you can switch the language, I hope. The code explains what to do when wrting(reading string with a binarywriter. The gist of it is that strings are written by binarywriter as 'length-prefixed' so you can teel the length by looking at the first two bytes. - But you may be better off using a BinaryFormatter and serializing the data. See hereGossamer
What does 'write directly to the folder' mean? One file for each record?Gossamer
I meant file. I want to separate each record in a distinguishable way so as to make searching faster, without using serialization (I.e. using a byte array and storing that via BinaryWriter and reading it back by parsing it back). I already have the code for the parsing and whatnot, but I need a good way to separate recordsSuffering
But with variable size records a direct seek won't work. The reading back in my example is done fast and directly, albeit on a record by record base. It is not (explicitly) parsing bytes, though, just the class member types.Gossamer
So if I understood correctly, I need to prefix the string or serialization (which isn't allowed in this task). My question is why didn't the code I originally wrote to make strings a fixed size work with the binarywriter but worked fine when using serialisation?Suffering
Well, no, the prefix is created by the system, so when you want to use seek with fixed size string you should add those 2 bytes into the calculation as well as the length of your int. It should help to look at the result in a hex-editor.. Also you will need to know about your charcter format! Why it didn't work we can tell because you have not posted that code and not told us what errors it produced..Gossamer
After hours of pounding my head and pulling my fair apart, I finally found the bug. In my code I was doing something like this: class.name = MakeFixedSize(20, class.Name); Where class is a paramaterSuffering
One last thing, what are you using to view the .bin? I'm using notepad++ but it doesn't show me any hexSuffering
There are many freeware hex editors; I used hexedit.nextsoft.deGossamer
G
0

There are many ways to do this and I'm not sure which is the best for your case.

If you can and want to create strictly fixed size records you could go for seeking the right position directly. This may be faster for large quantities of data.

Or you could rely on the BinaryWriter to prefix each string with two length bytes and then read the right number of characters..

If you want to stay more flexible and keep things simple you could go for serializing with a BinaryFormatter:

Create a serializable class:

[Serializable]
public class MyClass
{
    public string Name;
    public int Number;
}

Then you can write a number of instances to disk using a BinaryFormatter:

string fileName = "yourfileName"; 

FileStream stream = new FileStream(fileName, 
                        FileMode.Append, FileAccess.Write, FileShare.Write);
var formatter = new BinaryFormatter();
formatter.Serialize(stream, new MyClass() { Name = "A", Number = 12365 });
formatter.Serialize(stream, new MyClass() { Name = "B", Number = 2365 });
formatter.Serialize(stream, new MyClass() { Name = "C", Number = 365 });
stream.Close();

And read it back, maybe in a List<T>, still using the same formatter:

FileStream stream2 = new FileStream(fileName, FileMode.Open, FileAccess.Read);
List<MyClass> myClassList = new List<MyClass>();

while (stream2.Position < stream2.Length)
    myClassList.Add((MyClass)formatter.Deserialize(stream2));

Now you can access myClassList[2]..

Update:

Looks like you can do this as well, as long as you are sure about the record length:

using (BinaryWriter binWriter = new BinaryWriter(File.Open(fileName, FileMode.Create)))
{
    binWriter.Write("RAller");
    binWriter.Write(123);
    binWriter.Write("RoBler");
    binWriter.Write(23);
    binWriter.Write("RolCer");
    binWriter.Write(3);
    binWriter.Write("RolDDr");
    binWriter.Write(423);
    binWriter.Write("REEler");
    binWriter.Write(523);
    binWriter.Write("RFFler");
    binWriter.Write(66);
}

using (BinaryReader br = new BinaryReader(File.Open(fileName, FileMode.Open)))
{
    // string < 255 seems to get only a one byte length prefix:
    int recLen = 1 + 6 + 4;

    br.BaseStream.Seek(recLen * 3, SeekOrigin.Begin);
    string s = br.ReadString();
    int i = br.ReadInt32();
}

Short strings only get a one byte length counter:

enter image description here

Longer strings get the two byte prefix and for variable sizes you would have to step through the records, calculating the next offset.

I can't really image why you would want that, unless the data are really large.

Well, requirements sometimes are what they are and sometimes they require questioning..

Gossamer answered 31/10, 2015 at 19:44 Comment(2)
The task specifically requests that I don't use serialization but rather store the values directly. Could you expand on prefixing a string? I'm unfamiliar with the conceptSuffering
The link explains it rather well: A length-prefixed string represents the string length by prefixing to the string a single byte or word that contains the length of that string. This method first writes the length of the string as a UTF-7 encoded unsigned integer, and then writes that many characters to the stream by using the BinaryWriter instance's current encoding. It is worth studying the code, where you can see how to read back various types..Gossamer

© 2022 - 2024 — McMap. All rights reserved.