一、理论准备
当设计一个数据表时,考虑使用何种列的数据类型对性能有比较大的影响,如存储空间、查询开销等。甚至还影响到一些操作,如ip地址以字符串的形式存储在数据库中,就不可以直接比较大小。还有一点需要考虑,那就是可读性!数据虽然是存储在数据库中,但也要考虑到可读性问题。
大家都知道ip地址分为ipv4、ipv6,这里我以ipv4为例介绍,ipv6原理是一样的。ipv4的小为32bits(或者说是4Bytes),在使用过程中,我们通常是用点分十进制格式,如192.168.120.65。如何把"192.168.120.65"存储到数据库中呢?
我们考虑下面三个因素:
- 可读性
- 存储效率
- 查询效率
把"192.168.120.65"存储到数据库中有多少中可行方法呢?见下表所示:
数据类型 | 大小 | 注释 |
varchar(15) | 占7~15字节 | 可读性最好(192.168.120.65),但是最费存储空间 |
bigint | 8 字节 | 可以将ip地址存储为类似192168120065的格式,这种可读性稍差,也比较费存储空间 |
int | 4 字节 | 这种可读性很差,会存储为1084782657,由192*16777216+168*65536+120*256+65-2147483648计算所得,占用存储空间少。 |
tinyint | 4 字节 | 用4个字段来分开存储ip地址,可读性稍差(分别为192, 168, 120, 65),存储空间占用少 |
varbinary(4) | 4 字节 | 可读性差(0xC0A87841),存储空间占用少 |
从大小来看,依次varchar(15)> bigint> int、tinyint、varbinary(4)。
从可读性来看,依次是varchar(15)> bigint> tinyint> varbinary(4)>int。
从查询效率来看,
综合考虑,似乎tinyint比较好,其次是varbinary(4)。但是tinyint需要占多个表字段,而varbinary只需要占用一个字段即可。正确性还有待下面的实验检查!!!
使用整数存储Ip地址而不是字符串。一般来说, 在保证正确性的前提下,尽量使用最小的数据类型来存储和表示数据。小的数据类型一般比大的更快,因为小的数据类型占用的磁盘空间,内存和cup缓存都相对小,需要的cpu处理也要相对少; 这个原则很重要,但是设计的时候也不要低估需要存储的数据的数据范围。
综合可读性、存储效率、查询效率,我给这三者排序是:
如果考虑存储效率,tinyint是最好的!其次是bigint,然后是varbinary(4)
如果更多的是考虑查询效率,bigint是最好的!其次是varbinary(4),然后是tinyint
二、代码实现
/**
*@author cjn
*@version 1.0
*@date 2018年9月4日 下午16:38:15
*@remark
*
**/
public class IpUtil {
public static void main(String[] args) {
System.out.println(ipTolong("127.0.0.1"));
System.out.println(longToIp(2130706433L));
//learnLongToIp(2130706433);
System.out.println(ipTolong("180.167.72.252"));
System.out.println(longToIp(3030862076L));
int a= (int)3030862076L;
//int 的 -1264105220 等价于 long 3030862076
System.out.println(a);
//learnLongToIp(-1264105220);
}
/**
* 字符串IP转换为long 值
* */
public static Long ipTolong(String ip){
String[] strs = ip.split("\\.");
Long ipNumber = (Long.parseLong(strs[0]) << 24)
+(Long.parseLong(strs[1]) << 16)
+(Long.parseLong(strs[2]) << 8)
+(Long.parseLong(strs[3]));
return ipNumber;
}
/**
* long值转换为字符串 的 IP
* 将十进制整数形式转换成127.0.0.1形式的ip地址
* 将整数形式的IP地址转化成字符串的方法如下:
* 1、将整数值进行无符号右移位操作(>>>),右移24位,右移时高位补0,得到的数字即为第一段IP。
* 2、通过与操作符(&)将整数值的高8位设为0,再右移16位,得到的数字即为第二段IP。
* 3、通过与操作符吧整数值的高16位设为0,再右移8位,得到的数字即为第三段IP。
* 4、通过与操作符吧整数值的高24位设为0,得到的数字即为第四段IP。
*/
public static String longToIp(Long ip){
StringBuilder sb = new StringBuilder("");
sb.append(String.valueOf(ip >>> 24));
sb.append(".");
sb.append(String.valueOf( (ip & 0x00FFFFFF) >>> 16 ));
sb.append(".");
sb.append(String.valueOf( (ip & 0x0000FFFF) >>> 8 ));
sb.append(".");
sb.append(String.valueOf( ip & 0x000000FF ));
return sb.toString();
}
public static void learnLongToIp(int ip){
System.out.println("原始:"+Integer.toBinaryString(ip));
System.out.println("24处"+Integer.toBinaryString(ip >>> 24));
System.out.println("16处0x00FFFFFF:"+Integer.toBinaryString(0x00FFFFFF));
System.out.println("16处:"+Integer.toBinaryString((ip & 0x00FFFFFF) >>> 16 ));
System.out.println("8处0x0000FFFF:"+Integer.toBinaryString(0x0000FFFF));
System.out.println("8处:"+Integer.toBinaryString((ip & 0x0000FFFF) >>> 8 ));
System.out.println("0处0x000000FF:"+Integer.toBinaryString(0x000000FF));
System.out.println("0处:"+Integer.toBinaryString(ip & 0x000000FF));
}
}
三、动手实践计算验证
127.0.0.1 转换后 2130706433
2130706433 二进制 1111111000000000000000000000001
**************** 分割线 *********************
0111 1111 0000 0000 0000 0000 0000 0001
>>> 24
0000 0000 0000 0000 0000 0000 0111 1111 => 127
**************** 分割线 *********************
0111 1111 0000 0000 0000 0000 0000 0001
& 0x00FFFFFF
0000 0000 1111 1111 1111 1111 1111 1111
0000 0000 0000 0000 0000 0000 0000 0001
>>> 16
0000 0000 0000 0000 0000 0000 0000 0000 => 0
**************** 分割线 *********************
0111 1111 0000 0000 0000 0000 0000 0001
& 0x0000FFFF
0000 0000 0000 0000 1111 1111 1111 1111
0000 0000 0000 0000 0000 0000 0000 0001
>>>8
0000 0000 0000 0000 0000 0000 0000 0000 => 0
**************** 分割线 *********************
0111 1111 0000 0000 0000 0000 0000 0001
& 0x000000FF
0000 0000 0000 0000 0000 0000 1111 1111
0000 0000 0000 0000 0000 0000 0000 0001 => 1
**************** 分割线 *********************
180.167.72.252
3030862076 二进制 10110100101001110100100011111100
**************** 分割线 *********************
1011 0100 1010 0111 0100 1000 1111 1100
>>> 24
0000 0000 0000 0000 0000 0000 1011 0100 => 180
**************** 分割线 *********************
1011 0100 1010 0111 0100 1000 1111 1100
& 0x00FFFFFF
0000 0000 1111 1111 1111 1111 1111 1111
0000 0000 1010 0111 0100 1000 1111 1100
>>> 16
0000 0000 0000 0000 0000 0000 1010 0111 => 167
**************** 分割线 *********************
1011 0100 1010 0111 0100 1000 1111 1100
& 0x0000FFFF
0000 0000 0000 0000 1111 1111 1111 1111
0000 0000 0000 0000 0100 1000 1111 1100
>>>8
0000 0000 0000 0000 0000 0000 0100 1000 => 72
**************** 分割线 *********************
1011 0100 1010 0111 0100 1000 1111 1100
& 0x000000FF
0000 0000 0000 0000 0000 0000 1111 1111
0000 0000 0000 0000 0000 0000 1111 1100 => 252