【小松教你手游开发】【系统模块开发】unity 数据储存到本地为二进制文件(聊天记录本地储存)

unity游戏开发中有很多需要把数据储存到本地,官方有好几个方式可以使用,下面简单介绍一下。

一、Stream::Write,Stream::WriteLine

这个方法是打开数据流就开始写字符串,可以指定长度写,也可以一行一行的写。具体参考http://blog.csdn.net/dingxiaowei2013/article/details/19084859和雨松大神的http://www.xuanyusong.com/archives/1069

这种方法最简单,一行一行的写,一行一行的读,都已String的形式写下来,可以中午可以英文。

缺点是不是二进制文件,不好做数据截断获取,比如你想把一个数据包保存下来,中间的各种数据需要你在字符串中写标记符进行切分。


二、[System.Serializable]标记,BinaryFormatter或xml写入

把自己的数据类型标记成可序列化数据二进制文件,用BinaryFormatter来进行写入读取操。具体参考http://bbs.9ria.com/thread-417373-1-1.html

这种方法也比较简单,数据按数据结构存放,数据不用做解析直接按数据结构使用

缺点是只能存放一个数据,每次写入读取都是一个数据,并不适合大多数情况(有一种用哈希表的形式进行拓展,不好用不说据说在ios上还有问题)


三、自己做数据解析,BinaryWriter写入

可以定义一个数据结构,这个结构里面的确定这个数据结构里的每个变量的数据类型,如果有字符串还需要获取字符串长度放在字符串前面

也就是一般网络传输的方式。

我现在项目用的就是这个方法,具体我会在后面贴上代码。

这种方法比较麻烦,需要自己定义数据结构和确定好每个数据结构的变量数据类型,字符串需要计算长度,但是这种方法可以适合任何场景。并且数据不需要换行只管一直写入,

而且这种方面解决上面两个方式的缺点

这个方法还有一个缺点是如果数据结构改了需要改读取和写入数据的方法。


因为想着写入本地序列化不只是我一个需求,需要可拓展性。

跟进上面这些我对类结构进行了一些安排


下面是代码和解释

我写的是一个保存聊天记录的功能

数据结构基类(暂时里面为空。。。虽然没有东西但是总觉得以后需要所以建了个基类)

using UnityEngine;
using System.Collections;

public class SerializeDataBase
{

}


聊天数据的数据结构

using UnityEngine;
using System.Collections;

public class ChatSerializeData : SerializeDataBase
{
    public long timeStamp;
    public int senderID;
    public int receiverID;
    //public short contentLength //这里有16位代表下面的字符串长度
    public string content;

}


序列化的基类,写成单例

因为是io,所有写到一个线程里,用队列的存储,读写都在同一个线程中 

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Threading;

public class SerializeClassBase where T : SerializeDataBase where S : new()
{
    public delegate void LocalDataReadDelegate(List dataList);
    Queue m_onReadDoneDelegateQueue = new Queue();

    Thread m_thread;
    bool m_threadStart = true;

    Queue m_writeThreadQueue = new Queue();
    Queue m_readThreadQueue = new Queue();


    private static S m_instance;
    public static S Instance
    {
        get
        {
            if (m_instance == null)
                m_instance = new S();
            return m_instance;
        }
    }

    public SerializeClassBase()
    {
        m_thread = new Thread(ThreadAsync);
        m_thread.Start();
    }

    protected virtual void WriteFile(T data)
    {

    }

    protected virtual List ReadFile(string arg)
    {
        return null;
    }

    public void SaveData(T data)
    {
        m_writeThreadQueue.Enqueue(data);
    }

    public void GetData(string arg,LocalDataReadDelegate del)
    {
        m_readThreadQueue.Enqueue(arg);
        m_onReadDoneDelegateQueue.Enqueue(del);
    }

    void ThreadAsync()
    {
        while (m_threadStart)
        {

            while(m_readThreadQueue.Count > 0)
            {
                m_onReadDoneDelegateQueue.Dequeue()(ReadFile(m_readThreadQueue.Dequeue()));
            }


            while (m_writeThreadQueue.Count > 0)
            {
                WriteFile(m_writeThreadQueue.Dequeue());
            }


            Thread.Sleep(1000);
        }
    }

    public void StopThread()
    {
        m_threadStart = false;
    }
}

因为写入的时候通常不需要知道是否完成,但是读取的时候需要知道什么时候读取完成,

所有这里读取写的是通过回调的形式返回


