原文地址:https://www.zhaimaojun.top/Note/5475296

将数组存储到数据库的方法

(本人平时同csharp编写代码,所以本文中代码都是csharp代码,有些地方java和csharp有所不同,文中会有提示)
现在的电脑或者手机代码运行速度已经相当快了,而且各种语言,尤其是python,csharp,java等解析类语言的运行效率也大大提升,所以对字符串的操作很简单快速了,所以很多人都喜欢将数据作为字符串进行操作。本方法就是将数组拼接后存储到数据库。基本的代码逻辑如下:
var source = new int[100];//new double[100];//new string[100];//等等各种类型的数组
var result = string.Empty;//最后要存储到数据库中的字符串
foreach(var item in source){
    result += item + ",";//这里要注意,一般都用,号,当然不限于,号,但是要注意,这个符号不能出现在item中,一般情况下,int,dobule,float,等等数组元素tostring之后都不会出现,号,但是string类型的数组很有可能就会包含逗号,所以,需要用一个确保不会出现在item中的符号来作为每个元素的分隔符,这样才能在下次读取数据后能重新分割,分开每个数组成员而不会出错!
new SqlCommand("insert into XX(XX) value(" + result + ")",new SqlConnection("XX")).ExectueNoQuery();//将字符串类型的数据插入数据库
当需要将数据读取出来时(反序列化):
var sourcestring = "XXX,XXX,XXX,XXX";//从数据库读出的数据
var sourcedatas = sourcestring.Split(',');//当初用了什么符号拼接的就用什么符号分割!
var result = new int[sourcedatas.Lenth];//new double[];//new string[];//等等你当初存进去时的数组类型
for(int i = 0;i < sourcedatas.Lenth;i++){    if(string.IsNullOrEmpty(sourcedatas[i])) continue;
    result[i] = int.TryParse(sourcedatas[i], out var v) ? v : default;//double.TryParse//按照当初存进去时的数据类型反向解析出原始数据!如果是string类型的,这个for循环不必要,sourcedatas就是result。

此方法的优缺点:

易读,数据库管理员查库的时候可以直观的看到数据内容,因为都是string,直接可以显示,而且对于很小的标号,比如999以下的标号存储来说,每个标号加一个分隔符都是2-4字符,占用空间还是比较少的。缺点当然也很明显,对于string数组,分隔符不好取,对于double,long类型的数组,如果数据量很大时,就非常浪费空间,因为每个元素可能要占用十多个字符。

在这个方法之前先讲一下数据库中的一种数据类型:binary/varbinary:

这是一种将数据按照8bit直接进行存储的数据类型,数据内容不限制,可以是任何形式的数据。最大空间为8000*8bit。

如果对数据有所了解的同学应该知道,计算机中的所有数据都是binary(二进制)形式存储的,不管是手机还是电脑,只不过数据的意义有所不同。

一般来说,1bit就是1个二进制,8bit就是8个二进制,常常我们将8个二进制用byte表示一个字符,16个二进制用short表示,32个二进制用int表示,64个二进制用long表示,当然这是整形的情况,还有float,是32bit来表示一个float数,用64bit来表示double,等等,这些其实都是计算机的基本知识。

而数组,他其实就是连续存放的一堆数据,只不过给他进行了按大小的分组而已。比如,int[8];他的大小其实就是8个int,也就是8*4个byte,也就是8*4*8个bit,所以我们要把这些数据存储到数据库中,其实基本上就是直接内存拷贝就可以了,但是数据库他不支持内存拷贝啊~~

数据库也没有int[]这种数据类型啊,那怎么办呢,其实很简单,将int[]转化成byte[],再传递给数据库就可以了,数据库中的数据类型binary(8000)/varbinary(8000)就是为此而生的。

但是这里需要考虑一个历史遗留问题:大小端模式问题,不同的操作系统中对数据保存时,大小端可能是不同的,不能盲目的用位移操作和强制类型转换可能导致数据错误的情况,即:int a = 888;byte[] b = new byte[2];b[0] = (byte)(a>>8);b[1] = (byte)a;这种序列化后反向序列化时,可能导致数据出错的情况。

还要考虑语言的问题,java环境中对数据的正负要求非常严格,如果代码不正确,序列化和反序列化时可能导致数据的异常,这就是考验程序员能力的时候了。

下面看序列化过程:(将int数组转为byte数组)

var values = new int[100];//new double[100];//new float[100];//一些基本的数组类型,但是不能为string,除非所有的string都在GetBytes之后等长,如果不等长,反序列化时会造成困难,当然,也不是不行,在这部分代码中不行,下面还有一种方法,用于介绍不等长string的存储。
var result = new byte[values.Length * 8];//byte类型的需要存储到数据库中的结果数据
for (int i = 0; i < values.Length; i++)
    var bts = BitConverter.GetBytes(values[i]);//这里不同的数据类型他的长度是不一样的。需要注意
    Array.Copy(bts, 0, gds, i * bts.Lenth, bts.Lenth);//这里的长度要注意,不是固定的8,应该用bts.Lenth为准!不同的数据类型转化为byte后长度是不同的!但是所有的相同数据类型的数据转化后的byte是相同的,比如:int a=9;int b=900999;他们都是int,所以转化成byte后都是4个byte。(4个byte就不要抬杠了,如果非要抬杠,请自己换成sizeof(int)去计算长度)再比如,int a=9;double b = 9;他们是不同的数据类型,转化后a的长度是4,而b的长度是8,他们是不一样的!

在看反序列化过程:(将数据库中取出来的byte转化成int数组)

var soucedata= new byte[8000];//数据库中读取出来的binary类型的数据
var result = new int[soucedata.Length / sizeof(int)];//这里数据类型的就是当初存储时的原始类型,一般int的大小是4,如果不确定,可以用sizeof计算,不同的类型这里的类型是不同的
for (int i = 0; i < soucedata.Length; i++)
    result[i] = BitConverter.ToInt(soucedata[i],i*sizeof(int));//这里就需要考虑大小端的问题,BitConverter默认是小端模式,你可以自己百度如何设置大小端。

此方法的优缺点:

对于不等长不规则的数组无能为力,数据的不直观,数据库管理者看到的都是一堆16进制的码,易读性差,几乎不可改错,但是,对大数据的存储效果相当可观,能大大降低存储的数据量,而且几乎是将数据进行了内存拷贝,对于数据的使用来说提高了效率。

这是基于方法二的一种扩展方法,针对一些不规则数组,比如string[];struct等,与方法二同样,都是使用数据库的binary/varbinary数据类型存储数据的,主要解决了方法二中数据不能可变长度的问题。

下面看序列化过程:(不规则数租转化为byte数组)

var sourcedata = new string[]{ "ff", "aaaa", "dddddddddddd"};//不规则长度的字符串数组
var ms = new MemoryStream();//用到了一个流,便于操作byte
foreach(var item in sourcedata){
    var bt = Encoding.Utf8.GetBytes(item);//这是一种字符的编码形式,编码方式决定了反序列化后看到的内容是不是乱码,所以很关键!一般网络上都用的utf-8编码,这种编码支持多国语言,但是只是支持常用的词,有些生僻词是不支持的,根据自己的使用环境自己决定编码方式。
    var lbt = BitConverter.GetBytes((int)bt.Lenth);//这里用4个字符来表示这个可变长度数组内容的长度,
    ms.Write(lbt,0,lbt.Lenth);//先写入4字节的长度,
    ms.Write(bt,0,bt.Lenth);//再写入对应长度的内容,
ms.Seek(0,SeekOrigin.Begin);//这里是必须的,为了让ms流回到起点,否则后面的toarray会返回0字节
var result = ms.ToArray();//这里就得到了要写入到数据库的所有内容,当然,这里注意,result的大小要在8000以内,否则存储不进去的。

再看反序列化过程:(byte数组还原为数组)

var sourcedata = new byte[8000];//从数据库中读出的原始数据
var resultlist = new List<string>();//将要转换为的不规则数据的类型的数组
var ms = new MemoryStream(sourcedata);
    var bt = new byte[sizeof(int)];
    ms.Read(bt,0,bt.Lenth);
    var l = BitConverter.ToInt(bt,0);//获取不规则数据的长度
    bt = new byte[l];
    ms.Read(bt,0,bt.Lenth);
    resultlist.Add(Encoding.Utf8.GetString(bt));//这里要使用存入数据库时使用的编码形式去转化,否则得到的结果可能都是乱码
} while(ms.Position &lt; ms.Lenth);
var result = resultlist.ToArray();//得到了之前的不规则数据

此方法的优缺点:

这种方法可以对批量的字符串数组进行存储,能够很好的将不规则不等长的数组进行存储,补足了方法二中的缺点。但是我写的列子是对string[]进行存储的,没有写对其他类型的比如对象的属性变量等进行存储的,如果想要将某个对象进行存储,其实这个方法进行一定的变化就可以了,利用反射,获取对象的属性和变量,就可以进行存储了。代码就不贴了,如果有人想要就直接联系我。一般没几个人想要的。