“最后100个字节”的采访场景


78

前几天,我在一次采访中遇到了这个问题,想知道一些可能的最佳答案(我回答得不太好哈哈):

场景:有一个网页正在监视通过某个网络发送的字节。每次发送一个字节时,都会调用recordByte()函数来传递该字节,每天可能发生数十万次。此页面上有一个按钮,当按下该按钮时,将在屏幕上显示传递给recordByte()的最后100个字节(通过调用下面的print方法来执行此操作)。

以下是我被要求填写的代码:

public class networkTraffic {
    public void recordByte(Byte b){
    }
    public String print() {
    }
}

存储100个字节的最佳方法是什么?一个列表?好奇如何最好地做到这一点。


70
使用数组的循环缓冲区是一种方法。用0初始化它,然后跟踪头和长度。然后,您可以使用head和length在缓冲区周围循环以进行打印。有效利用内存和CPU,再加上历史需求。
蒂姆·劳埃德


2
是否需要保留所有字节或仅保留最后100个字节?
jpredham

2
我将使用堆栈,只需将要发送的字节压入,然后弹出最后100个结果。
Alvin Baena

5
@alvinbaena,当几天或几周的recordByte()过去而没有任何人调用print()时会发生什么?
罗素·博罗戈夫

Answers:


150

像这样的东西(循环缓冲区):

byte[] buffer = new byte[100];
int index = 0;

public void recordByte(Byte b) {
   index = (index + 1) % 100;
   buffer[index] = b; 
}

public void print() {
   for(int i = index; i < index + 100; i++) {
       System.out.print(buffer[i % 100]);
   }
}

使用循环缓冲区的好处:

  1. 您可以静态保留空间。在实时网络应用程序(VoIP,流式传输..)中,经常这样做是因为您不需要存储传输的所有数据,而只需要存储包含要处理的新字节的窗口即可。
  2. 快速:可以用读写成本为O(1)的数组实现。

6
达到int的最大值时,您将遇到意外的行为,但否则似乎是正确的。
CassOnMars 2011年

2
关闭,但当索引太大时会出错。而是在recordByte中使用它:index =(index + 1)%100; array [index] = b;
2011年

3
最好在末尾使用条件来代替模,而不是取模,这可能既更清晰也更快(如果对发送的每个字节都这样做,性能将很重要)。
jmoreno

4
我认为您需要跟踪长度,否则,如果在记录100个字节之前调用了print,则无论如何都要打印出100个字节,其中一些是单元化的数组值(零)
Mike Q

3
您可以使用128个字节而不是100个字节。这将浪费28个字节,但是取模操作会更快。对于如此短的功能,速度的提高将是显着的。
Mackie Messer

34

我不知道Java,但是必须有一个队列概念,您可以使字节排队直到队列中的项目数达到100,此时您将使一个字节出队,然后使另一个字节出队。

public void recordByte(Byte b)
{ 
  if (queue.ItemCount >= 100)
  {
    queue.dequeue();    
  }
  queue.enqueue(b);
}

您可以通过查看以下项目进行打印:

public String print() 
{ 
  foreach (Byte b in queue)
  {
    print("X", b);  // some hexadecimal print function
  }
}  

3
为队列+1。LinkedList实现Queue接口,并应允许add()(入队)和remove()(出队)操作在O(1)时间内运行。
sceaj 2011年

堆栈是我的第一个想法,但问题并没有说明它希望如何呈现数据(从出现顺序到最新字节优先)。..毕竟,除了用于度量/报告之外,所有其他内容的最后100个字节都没有用。
马修·考克斯

@MatthewCox是的,我的意思是作为面试问题,除了作为解决问题的测试之外,它并没有真正的用处,但我对如何最好地做到这一点感到好奇。
Yottagray 2011年

@MatthewCox从技术上讲,堆栈不允许访问最早的数据(第一个条目),而只能访问最新的条目,因此无法访问队列。
sceaj 2011年

@sceaj我要指出这一点。您仍然有迭代器...您不仅限于堆栈的第一项。就您的观点而言,反之亦然。您只能访问最早的字节,而不能访问最新的字节,因为队列仅从最前面删除。
马修·考克斯

26

使用数组的循环缓冲区

  1. 100字节数组
  2. 跟踪头索引在哪里
  3. 为了recordByte()将当前字节放入A [i],i = i + 1%100;
  4. 对于print(),return subarray(i + 1,100)与subarray(0,i)连接

