Condivisione della tecnologia

Completa alfabetizzazione nel modello di rete Java

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

Panoramica

Descrive la conoscenza di base di NIO a livello ava, per una revisione di base

1. Panoramica NIO

In Java, NIO (Non-blocking I/O o New I/O) è un nuovo set di API per operazioni di input/output introdotte in Java SE 1.4 e versioni successive.

​ Rispetto al modello IO tradizionale, offre maggiore efficienza e migliori capacità di elaborazione simultanea. La caratteristica principale di NIO è la sua funzionalità non bloccante, che consente a un singolo thread di gestire più canali I/O, migliorando così notevolmente le prestazioni dell'applicazione in scenari ad alta concorrenza.

Lo scenario di utilizzo di Java NIO è particolarmente adatto per situazioni in cui è necessario elaborare un gran numero di connessioni simultanee. Ad esempio, in un server di rete, un thread può gestire migliaia di connessioni client senza la necessità di allocare un thread indipendente per ciascuna connessione Può ridurre significativamente il consumo delle risorse di sistema e migliorare le capacità di elaborazione

2. Tre componenti principali di NIO in Java

1. Canali

Il canale è il mezzo del flusso di dati in Java NIO. È bidirezionale e può essere utilizzato per leggere o scrivere dati. Il vantaggio principale dei canali è che non sono bloccanti, il che significa che un thread può gestire più canali. Quando non si verificano eventi su un canale, il thread non verrà bloccato e potrà invece gestire altre attività. I principali tipi di canale includono:

  • FileChannel: Utilizzato per operazioni di lettura e scrittura di file. Può essere utilizzato per scrivere dati dal buffer al file o per leggere dati dal file nel buffer.
  • SocketChannel: utilizzato per le connessioni TCP nelle comunicazioni di rete e può essere utilizzato per leggere e scrivere dati.
  • ServerSocketChannel: Utilizzato per accettare nuove connessioni SocketChannel, simili al tradizionale ServerSocket.
  • DatagramChannel: Utilizzato per la comunicazione UDP, che può inviare e ricevere datagrammi.

2. Buffer

Il buffer è un oggetto utilizzato per archiviare dati in NIO. È un array di byte che può scrivere e leggere dati da esso. Un buffer ha una capacità specifica e ha due proprietà importanti: posizione e limite.

  • Capacità: il numero massimo di elementi che il buffer può memorizzare.
  • Posizione: L'indice dell'elemento su cui si sta attualmente operando, che cambia quando i dati vengono letti o scritti.
  • Limite: In modalità lettura, il valore massimo che la posizione può raggiungere; in modalità scrittura, la posizione non può superare tale valore.

I principali tipi di buffer sonoByteBufferCharBufferShortBufferIntBufferLongBufferFloatBufferEDoubleBuffer, ogni tipo corrisponde a un tipo di dati primitivo.

3. Selettori

Un selettore è un multiplexer in NIO che consente a un singolo thread di monitorare eventi da più canali, ad esempio lettura, scrittura, connessione e ricezione di eventi. Il selettore notifica all'applicazione quando uno dei canali è pronto per le operazioni di I/O. L'utilizzo dei selettori migliora notevolmente le capacità di elaborazione della concorrenza delle applicazioni di rete poiché non è necessario creare un thread per ogni connessione.

I principali metodi di selezione includono:

  • select(): si blocca finché almeno un canale non è pronto per le operazioni di I/O.
  • selectedKeys(): Restituisce un Set contenente gli oggetti SelectionKey di tutti i canali preparati.
  • wakeup(): Interrupt bloccatoselect()trasferimento.

SelectionKeyÈ l'associazione tra selettori e canali. Rappresenta lo stato di registrazione di un canale sul selettore, inclusi canali, selettori, raccolte di eventi interessati e raccolte di eventi pronti.

3. Programmazione di rete

1. Blocco I/O

​ Questo è il modello I/O più tradizionale. In Java, le tradizionali API Socket e ServerSocket si basano sul blocco dell'I/O. In questa modalità, quando un thread avvia un'operazione di lettura o scrittura, il thread viene bloccato fino al completamento dell'operazione di I/O. Se non sono presenti dati da leggere per l'operazione di lettura o l'operazione di scrittura non può essere completata immediatamente, il thread attenderà fino al completamento dell'operazione.

Caratteristiche:

  • Modello di programmazione semplice: Facile da comprendere e implementare.
  • Occupazione delle risorse: ogni connessione richiede un thread indipendente, con conseguente numero limitato di thread e consumo elevato di risorse.
  • Limitazioni delle prestazioni: Negli scenari ad alta concorrenza, il cambio di thread e il cambio di contesto sono costosi e possono facilmente diventare colli di bottiglia.

