×

算法练习(45): 环形缓冲区(1.3.39)

96
算法之路
2017.11.17 23:59* 字数 920

本系列博客习题来自《算法(第四版)》,算是本人的读书笔记,如果有人在读这本书的,欢迎大家多多交流。为了方便讨论,本人新建了一个微信群(算法交流),想要加入的,请添加我的微信号:zhujinhui207407 谢谢。另外,本人的个人博客 http://www.kyson.cn 也在不停的更新中,欢迎一起讨论

算法(第4版)

知识点

  • 环形缓冲区

题目

1.3.39 环形缓冲区。环形缓冲区,又称为环形队列,是一种定长为 N 的先进先出的数据结构。它在进程间的异步数据传输或记录日志文件时十分有用。当缓冲区为空时,消费者会在数据存入缓冲区前等待;当缓冲区满时,生产者会等待数据存入缓冲区。为 RingBuffer 设计一份 API 并用(回环)数组将其实现。


1.3.39 Ring buffer. A ring buffer, or circular queue, is a FIFO data structure of a fixed size N. It is useful for transferring data between asynchronous processes or for storing log files. When the buffer is empty, the consumer waits until data is deposited; when the buffer is full, the producer waits to deposit data. Develop an API for a RingBuffer and an implementation that uses an array representation (with circular wrap-around).

分析

圆形缓冲区(circular buffer)
也称作圆形队列(circular queue),循环缓冲区(cyclic buffer),环形缓冲区(ring buffer),是一种用于表示一个固定尺寸、头尾相连的缓冲区的数据结构,适合缓存数据流。

生产者消费者问题
(英语:Producer-consumer problem),也称有限缓冲问题(英语:Bounded-buffer problem),是一个多线程同步问题的经典案例。该问题描述了共享固定大小缓冲区的两个线程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。 要解决该问题,就必须让生产者在缓冲区满时休眠(要么干脆就放弃数据),等到下次消费者消耗缓冲区中的数据的时候,生产者才能被唤醒,开始往缓冲区添加数据。同样,也可以让消费者在缓冲区空时进入休眠,等到生产者往缓冲区添加数据之后,再唤醒消费者。通常采用进程间通信的方法解决该问题,常用的方法有信号灯法[1]等。如果解决方法不够完善,则容易出现死锁的情况。出现死锁时,两个线程都会陷入休眠,等待对方唤醒自己。该问题也能被推广到多个生产者和消费者的情形。

回到这道题,网上的解法有很多,因为这道题可以做的很简单,也可以做的很复杂。这篇文章Java Ring Buffer大家可以借鉴一下,现在我来给出几个答案。

答案

API

返回类型 函数 备注
boolean reset() 重新设置
int size() 大小
boolean put(Item x) 存入缓存
Item take() 读取
public class RingBuffer<Item> {

    public Item[] a = null;

    public int writePos = 0;
    public int readPos = 0;
    public boolean flipped = false; // the flip marker

    public RingBuffer(int cap) {
        this.a = (Item[]) new Object[cap];
    }

    public void reset() {
        this.writePos = 0;
        this.readPos = 0;
        this.flipped = false;
    }

    public int size() {
        return a.length;
    }

    public int available() {
        if (!flipped) {
            return writePos - readPos;
        }
        return size() - readPos + writePos;
    }

    public int remainingCapacity() {
        if (!flipped) {
            return size() - writePos;
        }
        return readPos - writePos;
    }

    public boolean put(Item x) {
        if (!flipped) {
            if (writePos == size()) {
                writePos = 0;
                flipped = true;

                if (writePos < readPos) {
                    a[writePos++] = x;
                    return true;
                } else {
                    return false;
                }
            } else {
                a[writePos++] = x;
                return true;
            }
        } else {
            if (writePos < readPos) {
                a[writePos++] = x;
                return true;
            } else {
                return false;
            }
        }
    }

    public Item take() {
        if (!flipped) {
            if (readPos < writePos) {
                return a[readPos++];
            } else {
                return null;
            }
        } else {
            if (readPos == size()) {
                readPos = 0;
                flipped = false;

                if (readPos < writePos) {
                    return a[readPos++];
                } else {
                    return null;
                }
            } else {
                return a[readPos++];
            }
        }
    }
}

这个方案中有个标记为 flipped,用于标记是否已经被读取过一次。

测试用例

public static void main(String[] args){
    int capacity = 10;
    RingBuffer<String> ringBuffer = new RingBuffer<String>(capacity);
    
    /*******************测试用例1*************************/
    for (int i = 0; i < capacity; i++) {
        String inputItem = i+"";
        boolean putSuccess = ringBuffer.put(inputItem);
        System.out.println(putSuccess ? "插入" + inputItem + "成功" : "插入" + inputItem + "失败" );
    }
    
    /*******************测试用例2*************************/
    for (int i = 0; i < capacity + 1; i++) {
        
        if (i == capacity - 1) {
            String takeItem = ringBuffer.take();
            System.out.println("取出" + takeItem + "成功");
        }
        
        if (i == capacity) {
            String takeItem = ringBuffer.take();
            System.out.println("取出" + takeItem + "成功");
        }
        
        String inputItem = i+"";
        boolean putSuccess = ringBuffer.put(inputItem);
        System.out.println(putSuccess ? "插入" + inputItem + "成功" : "插入" + inputItem + "失败" );
    }
    
}

代码索引

RingBuffer.java

日记本
Web note ad 1