-->

Seeking a particular value when using BinaryWriter

2019-09-01 19:03发布

问题:

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)

回答1:

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:

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..