Сценарій інтерв’ю „Останні 100 байт”


78

Я отримав це запитання в інтерв’ю днями і хотів би знати найкращі можливі відповіді (я відповів не дуже добре ха-ха):

Сценарій: Існує веб-сторінка, яка контролює байти, надіслані через певну мережу. Щоразу, коли байт надсилається, функція recordByte () називається передачею цього байта, це може відбуватися сотні тисяч разів на день. На цій сторінці є кнопка, яка при натисканні відображає на екрані останні 100 байтів, переданих RecordByte () (це робиться за допомогою методу друку нижче).

Наступний код - це те, що мені дали і попросили заповнити:

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

Який найкращий спосіб зберігати 100 байт? Список? Цікаво, як краще це зробити.


70
Циклічний буфер із використанням масиву є одним із способів. Ініціалізуйте його цифрами 0, а потім просто відстежуйте голову та довжину. Потім ви можете використовувати головку та довжину, щоб обвести буфер для його друку. Ефективне використання пам'яті та центрального процесора, а також відповідає історичним потребам.
Тім Ллойд

1
Ви також можете використовувати ByteBuffer: download.oracle.com/javase/6/docs/api/java/nio/ByteBuffer.html
Стефан,

2
чи потрібно зберігати всі байти або лише останні 100?
jpredham

2
Я б використав стек, просто натисніть байти, які надходять, а потім виведіть останні 100 результатів.
Елвін Баена

5
@alvinbaena, що трапляється, коли проходять дні або тижні recordByte (), а ніхто не викликає print ()?
Рассел Борогове

Відповіді:


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

2
закрито, але піддається помилкам, коли індекс стає занадто великим. Натомість використовуйте це в recordByte: index = (index + 1)% 100; масив [індекс] = b;
DwB

3
Краще використовувати умову в кінці для обгортання замість модуля, це, мабуть, і трохи чіткіше, і трохи швидше (і якщо ви робите це для кожного надісланого байта, продуктивність буде мати значення).
jmoreno

4
Я думаю, вам потрібно відстежувати довжину, інакше, якщо друк викликається до того, як буде записано 100 байт, тоді ви все одно роздрукуєте 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 реалізує інтерфейс черги і повинен дозволяти операціям add () (enqueue) та remove () (dequeue) виконуватися за час O (1).
сцен

Стек був моєю першою думкою, але питання не говорить про те, як йому потрібні представлені дані (в порядку появи проти останнього байту). .. адже всі останні 100 байт чогось точно не будуть корисними, крім метрик / звітування.
Метью Кокс,

@MatthewCox Так, я маю на увазі як запитання для співбесіди це насправді не було корисно, за винятком тесту вирішення проблем, але мені було цікаво, як краще насправді це зробити.
Йоттагрей,

@MatthewCox Технічно стек не дозволяє отримати доступ до найдавніших даних (перших записів), просто ОСТАННІЙ запис, отже, до черги.
сцен

@sceaj Я б аргументував це. У вас все ще є ітератори ... ви не обмежені лише першим елементом стека. Що стосується вашої точки зору, те саме стосується і зворотного шляху. Ви отримаєте доступ лише до найстаріших байтів, а не до найновіших, оскільки черга видаляється лише спереду.
Метью Кокс,

26

Круговий буфер з використанням масиву:

  1. Масив 100 байт
  2. Відстежуйте, де знаходиться головний індекс i
  3. Для recordByte()покласти поточний байт в A [i] і i = i + 1% 100;
  4. Для print()повернення підмасиву (i + 1, 100) об'єднується з підмасивом (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 тощо). Мені чогось не вистачає?


що трапляється, якщо print () викликається після того, як записано понад 100 байт?
jpredham

ви не записуєте більше 100 байт. зупинка, коли <= 100.
Шрікар Аппалараю

1
Це отримало б перші 100 байтів, а не останні 100.
інтержой

Я думаю, я насправді не розумію, як обробляти збереження лише останніх 100 байт, і робити це дійсно ефективно. Ваша відповідь не має жодного сенсу, а що, якщо ваш масив досягне 100 і записByte () буде викликаний знову? Ваше рішення містить лише 1-й 100 байт
Йоттагрей

1

Багатопотокове рішення з неблокуючим введенням / виведенням:

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");
        //  }
        // }
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.