值得分析的 IPUtils

Posted by 孙继峰 on April 7, 2020
public class IpUtils {
    private static final String XFF = "x-forwarded-for";
    private static final String RANGE = "(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)";
    private static final Pattern PATTERN = Pattern.compile("^(?:" + RANGE + "\\.){3}" + RANGE + "$");

    public static String longToIpv4(long longIp) {
        int octet3 = (int) ((longIp >> 24) % 256);
        int octet2 = (int) ((longIp >> 16) % 256);
        int octet1 = (int) ((longIp >> 8) % 256);
        int octet0 = (int) ((longIp) % 256);
        return octet3 + "." + octet2 + "." + octet1 + "." + octet0;
    }

    public static long ipv4ToLong(String ip) {
        String[] octets = ip.split("\\.");
        return (Long.parseLong(octets[0]) << 24) + (Integer.parseInt(octets[1]) << 16)
                + (Integer.parseInt(octets[2]) << 8) + Integer.parseInt(octets[3]);
    }

    public static boolean isIpv4Private(String ip) {
        long longIp = ipv4ToLong(ip);
        return (longIp >= ipv4ToLong("10.0.0.0") && longIp <= ipv4ToLong("10.255.255.255"))
                || (longIp >= ipv4ToLong("172.16.0.0") && longIp <= ipv4ToLong("172.31.255.255"))
                || longIp >= ipv4ToLong("192.168.0.0") && longIp <= ipv4ToLong("192.168.255.255");
    }

    public static boolean isIpv4(String ip) {
        return PATTERN.matcher(ip).matches();
    }
}


正则表达式预编译

    private static final String RANGE = "(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)";
    private static final Pattern PATTERN = Pattern.compile("^(?:" + RANGE + "\\.){3}" + RANGE + "$");

把 Pattern 作为一个常量, 类加载阶段初始化它

错误用例:
    public boolean isIpv4(String str) {
        String range = "(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)"
        Pattern pattern = Pattern.compile("^(?:" + range + "\\.){3}" + range + "$")
        return pattern.matcher(str).matches();
    }

原因: 每次进入方法都要编译一次正则表达式, 造成不必要的消耗

算法

    public static String longToIpv4(long longIp) {
        int octet3 = (int) ((longIp >> 24) % 256);
        int octet2 = (int) ((longIp >> 16) % 256);
        int octet1 = (int) ((longIp >> 8) % 256);
        int octet0 = (int) ((longIp) % 256);
        return octet3 + "." + octet2 + "." + octet1 + "." + octet0;
    }
    
    public static long ipv4ToLong(String ip) {
        String[] octets = ip.split("\\.");
        return (Long.parseLong(octets[0]) << 24) + (Integer.parseInt(octets[1]) << 16)
                + (Integer.parseInt(octets[2]) << 8) + Integer.parseInt(octets[3]);
    }

 刚一入眼, 我以为是 LeetCode 的 restore-ip-addresses, 我还在感叹这么几行代码就能实现, 仔细一看才发现 ipv4ToLonglongToIpv4 这两个方法其实是按照一个自定义的标准去实现的. — 开始分析:  long 类型在 Java 中占 64 位, 方法中只用了低 32 位, 将低 32 位分成 4 个 8 位组

1 个 8 位组刚好能够存储 0-255 4 个 8 位组就能够存储一个 ip 地址

long 转 IPV4 时通过位运算符取出每个 8 位组, 最后拼装成一个 IP 地址. 其中(longIp >> 24) % 256 还可以写作 (longIp >> 24) & 255 IPV4 转 long 时把 ip 分成了 4 份, 每份左移后累加, 最后得到 IP.