2. I/O non bloccanti

​ L'I/O non bloccante fa parte del framework Java NIO (New I/O), che consente ai thread di avviare operazioni di lettura e scrittura senza essere bloccati. Se non sono presenti dati da leggere per un'operazione di lettura o l'operazione di scrittura non può essere completata immediatamente, il thread non verrà sospeso e potrà continuare a eseguire altre attività.

Caratteristiche:

  • flessibilità: i thread possono gestire più connessioni perché non sono bloccati in attesa di operazioni di I/O.
  • Maggiore complessità: Il programmatore deve verificare esplicitamente se l'operazione è stata completata, il che aumenta la difficoltà della programmazione.
  • Miglioramenti delle prestazioni: negli scenari con concorrenza elevata, il sovraccarico causato dal cambio di thread può essere ridotto in modo significativo.

3. Multiplexing

​ Il multiplexing consiste nel monitorare più descrittori di file contemporaneamente attraverso un thread e operare su un descrittore solo quando è pronto (di solito significa che i dati sono leggibili o il buffer di scrittura è scrivibile). I selettori vengono utilizzati in Java per implementare il multiplexing.

Caratteristiche:

  • Elevata concorrenza: un thread può gestire migliaia di connessioni, migliorando notevolmente la velocità effettiva del server.
  • Efficiente: Solo quando un'operazione I/O è pronta, il thread verrà risvegliato per evitare cambi di thread non necessari.
  • Risparmio di risorse: Rispetto al blocco dell'I/O, i server con il modello multiplexing possono utilizzare le risorse di sistema in modo più efficiente.

Avviso:

Nelle applicazioni pratiche, l'I/O non bloccante viene spesso utilizzato insieme al multiplexing. Ad esempio, un server può utilizzare un selettore per monitorare più SocketChannel. Quando un SocketChannel contiene dati che possono essere letti o scritti, il selettore avviserà il server e il server elaborerà questo SocketChannel specifico in modo non bloccante.

4.NIO VS BIO

4.1 flusso vs canale

In Java, Stream e Channel sono due modi diversi di elaborare i flussi di dati. Appartengono rispettivamente alla libreria IO standard e alla libreria NIO. Ecco un confronto dettagliato tra i due concetti:

Flusso

Stream fa parte del modello IO (blocking IO) standard di Java, che fornisce un modo per leggere e scrivere i dati in sequenza. Il flusso è diviso in due tipi: InputStream e OutputStream, che vengono utilizzati rispettivamente per leggere e scrivere i dati.Questi flussi possono essere flussi di byte (comeInputStream, OutputStream) o un flusso di caratteri (comeReader, Writer)。

Caratteristiche:

  • Ostruttivo: per impostazione predefinita, le operazioni di flusso sono bloccanti, ovvero se non sono presenti dati da leggere per l'operazione di lettura o se l'operazione di scrittura non può essere completata immediatamente, il thread chiamante verrà bloccato fino al completamento dell'operazione.
  • Unidirezionalità: Ogni flusso ha una direzione, di sola lettura o di sola scrittura.
  • spegnimento automatico: dopo aver utilizzato Stream, in genere è necessario richiamarloclose() metodo per liberare risorse.A partire da Java 7, l'istruzione try-with-resources può chiudere automaticamente l'implementazioneAutoCloseableRisorse dell'interfaccia, incluso Stream.

Canale

Channel fa parte del modello Java NIO (New IO), che fornisce un livello di astrazione più elevato rispetto a Stream, consentendo un'elaborazione dei dati più efficiente. I canali possono essere bidirezionali, ovvero possono essere utilizzati per leggere e scrivere dati.I principali tipi di canali includonoFileChannelSocketChannelServerSocketChannelEDatagramChannel

Caratteristiche:

  • non bloccante :Il canale può essere configurato in modalità bloccante o non bloccante. In modalità non bloccante, se un'operazione di lettura non ha dati da leggere o un'operazione di scrittura non può essere completata immediatamente, l'operazione verrà restituita immediatamente invece di bloccare il thread.
  • Operazioni di buffer:Le operazioni sul canale vengono sempre eseguite attraverso il buffer (Buffer). I dati vengono letti dal canale nel buffer o scritti dal buffer nel canale.
  • Multiplexing: il canale può essere utilizzato insieme al selettore per ottenere il multiplexing, consentendo a un thread di monitorare le operazioni I/O di più canali e migliorare le capacità di elaborazione simultanea.

