IPV6离线地址库Java版(极致性能,无内存分配,申请了专利)

IPV6版本的技术含量就比V4版本的高了很多,当时还申请了技术专利的,也一样贴给大家看看。

IPV6离线数据包构建

java 复制代码
package com.bob.common.utils.ip.v6;

import java.io.ByteArrayInputStream;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.alibaba.fastjson.JSON;

import com.bob.common.utils.ip.IpGeoMetaInfo;
import com.google.common.collect.Lists;
import org.apache.commons.codec.digest.DigestUtils;

/**
 * @author jjb318191
 * @create 2020-04-30 9:40
 */
public class Ipv6IndexProcessor {

    private byte[] data;

    /**
     * 每个长度字节的ip数量
     */
    private int[] blockIpNum;

    /**
     * 相同ip前缀的ip起始序号, 在相同长度内部的序号
     */
    private List<int[]> ipBlockStartIndex;

    /**
     * 相同ip前缀的ip结束序号, 在相同长度内部的序号
     */
    private List<int[]> ipBlockEndIndex;

    /**
     * 不同字节长度的ip信息,ip差值,内容位置
     */
    private List<List<Byte>> diffLengthIpInfos;

    /**
     * 内容
     */
    private List<String> contents;

    /**
     * 内容的位置和长度
     * key: 内容
     * value: {@link #contents}位置
     */
    private Map<String, Integer> contentMapping;

    private int contentLength;

    /**
     * 内容字符编码
     */
    private static final String CONTENT_CHAR_SET = "utf-8";

    /**
     * 元数据的字节长度
     */
    private static final int META_INFO_BYTE_LENGTH = 1024;

    /**
     * IP前缀分片个数
     */
    private static final int IP_FIRST_SEGMENT_SIZE = 256 * 256;

    public boolean init() {
        this.blockIpNum = new int[16];

        this.ipBlockStartIndex = new ArrayList<>(16);
        this.ipBlockEndIndex = new ArrayList<>(16);
        this.diffLengthIpInfos = new ArrayList(16);

        for (int i = 0; i < 16; i++) {
            int[] ints1 = new int[IP_FIRST_SEGMENT_SIZE];
            Arrays.fill(ints1, -1);
            ipBlockStartIndex.add(ints1);
            int[] ints2 = new int[IP_FIRST_SEGMENT_SIZE];
            Arrays.fill(ints2, -1);
            ipBlockEndIndex.add(ints2);
            diffLengthIpInfos.add(new ArrayList<>());
        }

        this.contents = new ArrayList<>(1 << 14);
        this.contentMapping = new HashMap<>(1 << 14);
        return true;
    }

