オブジェクトをシリアライズでバイナリ保存、読みこみについて

タグの編集
投稿者 たかくん  (社会人) 投稿日時 2012/7/3 07:49:02
おはようございます。
今、自作クラスをシリアライズでバイナリ保存しています。
ストリームはFileStreamクラスでやっていますがSystem.IO.StreamReaderクラスは使えないみたいですね。
今困っているのはStreamReaderクラスではEndOfStreamプロパティがあるのでいいのですがFileStreamクラスではファイルの末尾に来たかどうかをどう探知すればいいのでしょうか?
よろしくお願いします。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2012/7/3 10:19:58
> 今、自作クラスをシリアライズでバイナリ保存しています。
どのようにしてバイナリ シリアライズしていますか?

自前でバイナリ保存/復元する方法のほか、VB の FilePut/FileGetを使う方法、
あるいは、各種シリアライザを使う方法などがあるかと思います。

また、シリアライザを使う手法ににしても、.NET標準の
BinaryFormatter や DataContractSerializer(のバイナリXML形式) の他、
オープンソース実装の物などがあり、それぞれで事情が異なるかと。


> ストリームはFileStreamクラスでやっていますが
ファイル化するなら FileStream、オンメモリなら MemoryStream あたりが定番ですね。


> System.IO.StreamReaderクラスは使えないみたいですね。
シリアライザの多くは、Stream (を継承した)クラスへの保存と読み込みに対応しています。

そして StreamReader の場合は、BaseStream プロパティから、元となる Stream を
取り出せるはずです。StreamReader のコンストラクタに Stream を渡していればそれが、
StreamReader のコンストラクタにパスを渡していれば FileStream が返されるかと。


> FileStreamクラスではファイルの末尾に来たかどうかをどう探知すればいいのでしょうか?
基本的には、Read 系メソッドの戻り値で判断することになります。
これは FileStream や StreamReader に限らず、Stream 継承クラスのすべてに言える事です。
投稿者 YuO  (社会人) 投稿日時 2012/7/3 10:20:31
PositionプロパティとLengthプロパティを比較すれば,末尾かどうかはわかります。