Riassumere

  • Scena applicabile: Stream è più adatto per l'elaborazione di set di dati più piccoli o semplici operazioni su file, mentre Channel è più adatto per l'elaborazione di grandi quantità di dati, in particolare comunicazioni di rete e operazioni su file di grandi dimensioni, perché fornisce prestazioni più elevate e migliori capacità di concorrenza.
  • Complessità di programmazione: L'API di Stream è relativamente semplice e facile da usare; mentre le API di Channel e NIO sono più complesse, ma forniscono funzioni più potenti e maggiore efficienza.
  • Gestione delle risorse: Che si tratti di uno stream o di un canale, le risorse devono essere gestite in modo appropriato e garantire che vengano chiuse quando non sono più necessarie per evitare perdite di risorse.

4.2.modello IO

In Java, i modelli operativi di I/O possono essere classificati in base alle due dimensioni sincrono/asincrono e bloccante/non bloccante.

4.2.1 I/O di blocco sincrono

definizione : L'I/O con blocco sincrono è il modello di I/O più tradizionale. Quando un thread chiama un'operazione di I/O (come la lettura o la scrittura), il thread verrà bloccato fino al completamento dell'operazione. Ciò significa che il thread non può eseguire altre attività finché l'operazione non viene completata.

Caratteristiche

  • Semplice e facile da usare, la logica del codice è chiara.
  • Ogni operazione di I/O richiede un thread esclusivo, che non è adatto a scenari con elevata concorrenza e può portare all'esaurimento delle risorse del thread.
  • Comunemente usato inInputStreamOutputStreamSocketEServerSocketscena.

Esempio:

