关于IP地址的存储
一、理论准备
当设计一个数据表时,考虑使用何种列的数据类型对性能有比较大的影响,如存储空间、查询开销等。甚至还影响到一些操作,如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
文章参考:
http://www.cnblogs.com/skynet/archive/2011/01/09/1931044.html
https://blog.csdn.net/wyc_cs/article/details/51742754