    public void index(String s, String e, String content) {
        byte[] startIp = new BigInteger(s).toByteArray();
        byte[] endIp = new BigInteger(e).toByteArray();
        int length = endIp.length;
        // 补充startIp长度
        if (startIp.length != length) {
            int l = startIp.length;
            byte[] bytes = new byte[length];
            System.arraycopy(startIp, 0, bytes, length - l, l);
            startIp = bytes;
        }
        // 有效字节长度
        int effectiveBytesLength = endIp[0] != 0 ? length : calculateEffectiveLength(endIp);
        int index = effectiveBytesLength - 1;
        // 有效首字节位置
        // ip首段, 比如 1.2.6.5, 首段是 1.2, 位置 1*256+2 = 258
        int startIndex = length - effectiveBytesLength;

        int segment;
        if (startIndex == length - 1) {  // 兼容一个字节的ip
            segment = endIp[length - 1] & 0xff;
        } else {
            segment = (endIp[startIndex] & 0xff) * 256 + (endIp[startIndex + 1] & 0xff);
        }

        // 获取当前ip的插入顺序, 比如是7个长度字节的地100个ip
        int order = blockIpNum[index];
        // 初始化起始序号, 当前字节长度内的序号
        int[] startBlock = ipBlockStartIndex.get(index);
        if (startBlock[segment] == -1) {
            startBlock[segment] = order;
        }

        // 初始化结束序号, 当前字节长度内的序号
        int[] endBlock = ipBlockEndIndex.get(index);
        endBlock[segment] = order;

        // 如果ip在256*256之前
        if (effectiveBytesLength <= 2) {
            // 起始ip的段序号
            int startSegment = effectiveBytesLength == 1 ? startIp[length - 1] & 0xff
                : (startIp[length - 2] & 0xff) * 256 + (startIp[length - 1] & 0xff);

            for (int i = startSegment; i < segment; i++) {
                startBlock[i] = order;
                endBlock[i] = order;
            }
        }

        // 插入序号自增
        blockIpNum[index] = ++order;

        int contentOffset;
        // 如果这个内容写过了
        if (contentMapping.containsKey(content)) {
            // 写内容索引的offset
            contentOffset = contentMapping.get(content);
        } else {  // 新的内容
            this.contents.add(content);
            contentOffset = contents.size() - 1;
            contentMapping.put(content, contentOffset);
        }
        List<Byte> segmentInfo = diffLengthIpInfos.get(index);
        if (startIndex + 2 < length) {
            // 写ip后缀
            listAddArray(segmentInfo, Arrays.copyOfRange(endIp, startIndex + 2, length));
            // 写ip前缀
            listAddArray(segmentInfo, Arrays.copyOfRange(startIp, startIndex + 2, length));
        }
        // 写内容位置, 默认3个字节
        listAddArray(segmentInfo, to3ByteArray(contentOffset));
    }

    public void finish() throws Exception {
        // 如果内容数小于 256*256,则用2个字节存储
        if (contents.size() < 1 << 16) {
            reduceContentIndexByteLength();
        }

        //4个字节的内容总数 + 16种字节长度 * 4 (每种长度下的ip数)
        // +  16*256*256*8字节 + ...
        int contentOffset = META_INFO_BYTE_LENGTH + 4 + 16 * 4 + 16 * IP_FIRST_SEGMENT_SIZE * 8
            + calculateTotalListSize(diffLengthIpInfos);

        this.data = new byte[contentOffset + sumIpNum() * 50];

        // 4字节内容数
        writeInt(this.data, META_INFO_BYTE_LENGTH, contents.size());
        // 写入字节长度的ip条数
        for (int i = 0; i < blockIpNum.length; i++) {
            writeInt(this.data, META_INFO_BYTE_LENGTH + 4 + i * 4, blockIpNum[i]);
        }
        // 把多个字节长度的ip的前缀序号都写入数组
        int nextOffset = META_INFO_BYTE_LENGTH + 4 + 16 * 4;
        for (int x = 0; x < 16; x++) {
            // 写入每个IP前缀的起始ip序号和结束ip序号
            for (int i = 0; i < IP_FIRST_SEGMENT_SIZE; i++) {
                writeInt(data, nextOffset, ipBlockStartIndex.get(x)[i]);
                writeInt(data, nextOffset + 4, ipBlockEndIndex.get(x)[i]);
                nextOffset += 8;
            }
        }
        // 写ip后缀,ip差值,内容位置
        for (int i = 0; i < diffLengthIpInfos.size(); i++) {
            List<Byte> list = diffLengthIpInfos.get(i);
            arrayAddList(data, list, nextOffset);
            // 比如6个字节有7000条ip, 写入ip条数
            nextOffset += list.size();
        }
        // 写内容
        for (String content : contents) {
            byte[] bytes = content.getBytes(CONTENT_CHAR_SET);
            writeInt(data, nextOffset, bytes.length);
            nextOffset += 4;
            System.arraycopy(bytes, 0, data, nextOffset, bytes.length);
            nextOffset += bytes.length;
        }

        this.contentLength = nextOffset;

        // 计算MD5
        IpGeoMetaInfo meta = new IpGeoMetaInfo();
        meta.setIpversion(6);
        meta.setChecksum(DigestUtils.md5Hex(new ByteArrayInputStream(data, 0, nextOffset)));
        String json = JSON.toJSONString(meta);
        byte[] metaData = json.getBytes(CONTENT_CHAR_SET);
        int length = metaData.length;
        // 验证meta长度
        if (4 + length > META_INFO_BYTE_LENGTH) {
            throw new IllegalStateException(
                String.format("meta info length greater than %d , meta info json: %s", META_INFO_BYTE_LENGTH, json));
        }
        // 写入meta信息
        writeInt(data, 0, length);
        System.arraycopy(metaData, 0, data, 4, length);
    }

