Probabilmente l'esempio più noto sono i buffer di file. Dato che l'I / O del disco (in particolare l'accesso al disco) è molto più lento di I / O di memoria, vale la pena leggere / scrivere su disco in blocchi di grandi dimensioni contemporaneamente, ma l'elaborazione avviene in genere in passaggi più piccoli. Quindi un buffer raccoglie i molti piccoli byte in un grosso blocco per la scrittura, o fornisce i dati byte per byte dalla memoria invece di passare al disco ogni volta.
Ma la stessa idea può essere utilizzata nel traffico di rete / database, praticamente ovunque l'accesso a una specifica fonte / sink di dati ha latenza elevata o sovraccarico, quindi vale la pena leggere / scrivere grandi quantità di dati contemporaneamente, ma elaborare succede in porzioni più piccole. I buffer possono colmare il divario tra queste esigenze in conflitto.
Anche i buffer giocano (o almeno hanno l'abitudine di giocare) un ruolo nelle schede grafiche. Il rendering di un'immagine di grandi dimensioni sullo schermo è un processo complesso e, anche con una scheda grafica veloce, potrebbe richiedere una notevole quantità di tempo, mentre lo spettatore può osservare lo sfarfallio di elementi grafici nuovi e mutevoli sullo schermo. Per evitare questo, e per rendere più agevole il passaggio tra fotogrammi contigui, la scheda grafica utilizza doppio buffering : uno dei buffer contiene la cornice reale mostrata sullo schermo, mentre il fotogramma successivo viene preparato nell'altro buffer, che non è visibile. Una volta che il nuovo frame è pronto, i buffer vengono commutati: il buffer 2 diventa il buffer attivo e visibile e il buffer 1 viene liberato per il rendering del fotogramma successivo. Questa tecnica è stata utilizzata in tutte le schede grafiche circa dieci anni fa - non so se è ancora usata comunque
L'implementazione di un buffer è fondamentalmente molto semplice: è necessario un array di memoria che memorizzi le unità fondamentali di dati (in genere byte / caratteri) e un puntatore o iteratore per tenere traccia di dove stai leggendo / scrivendo all'interno del buffer. Dopo ogni lettura / scrittura, il puntatore viene spostato di conseguenza. Un buffer di lettura viene riempito con i dati dall'origine prima di iniziare l'elaborazione, quindi una volta che tutti i dati sono stati elaborati, viene letto un nuovo blocco di dati e il puntatore viene reimpostato all'inizio del buffer, in modo che l'elaborazione possa iniziare al di sopra di. Questo è tutto trasparente per il chiamante del buffer, spesso un tale buffer è implementato come delegato / proxy, fornendo la stessa interfaccia dell'origine dati reale, quindi tutto ciò che il chiamante vede è che sta leggendo i dati dalla sorgente (un file , un socket di rete, una tabella DB). Naturalmente, il chiamante può osservare timeout tra letture / scritture successive quando si accede alla fonte in background.
Un concetto correlato è code . Una coda è una raccolta più strutturata contenente elementi di un tipo specifico. La differenza principale tra un buffer e una coda è che una coda può essere acceduta da più thread in parallelo, alcuni dei quali stanno spingendo i dati in esso, altri stanno scoppiando elementi da esso. Per una coda, gli elementi vengono tipicamente spinti fino alla fine e spuntati dall'inizio, pertanto le code vengono spesso definite strutture di dati LIFO (Last In First Out). L'uso tipico delle code è nei sistemi "produttore-consumatore", in cui uno o più thread / processi producono alcuni dati e altri thread stanno consumando i dati. La coda serve a disaccoppiare i produttori dai consumatori e a semplificare le loro interazioni.