聊天存储的类,二进制读写的子类

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Threading;

public class ChatSerializeClass : SerializeClassBase
{
    int MaximumFileCount = 5;

    string folderName = Application.persistentDataPath + "//" + "Chat";

    //Queue m_localFileNameList = new Queue();
    Dictionary> m_localFileNameList = new Dictionary>();


    public ChatSerializeClass()
    {
        if (!Directory.Exists(folderName))
        {
            Directory.CreateDirectory(folderName);
        }
    }

    protected override void WriteFile(ChatSerializeData data)
    {
        //按聊天的好友id创建文件夹
        int targetID;
        if (data.senderID != LoginManager.Instance.PlayerData.CharID)
            targetID = data.senderID;
        else
            targetID = data.receiverID;

        string folderNameWithID = folderName + "/" + targetID;
        if (!Directory.Exists(folderNameWithID))
        {
            Directory.CreateDirectory(folderNameWithID);
        }



        //储存文本
        if (data == null)
            return;

        string fileName = new DateTime(data.timeStamp).ToString("yyyyMMdd");
        FileStream fs = new FileStream(folderNameWithID + "/" + fileName, FileMode.Append);
        BinaryWriter bw = new BinaryWriter(fs);

        bw.Write(data.timeStamp);
        bw.Write(data.senderID);
        bw.Write(data.receiverID);
        bw.Write(Encoding.UTF8.GetByteCount(data.content));
        bw.Write(Encoding.UTF8.GetBytes(data.content));

        bw.Close();
        fs.Close();
        fs.Dispose();


        CDebug.Log("senderID:" + data.senderID + "--------");
    }

    protected override List ReadFile(string fileName)
    {
        List dataList = new List();

        FileStream fs = new FileStream(fileName, FileMode.Open);
        BinaryReader br = new BinaryReader(fs);

        try
        {
            while (true)
            {
                ChatSerializeData data = new ChatSerializeData();
                data.timeStamp = br.ReadInt64();
                data.senderID = br.ReadInt32();
                data.receiverID = br.ReadInt32();
                int contentLength = br.ReadInt32();
                data.content = Encoding.UTF8.GetString(br.ReadBytes(contentLength));
                dataList.Add(data);
            }
        }
        catch (Exception)
        {
            CDebug.Log("ReadFile:" + fileName + "----done!");
        }
        br.Close();
        fs.Close();
        fs.Dispose();


        return dataList;
    }

    /// 
    /// 通过好友id找到聊天记录,一次获取一天的聊天信息 
    /// 
    /// 
    public void GetDataByID(int id, LocalDataReadDelegate del)
    {
        string folderNameWithID = folderName + "/" + id;

        //判读是否有该id的聊天数据
        if (!Directory.Exists(folderNameWithID))
        {
            if (del != null)
                del(null);

            return;
        }

        //判断是否已经读取该id的文件列表,读取并排序
        if(!m_localFileNameList.ContainsKey(id))
        {
            Queue list = new Queue();
            string[] fileList = Directory.GetFiles(folderNameWithID);
            for (int i = 0; i < fileList.Length; i++)
            {
                list.Enqueue(fileList[i]);
            }
            list.Sorted();

            //如果超过5个删除
            if(list.Count > MaximumFileCount)
            {
                int count = list.Count;
                for (int i =0; i < count - MaximumFileCount; i++)
                {
                    File.Delete(list.Dequeue());//写上删除逻辑
                }

            }
            list.Reversed();

            m_localFileNameList.Add(id, list);
        }

        //读取文件
        if(m_localFileNameList[id].Count >0)
            GetData(m_localFileNameList[id].Dequeue(), del);
    }
}

由于我的聊天读写功能再稍微复杂一点,需要按id按天数来保存,并且删除超过5天以上的聊天记录,所以实际逻辑并不需要这么多


实际上只需要继承并重写

    protected virtual void WriteFile(T data)
    {

    }

    protected virtual List ReadFile(string arg)
    {
        return null;
    }

读写函数,因为这个涉及到你的数据应该怎么个读写法,你的数据规则,所以这个需要自己去重写。


使用的方法就是

ChatSerialieClass.Instance.SaveData();

ChatSerialieClass.Instance.GetData();


只有像我聊天那样比较复杂的需求才需要另外写接口再调用上面的函数。

 



这样就可以做到需要的需求和方便其他人拓展

你可能感兴趣的:(【unity系统模块开发】)