美文网首页
队列和循环队列

队列和循环队列

作者: 明日即是今朝 | 来源:发表于2020-08-14 17:09 被阅读0次

问题

在分析ffplay源码时,他有两个重要的结构体PacketQueue和FrameQueue,前者是一个用链表实现的队列(队列还可以用数组以及栈等来实现),后者是一个循环队列。所以必须得搞懂他的实现我们才能看懂ffplay的源码,下面我把java和c实现的方式都写出来。

链表实现队列

JAVA实现

先需要一个节点类Node

/**
 * 实现度列的node
 */
public class Node {
    int data;
    Node next;


    public Node(int data) {
        this.data = data;
    }
}

再来Queue

/**
 * 队列 用链表实现
 */
public class Queue {

    Node first;
    Node last;
    /**
     * 添加数据  添加到后面
     */
    public void add(int data){
        Node node = new Node(data);
        if(first==null){
            first=node;
            last=node;
        }else {
            //赋值到尾节点  然后尾节点往后移动
            last.next=node;
            last=node;
        }
    }
    /**
     * 取数据  从前面取数据 取出first 然后把first赋值给别人
     */
    public int  offer(){
      if(first==null){
          return 0;
      }else {
          return getNode().data;
      }
    }


    private Node getNode(){
        Node temp=first;
        first=first.next;
        return temp;
    }

}

其实原理很简单,大家看看就好。

C实现

c里面跟类对应的就是结构体,所以咱们需要一个Node和一个Queue结构体

typedef struct Node {
    struct Node *next;
    int data;

} Node;

typedef struct Queue {
    Node *first, *last;
};

然后就是写入和读取的方法了,思想和java的思想一模一样,就是java里面可以new直接有对象,C需要malloc

/**
 * 初始化queue
 * @return
 */
Queue *initQueue() {
    Queue *q = static_cast<Queue *>(malloc(sizeof(Queue)));
    return q;
}

/**
 * 添加数据
 * @param data
 */
void add(Queue *q, int data) {
    //node分配内存 把data 赋值进去 其实就是 java的new 构造函数的过程
    Node *node;
    node = static_cast<Node *>(malloc(sizeof(Node)));
    node->data = data;
    //有了node 节点后就要把它加到队列里面去了
    if (!q->first) {
        //如果头结点是null
        q->first = node;
        q->last = node;
    } else {
        //如果不是null就要拼接到后面
        q->last->next = node;
        q->last = node;
    }
}

/**
 * 获取数据
 */
int offer(Queue *q) {
    if (!q->first) {
        return -1;
    } else {
        int data = q->first->data;
        q->first = q->first->next;
        return data;
    }
}

关于链表实现其实还是比较简单的,了解了C 如何实现队列的功能后我们就可以搞明白PacketQueue的源码实现了,其实就是我们上面C代码。那关于FrameQueue他并没有采用这种实现方式,而是采用了一种循环队列的方式,相对来说会复杂一点,我们来看下java和c的实现。

循环队列

Java实现

一个CircleQueue就可以实现

/**
 * 循环队列的实现
 * <p>
 * 循环队列的特点是 得预分配他里面数组的大小  然后有一个头指针 和一个尾指针  通过取模来实现循环
 *
 */
public class CircleQueue {
    private int maxSize;
    int first;
    int last;
    Object[] data;
    int size;
    //初始化的时候就需要给到队列最大的size
    public CircleQueue(int maxSize) {
        this.maxSize = maxSize;
        data = new Object[maxSize];
    }
    //如果开始的时候first 和last相等 就是空
    public boolean isEmpty() {
        return first == last;
    }
    ///这里+1 是为了区分 empty 和full   如果不+1 那么empty了full没法区分
    //所以这里有一点是, 其实last 一直指向的是添加数据的后面一个  这里+1就会浪费一个地方不能被插入数据
    public boolean isFull() {
        return (last + 1) % maxSize == first;
    }


    public void add(Object object) {
        if (isFull()) {
            //如果满了 就直接返回
            //fixme 关于扩容
            Log.i(TAG, "add: 满了");
            return;
        }
        //给最后一个元素赋值
        data[last] = object;
        //last++  取模 这样的话就可以循环
        last = (last + 1) % maxSize;
        size++;
    }

    /**
     * 取出数据
     */
    public Object offer() {
        //如果是空  就返回NULL
        if (isEmpty()) {
            return null;
        }
        //取出first的数据 然后first往后移动
        Object o = data[first];
        data[first]=null;
        first = (first + 1) % maxSize;
        size--;
        return o;
    }

    public Object[] getData(){
        return data;
    }
}

如果不能理解代码的话看下这篇文章,图画的很容易理解,
https://juejin.im/post/6844903805264347149

C实现

上来还是需要定义两个结构体

#define MAX_SIZE 5

int size;
//这个是数据
typedef struct Object {
    int i;
}Object;


typedef struct CircleQueue {
    //定义队列的长度
    Object q[MAX_SIZE];
    int first;
    int last;

}CirCleQueue;

然后就是具体的实现函数

/**
 * 是否队列满了
 * @param q
 * @return
 */
bool isFull(CircleQueue *q) {
    return (q->last + 1) % MAX_SIZE==q->first;
}

bool isEmpty(CircleQueue *q) {
    LOGE(" isempty --------------%d %d",q->first,q->last);
    return q->last == q->first;
}

void enqueue(CircleQueue *c, int  data) {
    //object 分配内存
    Object *object;
    object= static_cast<Object *>(malloc(sizeof(Object)));
    object->i=data;
    if (isFull(c)) {
        LOGE("------------full---------");;
        return;
    }
    //last 的赋值
    c->q[c->last] = *object;
    //last后移
    c->last = (c->last + 1) % MAX_SIZE;
    LOGE(" last and first %d %d ",c->last,c->first);
    size++;
}



void dequeue(CircleQueue *c) {
    if (isEmpty(c)) {
        LOGE("----------------EMPTY");
    } else{
        //把fist 拿出去
        Object o = c->q[c->first];
        //删除first占用的内存
        free(&c->q[c->first]);
        //first往后移动
        c->first = (c->first + 1) % MAX_SIZE;
    }

}

这里面其实需要理解的就是如何判断满了 和空了,上面在java部分分享的链接如果仔细看很容易理解,我这里也就不赘言了。

总结

这里主要是介绍了队列和循环队列的java和c实现,java代码可能封装的比较好,大部分的数据结构都有java的集合类实现了,感觉在阅读java的一些源代码的时候遇到更多的是设计模式。但是在C里面,刚刚开始阅读ffplay就遇到了关于队列和循环队列的数据结构,所以这里就带着java一起复习下,在NDK的学习路上,如果再遇到其他数据结构还会拿出来跟大家一起重温,学习,因为数据结构是我们最基础的东西。具体ffplay这里为什么FrameQueue使用了循环队列我们就去ffplay源码部分窥探了。题外话:ffplay的源码正在研究进行中,研究透了ffplay的源码准备自己搞个开源的播放器,加油!

相关文章

网友评论

      本文标题:队列和循环队列

      本文链接:https://www.haomeiwen.com/subject/szzgdktx.html