一、理论准备

当设计一个数据表时,考虑使用何种列的数据类型对性能有比较大的影响,如存储空间、查询开销等。甚至还影响到一些操作,如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