    private void writeInt(byte[] data, int offset, int i) {
        writeVLong4(data, offset, i);
    }

    /**
     * 计算字节的有效长度
     *
     * @param bytes
     * @return
     */
    private int calculateEffectiveLength(byte[] bytes) {
        for (int i = 0; i < bytes.length; i++) {
            if (bytes[i] != 0) {
                return bytes.length - i;
            }
        }
        return bytes.length;
    }

    /**
     * 缩减内容位置所占字节数
     */
    private void reduceContentIndexByteLength() {
        for (int i = 0; i < 16; i++) {
            List<Byte> bytes = diffLengthIpInfos.get(i);
            if (bytes.isEmpty()) {
                continue;
            }
            int perLength = i < 2 ? 3 : 2 * (i + 1 - 2) + 3;
            List<Byte> newBytes = new ArrayList<>(bytes.size());
            List<List<Byte>> partitions = Lists.partition(bytes, perLength);
            // 缩减内容字节数, 由3字节减为2字节
            for (List<Byte> bts : partitions) {
                for (int k = 0; k < bts.size(); k++) {
                    if (k != perLength - 2 - 1) {
                        newBytes.add(bts.get(k));
                    }
                }
            }
            diffLengthIpInfos.remove(i);
            diffLengthIpInfos.add(i, newBytes);
        }
    }

    /**
     * 写入4个字节的long
     *
     * @param data   字节数据
     * @param offset 偏移量
     * @param i      值
     */
    private void writeVLong4(byte[] data, int offset, long i) {
        data[offset++] = (byte)(i >> 24);
        data[offset++] = (byte)(i >> 16);
        data[offset++] = (byte)(i >> 8);
        data[offset] = (byte)i;
    }

    private void listAddArray(List<Byte> list, byte[] array) {
        for (byte t : array) {
            list.add(t);
        }
    }

    private void arrayAddList(byte[] array, List<Byte> list, int offset) {
        for (byte b : list) {
            array[offset++] = b;
        }
    }

    /**
     * 用3个字节存储int
     *
     * @param n
     * @return
     */
    private byte[] to3ByteArray(int n) {
        byte[] b = new byte[3];
        b[2] = (byte)(n & 0xff);
        b[1] = (byte)(n >> 8 & 0xff);
        b[0] = (byte)(n >> 16 & 0xff);
        return b;
    }

    private int calculateTotalListSize(List<List<Byte>> dataList) {
        int length = 0;
        for (List<Byte> list : dataList) {
            length += list.size();
        }
        return length;
    }

    private int sumIpNum() {
        int sum = 0;
        for (int i : blockIpNum) {
            sum += i;
        }
        return sum;
    }

    public byte[] getData() {
        return this.data;
    }

    public int getContentOffset() {
        return this.contentLength;
    }

}

IPV6数据包加载及检索

java 复制代码
package com.bob.common.utils.ip.v6;

import java.io.File;
import java.util.Arrays;
import java.util.HashSet;

import com.bob.common.utils.ip.IpGeoMetaInfo;
import org.apache.commons.io.FileUtils;

import static com.bob.common.utils.ip.DigestUtils.rawToJSON;
import static com.bob.common.utils.ip.v4.IPv4BitCalc.readInt;

/**
 * @author jjb318191
 * @create 2020-04-30 9:44
 */
public class Ipv6SearchProcessor {

    /**
     * v6 a段索引区占用字节数
     */
    private static final int IP_FIRST_SEGMENT_SIZE = 256 * 256;

