目录
1. redis的哈希表的value,只能是int、string、bytes
2. bytearray和bytes可以互转,并且bytearray可以改变长度
3. array.array将传给bytearray的float数组先转为正确格式
0.前言
-
问题背景:python下用redis管理数据库
-
数据库结构为:
-
A(用户) 管理多个B(物品), 每个B下有多个C(物品的不同图片),其中C是float数组(直接拿到时是numpy.array)
-
原本操作是一个用户一个redis的db(分页库一样的东西),每个db里按照key去以list格式存放C(C本来就是float数组),另外“比较”只涉及同个用户底下的数据的比较(不同db不能比较),因此这种按list进行数据库管理的考虑是很自然的
-
最大的问题在于一点,redis的db库只有16个,因此上种写法在用户数达到16个时,无法进行新的录入
-
=======================================================
-
因此考虑使用redis的hash(哈希表)格式管理数据库
-
形式为:db=0下,用户名为哈希表(key),物品为键值对的域(field),一个物品所有的图片合成一个存为键值对的值(value)
-
在“一个物品所有的图片合成一个存为键值对的值(value)”这步遇到了坑,以下是debug历程和结论:
1. redis的哈希表的value,只能是int、string、bytes
- int和string都有局限性,bytes是二进制,理论上所有的数据都能转成二进制压起来。所以考虑用bytes
- 一开始考虑用np.tobytes和np.frombuffer来把整个numpy.array压缩,然后用b',,,'进行分隔,读取时用split(b",,,")去分割。这步我当时写的时候也觉得很有问题,因为传给数据库是一个bytes,每次增加新的C(float数组)都是按照“+b",,,"+bytes(float_array)”的形式在这个value尾部加上。
- 后来我才确定bytes是不能改变长短的。
2. bytearray和bytes可以互转,并且bytearray可以改变长度
后来我发现了好东西bytearray,其实就是bytes的array,和普通array的函数基本一致。我试验了下面这段代码,很成功:
import numpy as np
a = np.array([0.11,0.22,0.44]) #len(a) =3
b = np.array([0.55,0.66,0.77]) #len(b) =3
ba = bytearray(b'')
ba.extend(bytearray(a))
blist = bytes(ba)
ba = bytearray(blist)
ba.extend(bytearray(b))
blist = bytes(ba)
print(np.frombuffer(blist)) # 结果:[0.11 0.22 0.44 0.55 0.66 0.77]
但是我改了一下a,b的值,出现了奇怪的结果:
import numpy as np
a = np.array([1,2,3])
b = np.array([4,5,6])
ba = bytearray(b'')
ba.extend(bytearray(a))
blist = bytes(ba)
ba = bytearray(blist)
ba.extend(bytearray(b))
blist = bytes(ba)
print(np.frombuffer(blist)) # 结果:[4.24399158e-314 8.48798317e-314 1.27319747e-313]
但是改成float就没问题:
import numpy as np
a = np.array([1.,2.,3.])
b = np.array([4.,5.,6.])
ba = bytearray(b'')
ba.extend(bytearray(a))
blist = bytes(ba)
ba = bytearray(blist)
ba.extend(bytearray(b))
blist = bytes(ba)
print(np.frombuffer(blist)) # 结果:[1. 2. 3. 4. 5. 6.]
当时我有点侥幸心理,因为我的数据肯定是float,但是遇到一个bug:
我的数据是512维的数组,就测一个数组的情况下(存给value对应的数组总长就512),
但是先做一遍bytearray→bytes(存入数据库),再做一遍bytes→bytearray(从数据库取出)时,取出来的数组长度为256。
一开始我搜错了,以为bytearray最大长度是256。后面发现不是这样。仔细考虑了一下,觉得应该是我程序里获取到的那个float数组和我上面测试里的float数组格式不一样(32位和64位的区别)
因此我去找bytearray或者bytes的转格式时能不能指定数据的格式,搜了一圈发现没法通过bytearray指定这个数组里元素的格式。
3. array.array将传给bytearray的float数组先转为正确格式
既然bytearray()函数没法指定数组元素的格式,所以我就转而找到array.array先进行转格式。
我看了一下array.array支持的格式:
Type code | C Type | Python Type | Minimum size in bytes |
---|---|---|---|
'c' | char | character | 1 |
'b' | signed char | int | 1 |
'B' | unsigned char | int | 1 |
'u' | Py_UNICODE | Unicode character | 2 |
'h' | signed short | int | 2 |
'H' | unsigned short | int | 2 |
'i' | signed int | int | 2 |
'I' | unsigned int | long | 2 |
'l' | signed long | int | 4 |
'L' | unsigned long | long | 4 |
'f' | float | float | 4 |
'd' | double | float | 8 |
看到上表最后两行,应该是发现了某个python坑,因此改动上面的测试代码,同时同步更新我的程序里的写法为:
import numpy as np
import array
a = np.array([1,2,3])
b = np.array([4,5,6])
ba = bytearray(b'')
ba.extend(bytearray(array.array('d',a)))
blist = bytes(ba)
ba = bytearray(blist)
ba.extend(bytearray(array.array('d',b)))
blist = bytes(ba)
print(np.frombuffer(blist)) # 结果:[1. 2. 3. 4. 5. 6.]
在我正式的程序中也正确。
另外,在np.frombuffer()中指定np.float32或者np.float64似乎也能解决问题。
后话:我看到有很多SO上的回答都是推荐用struct,我看那个形式太丑了就不想用。
4. 参考:
- 【Python】array.array by 吉吉于
- StackOverflow: How can I convert a byte array to a double in python? by Martijn Pieters♦
- StackOverflow: Convert bytearray to bytes-like object?
- StackOverflow: Python bytes concatenation by Jasper
5. TODO
- 时间测试
-