最近看到一則招聘的JD,附了一個算法題的鏈接,原題如下:
請實現一個IP白名單過濾算法,實現以下接口
boolean addWhiteIpAddress(String ip);
boolean isWhiteIpAddress(String ip);
要求如下:
占用空間盡量少
運算效率盡量高
在內存中完成查詢及判斷
接口可能被並發詢問
盡量能存儲整個IP地址空間
代碼可運行,且包含單測
-
思路:
如題,需要實現的是一個白名單的功能而不是黑名單,且要求盡可能存儲整個IP地址空間,所以如果直接存儲ip地址的字符串32位JVM下需要約:2^32 * (32+32+ (7+15)/2) = 300GB(這里只是非常粗略的估算),當然如果把字符串換成char數組可以省下對象頭和類型指針,預計需約44GB,這完全超出了32位JVM的堆空間,而且就算借助於文件系統和硬盤,這么大數據量的字符串比較效率也是非常低下的。
所以換個思路,針對IPV4,一個IP地址為32個bit位,可以直接將IP地址轉換為int類型。又因為白名單只有兩種狀態,要么在白名單中,要么不在,所以很自然就想到位圖了,用兩個BitSet即可存儲全部IPV4的地址,占用512M多的內存,也支持隨機訪問。 -
缺點:
- volatile修飾,性能會降低(修飾對象或數組,如果對象屬性或數組元素被修改,可以間接保證對象屬性或數組元素的線程可見性)
- 目前沒考慮IPV6
- 測試的數據量不夠,可能有些邊界還沒考慮到,還沒做性能測試
import java.util.BitSet;
import org.junit.Test;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
public class IPFiliterTest {
// Java語言中沒有無符號整形,int的最大值只能表示到0x7fffffff,所以需要兩個BitSet來存儲高低位
private static volatile BitSet low = new BitSet(Integer.MAX_VALUE);
private static volatile BitSet high = new BitSet(Integer.MAX_VALUE);
/**
* 固定8位,前面不足的補零
* @param num
* @return
*/
private static String getByteBinaryStr(int num) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 8; i++) {
if ((num & 1) == 1) {
sb.append(1);
} else {
sb.append(0);
}
num = num >> 1;
}
return sb.reverse().toString();
}
/**
* 將IP地址字符串轉換為int
* @param ip
* @return
*/
private static int chgIpStrToInt(String ip) {
if (ip == null || StringUtils.isEmpty(ip)) {
throw new RuntimeException("Null string");
}
String[] arr = ip.split("\\.");
if (arr == null || arr.length != 4) {
throw new RuntimeException("Invalid ip");
}
if ("128.0.0.0".equals(ip)) {
return Integer.MIN_VALUE;
}
StringBuilder sb = new StringBuilder();
sb.append(getByteBinaryStr(new Integer(arr[0])));
sb.append(getByteBinaryStr(new Integer(arr[1])));
sb.append(getByteBinaryStr(new Integer(arr[2])));
sb.append(getByteBinaryStr(new Integer(arr[3])));
String intStr = sb.toString();
// Integer.valueOf處理負數會有問題
if (intStr.charAt(0) == '1') {
char[] chars = intStr.toCharArray();
chars[0] = '0';
intStr = new String(chars);
return 0 - Integer.valueOf(intStr, 2).intValue();
}
return Integer.valueOf(intStr, 2).intValue();
}
public static boolean addWhiteIpAddress(String ip){
int ipInt = chgIpStrToInt(ip);
if (ipInt < 0) {
high.set(ipInt+Integer.MAX_VALUE+1);
} else {
low.set(ipInt);
}
return true;
}
public static boolean isWhiteIpAddress(String ip) {
int ipInt = chgIpStrToInt(ip);
if (ipInt < 0) {
return high.get(ipInt+Integer.MAX_VALUE+1);
} else {
return low.get(ipInt);
}
}
@Test
public void test() throws InterruptedException {
String ip1= "0.0.0.0";
addWhiteIpAddress(ip1);
Assert.isTrue(isWhiteIpAddress(ip1));
String ip2= "127.0.0.1";
addWhiteIpAddress(ip2);
Assert.isTrue(isWhiteIpAddress(ip2));
String ip3= "255.255.255.255";
addWhiteIpAddress(ip3);
Assert.isTrue(isWhiteIpAddress(ip3));
String ip4= "128.0.0.0";
addWhiteIpAddress(ip4);
Assert.isTrue(isWhiteIpAddress(ip4));
Assert.isTrue(!isWhiteIpAddress("0.0.0.1"));
Assert.isTrue(!isWhiteIpAddress("1.1.1.1"));
Assert.isTrue(!isWhiteIpAddress("001.001.001.001"));
}
}