    /**
     * ipBlockStartIndex[1][128.100] = 1000 : 表示以128.100开头第一个IP段的序号是1000
     */
    private int[][] ipBlockStartIndex;

    /**
     * ipBlockStartIndex[128.100] = 1200 : 表示以128.100开始最后一个IP段的序号是1200
     */
    private int[][] ipBlockEndIndex;

    /**
     * 不同字节长度的ip信息,ip差值,内容位置
     */
    private byte[][] diffLengthIpInfos;

    private String[] contentArray;

    private ThreadLocal<byte[][]> cacheBytePool = new ThreadLocal<>();

    /**
     * 内容位置字节长度
     */
    private int contentIndexByteLength;

    /**
     * 元数据的字节长度
     */
    private static final int META_INFO_BYTE_LENGTH = 1024;

    /**
     * 内容字符编码
     */
    private static final String CONTENT_CHAR_SET = "utf-8";

    public final boolean load(File file, IpGeoMetaInfo metaInfo) throws Exception {
        byte[] data = FileUtils.readFileToByteArray(file);
        // 唯一性的内容条数
        this.contentArray = new String[readInt(data, META_INFO_BYTE_LENGTH)];
        // 内容位置占字节数, 内容条数一般3字节足够了
        this.contentIndexByteLength = contentArray.length < 1 << 16 ? 2 : 3;

        // 有多少种不同长度就生成多少个字节数组
        this.diffLengthIpInfos = new byte[16][];
        this.ipBlockStartIndex = new int[16][];
        this.ipBlockEndIndex = new int[16][];

        int offset = META_INFO_BYTE_LENGTH + 4 + 16 * 4;

        for (int i = 0; i < 16; i++) {
            // 此字节长度下的ip数
            int num = readInt(data, META_INFO_BYTE_LENGTH + 4 + i * 4);
            if (num > 0) {
                // 2字节的ip: 就存储内容序号
                // 6字节的ip : 4字节后缀 + 4字节的ip差值 + 2/3字节的内容序号
                // 16字节的ip : 14字节后缀 + 14字节的ip差值 + 2/3字节的内容序号
                int length = i < 2 ? contentIndexByteLength * num : (2 * (i + 1 - 2) + contentIndexByteLength) * num;
                this.diffLengthIpInfos[i] = new byte[length];
                ipBlockStartIndex[i] = new int[IP_FIRST_SEGMENT_SIZE];
                ipBlockEndIndex[i] = new int[IP_FIRST_SEGMENT_SIZE];
            }
        }
        // 加载每个ip段的起始序号和结束序号
        for (int i = 0; i < 16; i++) {
            // 此长度的ip不存在
            if (diffLengthIpInfos[i] == null) {
                offset += 8 * IP_FIRST_SEGMENT_SIZE;
                continue;
            }
            for (int j = 0; j < IP_FIRST_SEGMENT_SIZE; j++) {
                // 0:0:0:0:0:12:2e:3000  :  存储 6字节长度的ip 的 12 开头的ip, 他们的序号在 100~1000之间, 是6字节内部的序号
                ipBlockStartIndex[i][j] = readInt(data, offset);
                ipBlockEndIndex[i][j] = readInt(data, offset + 4);
                offset += 8;
            }
        }
        // 加载每个ip段的数据信息,ip后段, ip差值, 内容位置
        for (int i = 0; i < diffLengthIpInfos.length; i++) {
            byte[] bytes = diffLengthIpInfos[i];
            if (bytes != null) {
                System.arraycopy(data, offset, bytes, 0, bytes.length);
                offset += bytes.length;
            }
        }

        // 加载内容
        for (int i = 0; i < contentArray.length; i++) {
            int length = readInt(data, offset);
            offset += 4;
            // 将所有字符串都取出来, 每个字符串都缓存好
            String rawContent = new String(Arrays.copyOfRange(data, offset, offset + length), CONTENT_CHAR_SET);
            String content = rawToJSON(rawContent, Arrays.asList(metaInfo.getStoredProperties()),
                new HashSet<>(Arrays.asList(metaInfo.getLoadProperties())));
            contentArray[i] = content;
            offset += length;
        }
        return true;
    }

