java实现环形数组

Posted wen-pan

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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());
        
    

以上是关于java实现环形数组的主要内容,如果未能解决你的问题,请参考以下文章

java实现环形数组

java实现环形数组

超强解析环形队列,简析单项,双向队列及基础功能实现---风之java

Java数据结构 -- 环形队列 & 数组模拟环形队列

查找环形数组的和最大的连续子数组

java实现环形队列