如何通过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());
}
}
}