    public final String search(String ip) {
        // 将ip转换成字节数组
        byte[] iPv6Address = toByteArray(ip);
        // 当前字节长度在所有长度中的序号, 从0开始
        int segmentIndex = iPv6Address.length - 1;
        // 当前长度下的ip没有
        if (ipBlockStartIndex[segmentIndex] == null) {
            return null;
        }
        int prefixSegmentsInt;
        if (iPv6Address.length == 1) {  // 一个字节的ip
            prefixSegmentsInt = iPv6Address[0] & 0xff;
        } else {
            prefixSegmentsInt = ((iPv6Address[0] & 0xff) << 8) + (iPv6Address[1] & 0xff);
        }
        int start = ipBlockStartIndex[segmentIndex][prefixSegmentsInt];
        int end = ipBlockEndIndex[segmentIndex][prefixSegmentsInt];

        if (start < 0) {
            return null;
        }
        int cur = binarySearch(start, end, iPv6Address);
        // ip地址未分配
        if (cur == -1) {
            return null;
        }
        return contentArray[cur];
    }

    /**
     * 定位ip序号
     *
     * @param low  ip段的起始序号
     * @param high ip段的结束序号
     * @param ip   IP字节数组, 16字节
     * @return int
     */
    private int binarySearch(int low, int high, byte[] ip) {
        // 存储当前长度的ip信息的数组
        byte[] data = diffLengthIpInfos[ip.length - 1];

        // 如果是一字节或者二字节的ip
        if (ip.length <= 2) {
            return readVInt(data, high * contentIndexByteLength, contentIndexByteLength);
        }
        // 每份ip信息的字节长度
        int perLength = ((ip.length - 2) << 1) + contentIndexByteLength;

        int result = -2, order = high;
        while (low <= high) {
            int mid = (low + high) >> 1;
            result = compareByteArray(data, mid * perLength, ip, 2);
            // 如果结束的ip就是当前ip
            if (result == 0) {
                order = mid;
                break;
            } else if (result > 0) {
                order = mid;
                if (mid == 0) {
                    break;
                }
                high = mid - 1;
            } else {
                low = mid + 1;
            }
        }
        int offset = order * perLength;
        if (result != 0 && !compareByStartIp(ip, 2, data, offset + ip.length - 2)) {
            return -1;
        }
        return readVInt(data, offset + ((ip.length - 2) << 1), contentIndexByteLength);
    }

    /**
     * 读取3个字节凑成Int
     *
     * @param data
     * @param p
     * @return
     */
    private int readVInt(byte[] data, int p, int length) {
        if (length == 2) {
            return (int)(((data[p++] << 8) & 0xFF00L) | (data[p++] & 0xFFL));
        } else {
            return (int)(((data[p++] << 16) & 0xFF0000L) | ((data[p++] << 8) & 0xFF00L) | (data[p++] & 0xFFL));
        }
    }

