java实现环形数组

如何通过java优雅的实现一个环形数组?下面提供2种实现方式

  • SimpleCircularArray是环形数组简单实现
  • CircularArrayHolder代码实现参考的是com.netflix.hystrix.utilHystrixRollingNumber

实现方式一、SimpleCircularArray

bucket

/**
 * 桶
 *
 * @author wenpanfeng 2022/07/30 11:17
 */
public class Bucket {
    /**
     * 桶名称
     */
    String name;
    
    public Bucket() {
    }

    public Bucket(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

SimpleCircularArray

/**
 * 简单的环形数组实现,非线程安全,若需保证线程安全只需要在使用时对addLast和clear方法加锁即可
 *
 * @author wenpanfeng 2022/07/30 13:34
 */
public class SimpleCircularArray implements Iterable<Bucket> {

    /**
     * 环形数组,数组里是一个个的桶,桶内需要放什么数据可自己决定
     */
    private final AtomicReferenceArray<Bucket> circularArray;
    /**
     * 头指针
     */
    private int head;
    /**
     * 尾指针
     */
    private int tail;
    /**
     * 当前数组内元素的个数
     */
    private int size;
    /**
     * 环形数组容量
     */
    private final int capacity;

    public SimpleCircularArray(int capacity) {
        circularArray = new AtomicReferenceArray<>(capacity);
        head = 0;
        tail = 0;
        this.capacity = capacity;
    }

    public SimpleCircularArray(AtomicReferenceArray<Bucket> circularArray) {
        this.circularArray = circularArray;
        head = 0;
        tail = 0;
        capacity = circularArray.length();
    }

    public void addLast(Bucket bucket) {
        // 已经到达最后一个
        if (size == capacity) {
            if (head == capacity - 1) {
                head = 0;
            } else {
                head = head + 1;
            }
            if (tail == capacity) {
                circularArray.set(0, bucket);
                tail = 1;
            } else {
                circularArray.set(tail, bucket);
                tail = tail + 1;
            }
        } else {
            // 环形数组中元素个数还未达到capacity,则只移动tail
            circularArray.set(tail, bucket);
            tail = tail + 1;
            size++;
        }
    }

    /**
     * 清除环形数组
     */
    public void clear() {
        size = 0;
        head = 0;
        tail = 0;
    }

    /**
     * 在内部数组的副本上返回一个迭代器,以便迭代器不会因同时添加删除的存储桶而失败。
     */
    @Override
    public Iterator<Bucket> iterator() {
        // 获取环形数组里的所有元素,这里获取到的是环形数组里的元素的副本
        return Collections.unmodifiableList(Arrays.asList(getArray())).iterator();
    }

    /**
     * 获取环形数组中所有元素
     */
    protected Bucket[] getArray() {
        List<Bucket> array = new ArrayList<>();
        // 依次获取环形数组内部所有元素并加入到新的list
        for (int i = 0; i < size; i++) {
            array.add(circularArray.get(convert(i)));
        }
        return array.toArray(new Bucket[0]);
    }

    /**
     * convert() 方法采用逻辑索引(好像 head 始终为 0)并计算 elementData 内的索引
     */
    private int convert(int index) {
        return (index + head) % (capacity);
    }

}

实现方式二、CircularArrayHolder

/**
 * 环形数组管理器,通过该holder来方便的操作环形数组
 *
 * @author wenpanfeng 2022/07/30 11:12
 */
public class CircularArrayHolder implements Iterable<Bucket> {

    /**
     * 持有一个环形数组的引用,以便于可以通过该引用方便的访问环形数组
     */
    private final AtomicReference<CircularArray> circularArray;
    /**
     * 固定值,一旦创建就不会改变, 预留一个空间,作为后续向环形数组增减和删除的支持,
     * 长度始终为:桶的数量 + 1,比如:如果环形数组里有10个桶,那么该值就是11
     * <p>
     */
    private final int dataLength;
    /**
     * 桶的数量
     */
    private final int numBuckets;

    /**
     * 构造函数
     */
    public CircularArrayHolder(int size) {
        // + 1 as extra room for the add/remove;
        AtomicReferenceArray<Bucket> buckets = new AtomicReferenceArray<>(size + 1);
        // state持有该环形数组的引用
        circularArray = new AtomicReference<>(new CircularArray(buckets, 0, 0));
        dataLength = buckets.length();
        numBuckets = size;
    }

    /**
     * 清除环形数组里的所有元素(线程安全)
     */
    public void clear() {
        while (true) {
            // 获取到环形数组的引用
            CircularArray currentCircularArray = circularArray.get();
            // 调用环形数组的clear方法,此时会返回环形数组新的引用
            CircularArray newCircularArray = currentCircularArray.clear();
            // 使用新的引用替换旧的引用
            if (circularArray.compareAndSet(currentCircularArray, newCircularArray)) {
                // 如果cas替换成功则退出,不然则进行下一次尝试
                return;
            }
        }
    }

    /**
     * 在内部数组的副本上返回一个迭代器,以便迭代器不会因同时添加删除的存储桶而失败。
     */
    @Override
    public Iterator<Bucket> iterator() {
        // 获取环形数组里的所有元素,这里获取到的是环形数组里的元素的副本
        return Collections.unmodifiableList(Arrays.asList(getArray())).iterator();
    }

    /**
     * 往环形数组尾部添加一个bucket(非线程安全)
     */
    public void addLast(Bucket bucket) {
        // 获取到环形数组的引用
        CircularArray currentCircularArray = circularArray.get();
        // 将元素添加到环形数组里,添加成功后会返回一个新的环形数组的引用
        CircularArray newCircularArray = currentCircularArray.addBucket(bucket);
        // 将circularArray重新指向最新的环形数组
        circularArray.compareAndSet(currentCircularArray, newCircularArray);
    }

    /**
     * 获取环形数组最后一个元素
     */
    public Bucket getLast() {
        return peekLast();
    }

    /**
     * 获取环形数组里的元素个数
     */
    public int size() {
        // 大小也可以每次计算为: return (tail + data.length() - head) % data.length();
        return circularArray.get().size;
    }

    /**
     * 获取环形数组最后一个元素
     */
    public Bucket peekLast() {
        return circularArray.get().tail();
    }

    /**
     * 获取环形数组中所有的元素
     */
    private Bucket[] getArray() {
        return circularArray.get().getArray();
    }

    /**
     * 私有内部类,不允许外部直接访问(环形数组实现类,适用于写多读少的场景)
     */
    private class CircularArray {

        /**
         * 环形数组,数组里是一个个的桶,桶内需要放什么数据可自己决定
         */
        protected final AtomicReferenceArray<Bucket> data;
        /**
         * 环形数组的大小(也就是环形数组中现有元素的个数)
         */
        protected final int size;
        /**
         * 数组头节点下标索引
         */
        protected final int tail;
        /**
         * 数组尾节点下标索引
         */
        protected final int head;

        /**
         * 构造方法
         */
        public CircularArray(AtomicReferenceArray<Bucket> data, int head, int tail) {
            this.data = data;
            this.head = head;
            this.tail = tail;
            // 计算size
            if (head == 0 && tail == 0) {
                size = 0;
            } else {
                size = (tail + dataLength - head) % dataLength;
            }
        }

        /**
         * 获取环形数组尾部元素
         */
        public Bucket tail() {
            // 桶内还没有元素时,返回null
            if (size == 0) {
                return null;
            } else {
                // we want to get the last item, so size()-1
                return data.get(convert(size - 1));
            }
        }

        /**
         * convert() 方法采用逻辑索引(好像 head 始终为 0)并计算 elementData 内的索引
         */
        private int convert(int index) {
            return (index + head) % dataLength;
        }

        /**
         * 获取环形数组中所有的元素
         */
        protected Bucket[] getArray() {
            List<Bucket> array = new ArrayList<>();
            // 依次获取环形数组内部所有元素并加入到新的list
            for (int i = 0; i < size; i++) {
                array.add(data.get(convert(i)));
            }
            return array.toArray(new Bucket[0]);
        }

        /**
         * 增加一个元素到环形数组尾部
         */
        private CircularArray incrementTail() {
            // 如果已经到达环形数组最大长度,则头尾指针一起移动
            if (size == numBuckets) {
                return new CircularArray(data, (head + 1) % dataLength, (tail + 1) % dataLength);
            } else {
                // 如果还没有到达环形数组最大容量,则increment only tail
                return new CircularArray(data, head, (tail + 1) % dataLength);
            }
        }

        /**
         * 清除环形数组,其实也就是新建一个CircularArray然后将头尾指针都指向0位置
         *
         * @return CircularArray
         * @author wenpanfeng 2022/7/28 21:31
         */
        public CircularArray clear() {
            return new CircularArray(new AtomicReferenceArray<>(dataLength), 0, 0);
        }

        /**
         * 添加一个桶到环形数组里
         */
        public CircularArray addBucket(Bucket bucket) {
            // 设置尾结点位置的值为bucket
            data.set(tail, bucket);
            // 尾部移动一个
            return incrementTail();
        }
    }

}

测试

public class Main {

    public static void main(String[] args) {
        
        System.out.println("=============================测试CircularArrayHolder=============================");
        CircularArrayHolder holder = new CircularArrayHolder(10);

        for (int i = 0; i < 20; i++) {
            holder.addLast(new Bucket(String.valueOf(i)));
        }

        for (Bucket next : holder) {
            System.out.println(next.getName());
        }

        holder.clear();
        System.out.println("==================================================");

        for (Bucket next : holder) {
            System.out.println(next.getName());
        }

        System.out.println("=============================测试SimpleCircularArray=============================");

        SimpleCircularArray simpleCircularArray = new SimpleCircularArray(10);

        for (int i = 0; i < 10; i++) {
            simpleCircularArray.addLast(new Bucket(String.valueOf(i)));
        }

        for (Bucket bucket : simpleCircularArray) {
            System.out.println(bucket.getName());
        }

        simpleCircularArray.clear();
        System.out.println("======================================");

        for (int i = 0; i < 5; i++) {
            simpleCircularArray.addLast(new Bucket(String.valueOf(i)));
        }

        for (Bucket bucket : simpleCircularArray) {
            System.out.println(bucket.getName());
        }

        simpleCircularArray.clear();
        System.out.println("======================================");

        for (int i = 0; i < 3; i++) {
            simpleCircularArray.addLast(new Bucket(String.valueOf(i)));
        }

        for (Bucket bucket : simpleCircularArray) {
            System.out.println(bucket.getName());
        }

        simpleCircularArray.clear();
        System.out.println("======================================");

        for (int i = 0; i < 500; i++) {
            simpleCircularArray.addLast(new Bucket(String.valueOf(i)));
        }

        for (Bucket bucket : simpleCircularArray) {
            System.out.println(bucket.getName());
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值