ちなみに,StreamReaderはその名の通り「ストリームから読み出す」ものです。
また,継承関係から分かる通り,「テキストを読み出す」ものです (TextReaderを継承している)。
投稿者 たかくん  (社会人) 投稿日時 2012/7/4 11:13:30
こんにちは、YuOさんありがとうございます。
一応コードを乗せてみます。
コメントはテキストファイルとしてやっていたのを残してるだけなので気になされないで下さい。
このメソッドは保存ですが問題は読み込みの時にどうファイルの末尾の探知をするかです。
   ''' <summary>
    ''' 棋譜データ保存
    ''' </summary>
    ''' <param name="filename">ファイル名</param>
    ''' <remarks></remarks>
    Public Sub Save(ByVal filename As String)
        If (Me.RePlayModeBoolean) Then
            TestClass.OutPutSystemFlow("RePlayClass->Save()")
            Dim count As SByte = 0
            'Dim save As New System.IO.StreamWriter(filename, False, System.Text.Encoding.Default)
            Dim save As New FileStream(filename, FileMode.Create)
            Dim binalyfarmat As New BinaryFormatter
            Try
                For Each log As Stone(,) In Me.ReplayStoneBordList
                    'For Each stone As Stone In log
                    'save.Write(stone.LogicalPoint.X.ToString + ",")
                    'save.Write(stone.LogicalPoint.Y.ToString + ",")
                    'save.Write(stone.StoneRectangle.Width.ToString + ",")
                    'save.Write(stone.StoneRectangle.Height.ToString + ",")
                    'save.Write(stone.State.ToString + ",")
                    'save.Write(stone.IsReach.ToString + ",")
                    'save.WriteLine(stone.IsClearStone.ToString)
                    binalyfarmat.Serialize(save, log)
                    'Next
                    binalyfarmat.Serialize(save, Me.NowPutStoneList(count))
                    'save.Write(Me.NowPutStoneList(count).LogicalPoint.X.ToString + ",")
                    'save.Write(Me.NowPutStoneList(count).LogicalPoint.Y.ToString + ",")
                    'save.Write(Me.NowPutStoneList(count).StoneRectangle.Width.ToString + ",")
                    'save.Write(Me.NowPutStoneList(count).StoneRectangle.Height.ToString + ",")
                    'save.Write(Me.NowPutStoneList(count).State.ToString + ",")
                    'save.Write(Me.NowPutStoneList(count).IsReach.ToString + ",")
                    'save.WriteLine(Me.NowPutStoneList(count).IsClearStone.ToString + ",")
                    count += 1
                Next
                save.Dispose()
                save.Close()
            Catch ex As Exception
                Me.TestClass.SystemLog("RePlayClassClass->Save()" + ex.Message)
            End Try
        Else
            MessageBox.Show(KeyWordNotGameEnd, "五目並べ", MessageBoxButtons.OK, MessageBoxIcon.Warning)
        End If
    End Sub
投稿者 shu  (社会人) 投稿日時 2012/7/4 13:57:11
こんな感じでどうでしょう。

        Dim rd As New FileStream(ファイル名, FileMode.Open)
        Dim binaryformat As New BinaryFormatter

        ReplayStoneBordList = New List(Of Stone(,))
        NowPutStoneList = New List(Of Stone)

        Do While rd.Position < rd.Length
            Dim log(,) As Stone = DirectCast(binaryformat.Deserialize(rd), Stone(,))
            ReplayStoneBordList.Add(log)
            Dim stn As Stone = DirectCast(binaryformat.Deserialize(rd), Stone)
            NowPutStoneList.Add(stn)
        Loop

        rd.Close()
投稿者 たかくん  (社会人) 投稿日時 2012/7/5 00:24:25
今晩はshuさん、ありがとうございます。
そうです、これでできると思います。
でも自分で質問しておいて後で思ったのですが、RePlayStoneBordはList(Of Stone(,))なのですが
これ自体をシリアライズしてこれ自体をデシリアライズした方が早かったかもですね。
Listはオブジェクトのアドレスを保持してるだけだからシリアライズできるかやってみないとわかりませんけど・・・やってみます、ありがとうございました。
投稿者 YuO  (社会人) 投稿日時 2012/7/5 10:00:22
出遅れていますが……。


シリアライズしたい内容を一つのインスタンスにできないのですか。
例えば,Tuple(Of List(Of Stone(,)), List(Of Stone))型のインスタンスとかに。

複数回シリアライズ/デシリアライズした場合,それぞれのシリアライズ/デシリアライズにおいて,
本来共有されていたインスタンスが別のインスタンスに分離します。
Imports System.IO
Imports System.Runtime.Serialization.Formatters.Binary

<Serializable()>
Class Foo
    Private _value As Integer

    Public Sub New(value As Integer)
        _value = value
    End Sub

    Public Property Value As Integer
        Set(value As Integer)
            _value = value
        End Set
        Get
            Return _value
        End Get
    End Property

    Public Overrides Function ToString() As String
        Return String.Format("{{ Value : {0} }}", _value)
    End Function

    Shared Sub Main()
        Dim f1 As Foo, f2 As Foo
        f1 = New Foo(10)
        f2 = f1

        Console.WriteLine("初期")
        Console.WriteLine("f1 : {0} / f2 : {1}", f1, f2)
        f1.Value = 20
        Console.WriteLine("f1 : {0} / f2 : {1}", f1, f2)
        Console.WriteLine()

        Dim ms As MemoryStream
        Dim formatter As New BinaryFormatter

        ms = New MemoryStream()
        formatter.Serialize(ms, f1)
        formatter.Serialize(ms, f2)

        ms.Seek(0L, SeekOrigin.Begin)
        Dim f3 As Foo = DirectCast(formatter.Deserialize(ms), Foo)
        Dim f4 As Foo = DirectCast(formatter.Deserialize(ms), Foo)

        Console.WriteLine("独立")
        Console.WriteLine("f3 : {0} / f4 : {1}", f3, f4)
        f3.Value = 30
        Console.WriteLine("f3 : {0} / f4 : {1}", f3, f4)
        Console.WriteLine()

        ms = New MemoryStream()
        formatter.Serialize(ms, Tuple.Create(f1, f2))

        ms.Seek(0L, SeekOrigin.Begin)
        Dim t As Tuple(Of Foo, Foo) = DirectCast(formatter.Deserialize(ms), Tuple(Of Foo, Foo))
        Dim f5 As Foo = t.Item1
        Dim f6 As Foo = t.Item2

        Console.WriteLine("一括")
        Console.WriteLine("f5 : {0} / f6 : {1}", f5, f6)
        f5.Value = 30
        Console.WriteLine("f5 : {0} / f6 : {1}", f5, f6)
        Console.WriteLine()

        Console.ReadLine()
    End Sub
End Class
これを実行すると,
初期
f1 : { Value : 10 } / f2 : { Value : 10 }
f1 : { Value : 20 } / f2 : { Value : 20 }

独立
f3 : { Value : 20 } / f4 : { Value : 20 }
f3 : { Value : 30 } / f4 : { Value : 20 }

一括
f5 : { Value : 20 } / f6 : { Value : 20 }
f5 : { Value : 30 } / f6 : { Value : 30 }
となります。
f3.Valueの変更によってf4.Valueは変更されませんが,
f5.Valueの変更によってf6.Valueはf1/f2の関係と同じく変更されます。

これは単純な例ですが,オブジェクトのグラフを辿ると共有している可能性があるので,
基本的には一括でシリアライズ/デシリアライズした方が問題が少なくなると思います。
投稿者 たかくん  (社会人) 投稿日時 2012/7/5 18:06:09
はい、ありがとうございます。
YuOさん、盤情報と棋譜情報をそのままシリアライズした方が早いって後で気づきました。
こんなもんですね(笑)