    public byte[] toByteArray(String address) {

        if (address == null) {
            throw new IllegalArgumentException("Attempted to parse null address");
        }
        // 验证长度
        int first = 0, last = address.length();
        if (address.length() < 2) {
            throw new IllegalArgumentException(
                String.format("Invalid length - the string %s is too short to be an IPv6 address", address));
        }
        int length = last - first;
        if (length > 39) { // 32个数字 + 7个":"
            throw new IllegalArgumentException(
                String.format("Invalid length - the string %s is too long to be an IPv6 address. Length: %d",
                    address, address.length()));
        }
        int partIndex = 0, partHexDigitCount = 0, afterDoubleSemicolonIndex = last + 2;
        // 获得缓存的字节数组
        byte[][] pool = cacheBytePool.get();
        if (pool == null) {
            initCacheBytePool();
            pool = cacheBytePool.get();
        }
        byte[] data = null;
        byte v;
        int k = 0, s = 0, x = 1, left = 0;
        for (int i = first; i < last; i++) {
            char c = address.charAt(i);
            if (isHexDigit(c)) {
                if (c == '0' && data == null) {
                    continue;
                }
                // 定位当前段有几个数字, 且当前数字的序号:1 2 3 4 中的一个
                if (partHexDigitCount == 0) {
                    int y = i + 1;
                    until:
                    {
                        if (y >= last || address.charAt(y++) == ':') {
                            break until;
                        } else {
                            x++;
                        }
                        if (y >= last || address.charAt(y++) == ':') {
                            break until;
                        } else {
                            x++;
                        }
                        if (y >= last || address.charAt(y++) == ':') {
                            break until;
                        } else {
                            x++;
                        }
                    }
                    // ipv6分8段, 每一段都会写入两个字节
                    left = left + 2;
                    // 当前数字的序号
                    partHexDigitCount = 4 - x;
                }
                // 初始化字节数组
                if (data == null) {
                    int dataLength = ((7 - partIndex) << 1) + (x > 2 ? 2 : 1);
                    data = pool[dataLength - 1];
                }
                // 每段数字不能超过4个
                if (++partHexDigitCount > 4) {
                    throw new IllegalArgumentException(String.format("Ipv6 address %s parts must contain no more than 16 bits (4 hex digits)", address));
                }
                // 当前数字的字节值
                v = c >= 97 ? (byte)((c - 87) & 0xff) : (byte)((c - 48) & 0xff);
                // 0:0:12:c::12:226 将'c'的前一字节填充0
                if (x == 1 && partHexDigitCount == 4 && k > 0) {
                    data[k] = 0;
                    s++;
                    k++;
                }
                // 0:0:12:2c::12:226 将'2c'的前一字节填充0
                if (x == 2 && partHexDigitCount == 3 && k > 0) {
                    data[k] = 0;
                    s++;
                    k++;
                }
                // 根据数字位置填充字节的前一半
                if (partHexDigitCount == 1 || partHexDigitCount == 3) {
                    data[k] = (byte)(v << 4);
                }
                // 填充字节的后一半
                if (partHexDigitCount == 2 || partHexDigitCount == 4) {
                    // 如果此段只有一个数字,或者有3个数字,当前是第一个数字
                    if (x == 1 || (partHexDigitCount == 2 && x == 3)) {
                        data[k] = 0;
                    }
                    data[k] = (byte)(data[k] | v);
                    s++;
                    k++;
                }
            } else {
                if (c == ':') {
                    if (data != null) {
                        // 下一次写的字节位置
                        if (k != 1) {
                            k += 2 - s;
                        }
                    }
                    s = 0;
                    x = 1;
                    partIndex++;
                    partHexDigitCount = 0;
                    // 如果存在连续的两个":", 即 "::"
                    if (i < last - 1 && address.charAt(i + 1) == ':') {
                        // 在两个: 之后的下一个数字的位置
                        afterDoubleSemicolonIndex = i + 2;
                        break;
                    }
                    continue;
                }
                throw new IllegalArgumentException(
                    String.format("Ipv6 address %s illegal character: %c at index %d", address, c, i));
            }
        }

        // 从末尾倒叙向前遍历 直至 ::
        int lastFilledPartIndex = partIndex - 1, l = data.length - 1, right = l;
        partIndex = 7;
        for (int i = last - 1; i >= afterDoubleSemicolonIndex; i--) {
            char c = address.charAt(i);
            if (isHexDigit(c)) {
                if (partIndex <= lastFilledPartIndex) {
                    throw new IllegalArgumentException(
                        String.format("Ipv6 address %s too many parts. Expected 8 parts", address));
                }
                if (++partHexDigitCount > 4) {
                    throw new IllegalArgumentException(
                        String
                            .format("Ipv6 address %s parts must contain no more than 16 bits (4 hex digits)", address));
                }
                // 当前数字的字节值
                v = c >= 97 ? (byte)((c - 87) & 0xff) : (byte)((c - 48) & 0xff);
                // 根据数字位置填充字节的后一半
                if (partHexDigitCount == 1 || partHexDigitCount == 3) {
                    right--;
                    data[l] = v;
                }
                // 填充字节的前一半
                if (partHexDigitCount == 2 || partHexDigitCount == 4) {
                    data[l] = (byte)(data[l] | v << 4);
                    s++;
                    l--;
                }
            } else {
                if (c == ':') {
                    l -= 2 - s;
                    s = 0;
                    partIndex--;
                    partHexDigitCount = 0;
                    continue;
                }
                throw new IllegalArgumentException(
                    String.format("Ipv6 address %s illegal character: %c at index %d", address, c, i));
            }
        }
        // 填充 "::" 代表的空字节, 置为0, 因为字节数组是缓存重用的, 需要复位
        if (left != right) {
            for (int i = left; i <= right; i++) {
                data[i] = 0;
            }
        }
        return data;
    }