使用链表的队列(或Java队列):

  1. 用于recordByte()将新字节添加到末尾
  2. 如果新长度超过100,则删除第一个元素
  3. 对于 print()简单的打印列表

9

这是我的代码。它可能看起来有些晦涩,但是我很确定这是最快的方法(至少是在C ++中,而不是Java):

public class networkTraffic {
    public networkTraffic() {
      _ary = new byte[100];
      _idx = _ary.length;
    }

    public void recordByte(Byte b){
      _ary[--_idx] = b;
      if (_idx == 0) {
        _idx = _ary.length;
      }   
    }

    private int _idx;
    private byte[] _ary;
}

需要注意的几点:

  • 调用recordByte()时,没有分配/取消分配数据。
  • 我没有使用%,因为它比直接比较和使用if慢(分支预测在这里也可能会有所帮助)
  • --_idx_idx--不涉及任何临时变量要快。
  • 我倒数为0,因为这样,我不必_ary.length每次都获得呼叫,而是在到达第一个条目时仅获得100次。也许这不是必需的,编译器可以解决。
  • 如果对recordByte()的调用少于100次,则其余为零。

1
由于您是对的,因此被推荐使用,但是在Java中,我不必担心临时变量和避免检查长度。这两件事我都希望任何不错的JIT都可以优化。
丹尼尔·普里登

如果您也添加必需的print()方法,我会赞成。
icza

4

最简单的方法是将其推入数组。数组可容纳的最大大小为100个字节。不断增加字节流,因为它们正在通过网络流式传输。在数组中的前100个字节之后,当第101个字节出现时,请删除开头的字节(即第0个)。继续这样做。这基本上是一个队列。FIFO概念。下载完成后,您将剩下最后100个字节。

不仅在下载之后,而且在任何给定的时间点,此数组都将具有最后100个字节。

@Yottagray无法到达问题所在?似乎有许多通用方法(数组,循环数组等)和特定于语言的方法(byteArray等)。我想念什么吗?


如果记录了100个字节以上后调用print()会发生什么?
jpredham 2011年

您不会记录超过100个字节。停止时<= 100
Srikar Appalaraju

1
这将得到第一个100个字节,而不是最后100
interjay

我想我不太了解如何处理仅保留最后100个字节,并以一种非常有效的方式进行操作。您的答案毫无意义,如果您的数组达到100并再次调用recordByte()怎么办?您的解决方案仅保留第1个100字节
Yottagray,2011年

1

具有非阻塞I / O的多线程解决方案:

private static final int N = 100;
private volatile byte[] buffer1 = new byte[N];
private volatile byte[] buffer2 = new byte[N];
private volatile int index = -1;
private volatile int tag;

synchronized public void recordByte(byte b) {
  index++;
  if (index == N * 2) {
    //both buffers are full
    buffer1 = buffer2;
    buffer2 = new byte[N];
    index = N;
  }
  if (index < N) {
    buffer1[index] = b;
  } else { 
    buffer2[index - N] = b;
  }
}

public void print() {
  byte[] localBuffer1, localBuffer2;
  int localIndex, localTag;
  synchronized (this) {
   localBuffer1 = buffer1;
   localBuffer2 = buffer2;
   localIndex = index;
   localTag = tag++;
  }
  int buffer1Start = localIndex - N >= 0 ? localIndex - N + 1 : 0;
  int buffer1End = localIndex < N ? localIndex : N - 1;      
  printSlice(localBuffer1, buffer1Start, buffer1End, localTag);
  if (localIndex >= N) {
    printSlice(localBuffer2, 0, localIndex - N, localTag);
  }
}

private void printSlice(byte[] buffer, int start, int end, int tag) {
  for(int i = start; i <= end; i++) {
    System.out.println(tag + ": "+ buffer[i]);
  }
}

0

只是为了它。如何使用ArrayList<Byte>?说为什么不呢?

public class networkTraffic {
    static ArrayList<Byte> networkMonitor;          // ArrayList<Byte> reference
    static { networkMonitor = new ArrayList<Byte>(100); }   // Static Initialization Block
    public void recordByte(Byte b){
        networkMonitor.add(b);
        while(networkMonitor.size() > 100){
            networkMonitor.remove(0);
        }
    }
    public void print() {
        for (int i = 0; i < networkMonitor.size(); i++) {
            System.out.println(networkMonitor.get(i));
        }
        // if(networkMonitor.size() < 100){
        //  for(int i = networkMonitor.size(); i < 100; i++){
        //      System.out.println("Emtpy byte");
        //  }
        // }
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.