Usa tradizionaleInputStreamEOutputStreamPer leggere e scrivere file:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class SyncBlockingIOExample {
    public static void main(String[] args) {
        try (FileInputStream fis = new FileInputStream("input.txt");
             FileOutputStream fos = new FileOutputStream("output.txt")) {
            byte[] buffer = new byte[1024];
            int bytesRead;
            while ((bytesRead = fis.read(buffer)) != -1) {
                fos.write(buffer, 0, bytesRead);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

4.2.2 I/O sincrono non bloccante

definizione : nell'I/O sincrono non bloccante, il thread non verrà bloccato dopo aver chiamato l'operazione I/O. Se l'operazione non può essere completata immediatamente, il metodo restituirà immediatamente un risultato, solitamente restituendo un valore speciale (come -1 o 0) o lanciando un'eccezione per indicare che l'operazione non è stata completata.

Caratteristiche

  • È necessario eseguire il polling dello stato dell'operazione fino al completamento dell'operazione.
  • Maggiore utilizzo dei thread perché i thread possono eseguire altre attività durante l'attesa di I/O.
  • L'implementazione è più complessa e necessita di gestire polling e controlli di stato.
  • Basato su Java NIOChannelsEBuffers, a chiamataconfigureBlocking(false)Imposta il canale in modalità non bloccante.

Esempio:

Utilizzando NIOFileChannelPer la lettura e la scrittura non bloccanti:

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.ByteBuffer;

public class SyncNonBlockingIOExample {
    public static void main(String[] args) {
        try (FileInputStream fis = new FileInputStream("input.txt");
             FileOutputStream fos = new FileOutputStream("output.txt");
             FileChannel inChannel = fis.getChannel();
             FileChannel outChannel = fos.getChannel()) {
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            inChannel.configureBlocking(false);
            while (inChannel.read(buffer) > 0) {
                buffer.flip();
                outChannel.write(buffer);
                buffer.clear();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

4.2.3 Multiplexing sincrono I/O

definizione: Utilizzo del modello I/O multiplex sincronoSelector(selettore) per monitorare piùChannel evento.Quando il thread chiamaSelector.select(), si bloccherà finché non ce ne sarà almeno unoChannelSi verificano eventi (come ad esempio leggibili, scrivibili, richieste di connessione, ecc.).

Caratteristiche

  • Consente a un thread di gestirne piùChannel, migliora la concorrenza.
  • passaggioSelectorESelectionKeyMeccanismo in grado di gestire in modo efficiente un gran numero di connessioni simultanee.
  • Adatto per scenari di server di rete, come server Web e server di chat.
  • Basato sul framework Java NIO.

Esempio:

Utilizzando NIOFileChannelPer la lettura e la scrittura non bloccanti:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Set;

public class SyncMultiplexingIOExample {
    public static void main(String[] args) throws IOException {
        try (Selector selector = Selector.open();
             ServerSocketChannel serverSocketChannel = ServerSocketChannel.open()) {
            serverSocketChannel.socket().bind(new InetSocketAddress(8080));
            serverSocketChannel.configureBlocking(false);
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            while (true) {
                if (selector.select() > 0) {
                    Set<SelectionKey> keys = selector.selectedKeys();
                    for (SelectionKey key : keys) {
                        if (key.isAcceptable()) {
                            ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
                            SocketChannel clientChannel = ssc.accept();
                            clientChannel.configureBlocking(false);
                            clientChannel.register(selector, SelectionKey.OP_READ);
                        }
                    }
                    keys.clear();
                }
            }
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

4.2.4 I/O asincrono non bloccante

definizione : nel modello I/O asincrono non bloccante, il thread ritorna immediatamente dopo aver avviato l'operazione di I/O senza attendere il completamento dell'operazione. Una volta completata l'operazione, il thread verrà avvisato in modo asincrono tramite una funzione di callback o una notifica di evento.

Caratteristiche

  • Il modello I/O più efficiente, i thread non sono affatto bloccati e possono eseguire immediatamente altre attività.
  • passaggioAsynchronousChannelInterfaccia e la sua implementazione della sottoclasse, come ad esempioAsynchronousFileChannelEAsynchronousSocketChannel
  • Adatto per scenari con concorrenza elevata e prestazioni elevate, come l'elaborazione di big data e server di rete a carico elevato.
  • Java 7 ha introdotto il modello I/O asincrono non bloccante.

Esempio:

Utilizzando NIOAsynchronousFileChannelEseguire la lettura e la scrittura asincrona dei file:

import java.io.IOException;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.ByteBuffer;
import java.util.concurrent.CountDownLatch;

public class AsyncNonBlockingIOExample {
    public static void main(String[] args) throws IOException, InterruptedException {
        AsynchronousFileChannel inChannel = AsynchronousFileChannel.open(Paths.get("input.txt"), StandardOpenOption.READ);
        AsynchronousFileChannel outChannel = AsynchronousFileChannel.open(Paths.get("output.txt"), StandardOpenOption.WRITE);
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        CountDownLatch latch = new CountDownLatch(1);
        inChannel.read(buffer, 0, buffer, 0, (channel, result) -> {
            buffer.flip();
            outChannel.write(buffer, 0, buffer, 0, null);
            latch.countDown();
        });
        latch.await();
        inChannel.close();
        outChannel.close();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

Nota: il modello asincrono richiede il supporto del sistema operativo sottostante (Kernel)

  • I sistemi Windows implementano un vero I/O asincrono tramite IOCP
  • L'I/O asincrono nel sistema Linux è stato introdotto nella versione 2.6, ma la sua implementazione sottostante utilizza ancora il multiplexing per simulare l'I/O asincrono e non vi è alcun vantaggio in termini di prestazioni.

4.2.5 I/O di blocco asincrono

definizione : In teoria questo modello non esiste, perché "asincrono" significa che l'operazione viene eseguita in background e il thread non è bloccato. Pertanto, il blocco asincrono degli I/O è un concetto contraddittorio e non apparirà nella pratica.

4.2.6 Riepilogo

La scelta del modello I/O appropriato è fondamentale per le prestazioni e la gestione delle risorse. Negli scenari ad alta concorrenza, i modelli I/O sincrono non bloccante o I/O multiplex sincrono sono scelte comuni. Negli scenari che richiedono prestazioni e velocità di risposta estreme, il modello I/O asincrono non bloccante è la prima scelta.

4.3 Copia zero

Concetto di copia zero

​ La tecnologia zero-copy significa che durante il processo di trasmissione dei dati da un luogo a un altro, i dati non devono essere copiati tra lo spazio utente e lo spazio kernel, o almeno riduce il numero di tali copie, migliorando così l'efficienza del sistema. Nelle operazioni di I/O tradizionali, quando i dati vengono letti dalla rete o dal disco, vengono prima copiati nel buffer nello spazio del kernel e quindi copiati dallo spazio del kernel al buffer nello spazio utente. Al contrario, in zero-copy, i dati possono essere elaborati direttamente nello spazio del kernel o trasferiti direttamente dallo spazio del kernel al dispositivo di rete, riducendo così l'operazione di copia della CPU, riducendo il sovraccarico del sistema e migliorando l'efficienza della trasmissione dei dati.

Origine della copia zero

Il concetto di zero copy è apparso per la prima volta nella progettazione del sistema operativo, con l'obiettivo di risolvere il collo di bottiglia prestazionale causato dalla copia dei dati nelle tradizionali operazioni di I/O. Nei primi sistemi informatici, tutte le operazioni di I/O richiedevano più copie di dati tra lo spazio utente e lo spazio kernel. Ciò divenne gradualmente un collo di bottiglia delle prestazioni dopo che le reti ad alta velocità e i dischi di grande capacità divennero comuni.

Punti tecnici chiave

  • I/O diretto: consente alle applicazioni di accedere direttamente ai dispositivi disco, ignorando il meccanismo di memorizzazione nella cache del file system.
  • Mappatura della memoria (MMAP): mappa i file sulla memoria in modo che sia possibile accedere ai dati dei file come se fossero memoria, riducendo le operazioni di copia.
  • Inviare file: chiamata di sistema Linux che può trasferire i dati direttamente da un descrittore di file a un altro, evitando copie intermedie.
  • DMA (accesso diretto alla memoria): Tecnologia a livello hardware che consente il trasferimento diretto dei dati tra il dispositivo e la memoria senza il coinvolgimento della CPU.

Implementazione in Java:

Java supporta la tecnologia zero-copy tramite il framework NIO (New I/O). Presentato NIOFileChannelESocketChannel e altre classi, che forniscono operazioni di I/O più efficienti. Nello specifico,FileChannel.transferTo()EFileChannel.transferFrom()I metodi possono trasferire i dati direttamente da un canale file a un canale socket o viceversa senza caricare i dati in un buffer, ottenendo così zero copie.

Ad esempio, supponiamo di dover inviare il contenuto di un file di grandi dimensioni alla rete. L'approccio tradizionale consiste nel leggere prima il contenuto del file in un buffer e quindi scriverlo nella rete. Ciò comporta due operazioni di copia: una dal disco al buffer e un'altra dal buffer alla rete.durante l'utilizzotransferTo()Quando si utilizza questo metodo, i dati possono essere trasferiti direttamente dal disco alla rete senza la necessità di un buffer intermedio, riducendo così il numero di copie e raggiungendo zero copie.

Un esempio utilizzando ByteBuffer:

ByteBuffere altroBufferclasse (comeCharBufferShortBufferecc.) forniscono buffer che possono essere riempiti o svuotati senza coinvolgere direttamente una copia tra lo spazio utente e lo spazio kernel.ByteBufferPuò essere utilizzato direttamente o indirettamente nella tecnologia zero-copy:

  1. Buffer direttoByteBuffer.allocateDirect(size)CreatoByteBuffer Le istanze vengono mappate direttamente sulla memoria fisica, ignorando l'heap Java.Quando tale buffer viene confrontato conFileChannelOSocketChannel Se utilizzati insieme, i dati possono essere trasferiti direttamente tra la memoria fisica e i dispositivi hardware senza la necessità di copie aggiuntive tramite l'heap Java. Ciò consente la vera copia zero su alcune piattaforme.
  2. utilizzoFileChannelDitransferTo()EtransferFrom(): Questi metodi consentono di archiviare i dati direttamente nelFileChannelESocketChanneltrasmesso traByteBuffer come intermediario. Ciò significa che i dati possono essere trasferiti direttamente dal disco alla rete o viceversa senza dover essere copiati tra lo spazio utente e lo spazio kernel.
  3. utilizzoByteBufferDiwrap()metodowrap()Il metodo consente di racchiudere un array di byte esistente o un altro buffer in un fileByteBuffer , quindi non è necessario copiare i dati in un nuovo buffer. Ciò è utile per evitare copie inutili dei dati.
  4. utilizzoByteBufferDislice()metodoslice() Il metodo crea una visualizzazione del buffer senza copiare i dati sottostanti.Ciò significa che un grandeByteBufferSuddiviso in più buffer più piccoli senza copiare i dati.

Combina ByteBuffer con FileChannel:

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;

public class ZeroCopyExample {
    public static void main(String[] args) {
        Path path = Path.of("example.txt");
        try (FileChannel fileChannel = FileChannel.open(path, StandardOpenOption.READ)) {
            ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
            long transferred = fileChannel.transferTo(0, fileChannel.size(), System.out);
            System.out.println("Transferred bytes: " + transferred);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

Avviso:

Dovremmo notare che in realtà non esiste una copia zero completa. La copia zero menzionata qui è solo per la nostra stessa applicazione, che non ha una copia a livello di utente. Ma anche a livello di utente, è impossibile non effettuare alcuna operazione di copia, ma ridurre il più possibile le copie. Pertanto, possiamo capire che il termine copia zero si riferisce in realtà alla tecnologia di riduzione del numero di copie dei dati. e ciò non significa che non esista una vera e propria operazione di copia.