    private static boolean isHexDigit(char c) {
        return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f');
    }

    /**
     * 初始化ThreadLocal的二维字节数组
     */
    private void initCacheBytePool() {
        //缓存待用字节数组
        byte[][] cacheBytes = new byte[16][];
        for (int i = 0; i < cacheBytes.length; i++) {
            cacheBytes[i] = new byte[i + 1];
        }
        cacheBytePool.set(cacheBytes);
    }

    /**
     * 比较两个字节数字的大小,逐位比较
     *
     * @param array1 数组1
     * @param index1 开始的比较位
     * @param array2 数组2
     * @param index2 开始的比较位
     * @return int
     */
    private int compareByteArray(byte[] array1, int index1, byte[] array2, int index2) {
        int k = 0, result = 0;
        for (int i = index1; i < index1 + array2.length; i++) {
            if (array1[i] != array2[index2 + k]) {
                return (array1[i] & 0xff) - (array2[index2 + k] & 0xff);
            }
            // end ip 和当前检索ip相同
            if (index2 + ++k == array2.length) {
                return 0;
            }
        }
        return result;
    }

    /**
     * 和起始ip比较
     *
     * @param ip         检索ip
     * @param index
     * @param data       startIp后缀
     * @param startIndex
     * @return
     */
    private boolean compareByStartIp(byte[] ip, int index, byte[] data, int startIndex) {
        int k = 0;
        for (int i = index; i < ip.length; i++) {
            int x1 = ip[i] & 0xff, x2 = data[startIndex + k] & 0xff;
            if (x1 < x2) {
                return false;
            } else if (x1 > x2) {
                return true;
            }
            k++;
        }
        return true;
    }

}

工具类

工具类在V4版文章里,这里不重复贴了

相关推荐
java排坑日记3 小时前
poi-tl+kkviewfile实现生成pdf业务报告
java·pdf·word
V+zmm101343 小时前
校园约拍微信小程序设计与实现ssm+论文源码调试讲解
java·微信小程序·小程序·毕业设计·ssm
猿来入此小猿3 小时前
基于SpringBoot小说平台系统功能实现四
java·spring boot·毕业设计·毕业源码·在线小说阅读·在线小说平台·免费学习:猿来入此
Cosmoshhhyyy4 小时前
LeetCode:2274. 不含特殊楼层的最大连续楼层数(排序 Java)
java·算法·leetcode
Dolphin_Home4 小时前
Spring Boot 多环境配置与切换
java·spring boot·后端
我本是机械人4 小时前
MVCC实现原理及其作用
java·数据结构·数据库·后端·mysql·算法
路在脚下@5 小时前
SpringMVC的消息转换器
java·spring boot
sin22015 小时前
springmvc--请求参数的绑定
java·mvc
装不满的克莱因瓶5 小时前
【Redis经典面试题十】热key与大key的问题如何解决?
java·数据库·redis·缓存·面试·面试题·key
武昌库里写JAVA5 小时前
Redis奇幻之旅(四)4. Redis Cluster
java·开发语言·spring boot·学习·课程设计