Domanda

Sto lavorando a un'applicazione e un approccio di progettazione prevede un uso estremamente pesante dell'operatore instanceof. Mentre so che il design OO generalmente cerca di evitare di usare ==, questa è una storia diversa e questa domanda è puramente correlata alle prestazioni. Mi chiedevo se ci fosse qualche impatto sulle prestazioni? È veloce quanto <=>?

Ad esempio, ho una classe base con 10 sottoclassi. In una singola funzione che accetta la classe di base, controllo se la classe è un'istanza della sottoclasse ed eseguo alcune routine.

Uno degli altri modi in cui ho pensato di risolverlo era usare un " type id " invece, la primitiva intera e usa una maschera di bit per rappresentare le categorie delle sottoclassi, quindi fai semplicemente un confronto maschera di bit delle sottoclassi " type id " a una maschera costante che rappresenta la categoria.

<=> La JVM è in qualche modo ottimizzata per essere più veloce di così? Voglio attenermi a Java, ma le prestazioni dell'app sono fondamentali. Sarebbe bello se qualcuno che è stato su questa strada prima potesse offrire qualche consiglio. Sto pignolando troppo o mi sto concentrando sulla cosa sbagliata da ottimizzare?

È stato utile?

Soluzione

I moderni compilatori JVM / JIC hanno rimosso l'hit di prestazioni della maggior parte delle quot tradizionalmente &; slow &; operazioni, tra cui esempio di gestione delle eccezioni, riflessione, ecc.

Come scrisse Donald Knuth, " Dovremmo dimenticare le piccole efficienze, diciamo circa il 97% delle volte: l'ottimizzazione prematura è la radice di tutti i mali. " Le prestazioni di instanceof probabilmente non saranno un problema, quindi non perdere tempo a trovare soluzioni esotiche fino a quando non sei sicuro che sia il problema.

Altri suggerimenti

Approccio

Ho scritto un programma di riferimento per valutare diverse implementazioni:

  1. instanceof implementazione (come riferimento)
  2. oggetto orientato tramite una classe astratta e @Override un metodo di prova
  3. utilizzando un'implementazione del proprio tipo
  4. getClass() == _.class implementazione

Ho usato jmh per eseguire il benchmark con 100 chiamate di riscaldamento, 1000 iterazioni sotto misura e con 10 forchette. Quindi ogni opzione è stata misurata con 10.000 volte, il che richiede 12:18:57 per eseguire l'intero benchmark sul mio MacBook Pro con macOS 10.12.4 e Java 1.8. Il benchmark misura il tempo medio di ciascuna opzione. Per maggiori dettagli vedi la mia implementazione su GitHub .

Per completezza: esiste una versione precedente di questa risposta e il mio benchmark .

Risultati

| Operation  | Runtime in nanoseconds per operation | Relative to instanceof |
|------------|--------------------------------------|------------------------|
| INSTANCEOF | 39,598 ± 0,022 ns/op                 | 100,00 %               |
| GETCLASS   | 39,687 ± 0,021 ns/op                 | 100,22 %               |
| TYPE       | 46,295 ± 0,026 ns/op                 | 116,91 %               |
| OO         | 48,078 ± 0,026 ns/op                 | 121,42 %               |

tl; dr

In Java 1.8 getClass() è l'approccio più veloce, sebbene <=> sia molto vicino.

Ho appena fatto un semplice test per vedere come le prestazioni di instanceOf si confrontano con una semplice chiamata s.equals () a un oggetto stringa con una sola lettera.

in un ciclo di 10.000.000 l'istanza di Of mi ha dato 63-96ms e la stringa è uguale a 106-230ms

Ho usato java jvm 6.

Quindi nel mio semplice test è più veloce fare un'istanza di anziché un confronto di stringhe di un carattere.

l'uso di .equals () di Integer anziché di string mi ha dato lo stesso risultato, solo quando ho usato == sono stato più veloce di istanza di 20 ms (in un ciclo di 10.000.000)

Gli elementi che determineranno l'impatto sulle prestazioni sono:

  1. Il numero di classi possibili per le quali l'operatore instanceof potrebbe restituire true
  2. La distribuzione dei tuoi dati - la maggior parte delle istanze di operazioni sono state risolte nel primo o nel secondo tentativo? Ti consigliamo di mettere al primo posto il più probabile per restituire operazioni vere.
  3. L'ambiente di distribuzione. L'esecuzione su una Sun Solaris VM è significativamente diversa dalla Sun JVM di Windows. Solaris verrà eseguito in modalità "server" per impostazione predefinita, mentre Windows verrà eseguito in modalità client. Le ottimizzazioni JIT su Solaris renderanno tutti gli accessi ai metodi uguali.

Ho creato un microbenchmark per quattro diversi metodi di spedizione . I risultati di Solaris sono i seguenti, con il numero più piccolo più veloce:

InstanceOf 3156
class== 2925 
OO 3083 
Id 3067 

Rispondere alla tua ultima domanda: a meno che un profiler non ti dica, che passi una quantità ridicola di tempo in un'istanza di: Sì, stai puntando.

Prima di chiederti di ottimizzare qualcosa che non ha mai avuto bisogno di essere ottimizzato: scrivi il tuo algoritmo nel modo più leggibile ed eseguilo. Eseguilo, fino a quando il compilatore jit non ha la possibilità di ottimizzarlo da solo. Se poi hai problemi con questo pezzo di codice, usa un profiler per dirti dove ottenere di più e ottimizzarlo.

In tempi di compilatori altamente ottimizzati, è probabile che le tue ipotesi sui colli di bottiglia siano completamente sbagliate.

E nel vero spirito di questa risposta (di cui credo sinceramente): non so assolutamente come si collegano instanceof e == una volta che il compilatore jit ha avuto la possibilità di ottimizzarlo.

Ho dimenticato: non misurare mai la prima corsa.

Ho la stessa domanda, ma poiché non ho trovato "metriche delle prestazioni" per un caso d'uso simile al mio, ho creato un altro codice di esempio. Sul mio hardware e ampli Java 6 &; 7, la differenza tra instanceof e l'attivazione di iterazioni da 10 mln è

for 10 child classes - instanceof: 1200ms vs switch: 470ms
for 5 child classes  - instanceof:  375ms vs switch: 204ms

Quindi, instanceof è molto più lento, specialmente su un numero enorme di istruzioni if-else-if, tuttavia la differenza sarà trascurabile all'interno dell'applicazione reale.

import java.util.Date;

public class InstanceOfVsEnum {

    public static int c1, c2, c3, c4, c5, c6, c7, c8, c9, cA;

    public static class Handler {
        public enum Type { Type1, Type2, Type3, Type4, Type5, Type6, Type7, Type8, Type9, TypeA }
        protected Handler(Type type) { this.type = type; }
        public final Type type;

        public static void addHandlerInstanceOf(Handler h) {
            if( h instanceof H1) { c1++; }
            else if( h instanceof H2) { c2++; }
            else if( h instanceof H3) { c3++; }
            else if( h instanceof H4) { c4++; }
            else if( h instanceof H5) { c5++; }
            else if( h instanceof H6) { c6++; }
            else if( h instanceof H7) { c7++; }
            else if( h instanceof H8) { c8++; }
            else if( h instanceof H9) { c9++; }
            else if( h instanceof HA) { cA++; }
        }

        public static void addHandlerSwitch(Handler h) {
            switch( h.type ) {
                case Type1: c1++; break;
                case Type2: c2++; break;
                case Type3: c3++; break;
                case Type4: c4++; break;
                case Type5: c5++; break;
                case Type6: c6++; break;
                case Type7: c7++; break;
                case Type8: c8++; break;
                case Type9: c9++; break;
                case TypeA: cA++; break;
            }
        }
    }

    public static class H1 extends Handler { public H1() { super(Type.Type1); } }
    public static class H2 extends Handler { public H2() { super(Type.Type2); } }
    public static class H3 extends Handler { public H3() { super(Type.Type3); } }
    public static class H4 extends Handler { public H4() { super(Type.Type4); } }
    public static class H5 extends Handler { public H5() { super(Type.Type5); } }
    public static class H6 extends Handler { public H6() { super(Type.Type6); } }
    public static class H7 extends Handler { public H7() { super(Type.Type7); } }
    public static class H8 extends Handler { public H8() { super(Type.Type8); } }
    public static class H9 extends Handler { public H9() { super(Type.Type9); } }
    public static class HA extends Handler { public HA() { super(Type.TypeA); } }

    final static int cCycles = 10000000;

    public static void main(String[] args) {
        H1 h1 = new H1();
        H2 h2 = new H2();
        H3 h3 = new H3();
        H4 h4 = new H4();
        H5 h5 = new H5();
        H6 h6 = new H6();
        H7 h7 = new H7();
        H8 h8 = new H8();
        H9 h9 = new H9();
        HA hA = new HA();

        Date dtStart = new Date();
        for( int i = 0; i < cCycles; i++ ) {
            Handler.addHandlerInstanceOf(h1);
            Handler.addHandlerInstanceOf(h2);
            Handler.addHandlerInstanceOf(h3);
            Handler.addHandlerInstanceOf(h4);
            Handler.addHandlerInstanceOf(h5);
            Handler.addHandlerInstanceOf(h6);
            Handler.addHandlerInstanceOf(h7);
            Handler.addHandlerInstanceOf(h8);
            Handler.addHandlerInstanceOf(h9);
            Handler.addHandlerInstanceOf(hA);
        }
        System.out.println("Instance of - " + (new Date().getTime() - dtStart.getTime()));

        dtStart = new Date();
        for( int i = 0; i < cCycles; i++ ) {
            Handler.addHandlerSwitch(h1);
            Handler.addHandlerSwitch(h2);
            Handler.addHandlerSwitch(h3);
            Handler.addHandlerSwitch(h4);
            Handler.addHandlerSwitch(h5);
            Handler.addHandlerSwitch(h6);
            Handler.addHandlerSwitch(h7);
            Handler.addHandlerSwitch(h8);
            Handler.addHandlerSwitch(h9);
            Handler.addHandlerSwitch(hA);
        }
        System.out.println("Switch of - " + (new Date().getTime() - dtStart.getTime()));
    }
}

instanceof è veramente veloce, prendendo solo alcune istruzioni della CPU.

Apparentemente, se una classe X non ha sottoclassi caricate (JVM lo sa), <=> può essere ottimizzato come:

     x instanceof X    
==>  x.getClass()==X.class  
==>  x.classID == constant_X_ID

Il costo principale è solo una lettura!

Se <=> ha sottoclassi caricate, sono necessarie alcune letture in più; sono probabilmente situati in una posizione così il costo aggiuntivo è anche molto basso.

Buone notizie a tutti!

instanceof sarà probabilmente più costoso di un semplice uguale nella maggior parte delle implementazioni del mondo reale (vale a dire, quelle in cui è realmente necessaria instanceof e non puoi semplicemente risolverlo sostituendo un metodo comune, come ogni libro di testo per principianti come suggerisce Demian sopra).

Perché? Perché ciò che probabilmente accadrà è che hai diverse interfacce, che forniscono alcune funzionalità (diciamo, interfacce x, ye z) e alcuni oggetti da manipolare che possono (o meno) implementare una di quelle interfacce ... ma non direttamente. Ad esempio, ho:

w estende x

A implementa w

B estende A

C estende B, implementa y

D estende C, implementa z

Supponiamo che stia elaborando un'istanza di D, l'oggetto d. Il calcolo (d istanza di x) richiede di prendere d.getClass (), scorrere le interfacce che implementa per sapere se si è ==x, e in caso contrario ricorrere in modo ricorsivo per tutti i loro antenati ... Nel nostro caso, se fai una prima esplorazione ampia di quell'albero, produce almeno 8 confronti, supponendo che y e z non estendano nulla ...

È probabile che la complessità di un albero di derivazione del mondo reale sia maggiore. In alcuni casi, JIT può ottimizzare la maggior parte di esso, se è in grado di risolvere in anticipo d come essere, in tutti i casi possibili, un'istanza di qualcosa che estende x. Realisticamente, tuttavia, attraverserai quell'albero per la maggior parte del tempo.

Se questo diventa un problema, suggerirei invece di utilizzare una mappa del gestore, collegando la classe concreta dell'oggetto a una chiusura che esegue la gestione. Rimuove la fase di attraversamento dell'albero a favore di una mappatura diretta. Tuttavia, fai attenzione che se hai impostato un gestore per C.class, il mio oggetto d sopra non verrà riconosciuto.

ecco i miei 2 centesimi, spero che mi aiutino ...

'instanceof' è in realtà un operatore, come + o -, e credo che abbia una sua istruzione bytecode JVM. Dovrebbe essere molto veloce.

Non dovrei farlo se hai un interruttore in cui stai testando se un oggetto è un'istanza di qualche sottoclasse, potrebbe essere necessario rielaborare il tuo progetto. Prendi in considerazione l'idea di inserire il comportamento specifico della sottoclasse nelle sottoclassi stesse.

Instanceof è molto veloce. Si riduce a un bytecode utilizzato per il confronto dei riferimenti di classe. Prova alcuni milioni di istanze in un ciclo e vedi di persona.

Demian e Paul menzionano un buon punto; tuttavia , il posizionamento del codice da eseguire dipende davvero da come si desidera utilizzare i dati ...

Sono un grande fan dei piccoli oggetti dati che possono essere utilizzati in molti modi. Se segui l'approccio di override (polimorfico), i tuoi oggetti possono essere usati solo & Quot; solo andata & Quot ;.

Qui entrano in gioco gli schemi ...

Puoi usare il doppio invio (come nel modello visitatore) per chiedere a ciascun oggetto di " chiamarti " passando se stesso - questo risolverà il tipo di oggetto. Comunque (di nuovo) avrai bisogno di una classe che può " fare cose " con tutti i possibili sottotipi.

Preferisco usare un modello di strategia, in cui è possibile registrare le strategie per ogni sottotipo che si desidera gestire. Qualcosa di simile al seguente. Nota che questo aiuta solo per corrispondenze esatte di tipi, ma ha il vantaggio di essere estensibile: i collaboratori di terze parti possono aggiungere i propri tipi e gestori. (È utile per framework dinamici come OSGi, in cui è possibile aggiungere nuovi bundle)

Speriamo che questo ispirerà alcune altre idee ...

package com.javadude.sample;

import java.util.HashMap;
import java.util.Map;

public class StrategyExample {
    static class SomeCommonSuperType {}
    static class SubType1 extends SomeCommonSuperType {}
    static class SubType2 extends SomeCommonSuperType {}
    static class SubType3 extends SomeCommonSuperType {}

    static interface Handler<T extends SomeCommonSuperType> {
        Object handle(T object);
    }

    static class HandlerMap {
        private Map<Class<? extends SomeCommonSuperType>, Handler<? extends SomeCommonSuperType>> handlers_ =
            new HashMap<Class<? extends SomeCommonSuperType>, Handler<? extends SomeCommonSuperType>>();
        public <T extends SomeCommonSuperType> void add(Class<T> c, Handler<T> handler) {
            handlers_.put(c, handler);
        }
        @SuppressWarnings("unchecked")
        public <T extends SomeCommonSuperType> Object handle(T o) {
            return ((Handler<T>) handlers_.get(o.getClass())).handle(o);
        }
    }

    public static void main(String[] args) {
        HandlerMap handlerMap = new HandlerMap();

        handlerMap.add(SubType1.class, new Handler<SubType1>() {
            @Override public Object handle(SubType1 object) {
                System.out.println("Handling SubType1");
                return null;
            } });
        handlerMap.add(SubType2.class, new Handler<SubType2>() {
            @Override public Object handle(SubType2 object) {
                System.out.println("Handling SubType2");
                return null;
            } });
        handlerMap.add(SubType3.class, new Handler<SubType3>() {
            @Override public Object handle(SubType3 object) {
                System.out.println("Handling SubType3");
                return null;
            } });

        SubType1 subType1 = new SubType1();
        handlerMap.handle(subType1);
        SubType2 subType2 = new SubType2();
        handlerMap.handle(subType2);
        SubType3 subType3 = new SubType3();
        handlerMap.handle(subType3);
    }
}

instanceof è molto efficiente, quindi è improbabile che la tua performance ne risenta. Tuttavia, l'utilizzo di un sacco di esempio di ciò suggerisce un problema di progettazione.

Se puoi usare xClass == String.class, questo è più veloce. Nota: non hai bisogno di instanceof per le classi finali.

È difficile dire come una determinata JVM implementa l'istanza di, ma nella maggior parte dei casi, anche gli oggetti sono paragonabili a strutture e classi e ogni struttura di oggetto ha un puntatore alla struttura di classe di cui è un'istanza. Quindi in realtà instanceof per

if (o instanceof java.lang.String)

potrebbe essere veloce come il seguente codice C

if (objectStruct->iAmInstanceOf == &java_lang_String_class)

supponendo che sia stato creato un compilatore JIT e svolga un lavoro dignitoso.

Considerando che questo sta solo accedendo a un puntatore, ottenendo un puntatore a un certo offset a cui punta il puntatore e confrontandolo con un altro puntatore (che è sostanzialmente lo stesso del test con numeri a 32 bit uguali), direi che l'operazione può effettivamente essere molto veloce.

Tuttavia, non è necessario, dipende molto dalla JVM. Tuttavia, se ciò si rivelasse l'operazione di collo di bottiglia nel tuo codice, considererei l'implementazione di JVM piuttosto scadente. Anche uno che non ha un compilatore JIT e interpreta solo il codice dovrebbe essere in grado di eseguire un test di istanza praticamente in pochissimo tempo.

InstanceOf è un avvertimento di un design orientato agli oggetti scadente.

Le JVM attuali indicano che la istanza di non è di per sé una preoccupazione per le prestazioni. Se ti ritrovi a usarlo molto, soprattutto per le funzionalità di base, è probabilmente il momento di esaminare il design. Le prestazioni (e la semplicità / manutenibilità) ottenute dal refactoring per una progettazione migliore supereranno di gran lunga qualsiasi ciclo effettivo del processore speso nell'effettiva istanza di chiamata.

Per fornire un semplicistico esempio di programmazione semplicissima.

if (SomeObject instanceOf Integer) {
  [do something]
}
if (SomeObject instanceOf Double) {
  [do something different]
}

È un'architettura scadente una scelta migliore sarebbe stata quella di avere SomeObject come classe padre di due classi figlio in cui ogni classe figlio sovrascrive un metodo (doSomething) in modo che il codice appaia come tale:

Someobject.doSomething();

Ti ricontatterò sull'istanza di performance. Ma un modo per evitare del tutto il problema (o la sua mancanza) sarebbe quello di creare un'interfaccia genitore per tutte le sottoclassi su cui è necessario eseguire istanza di. L'interfaccia sarà un super set di tutti i metodi nelle sottoclassi per le quali è necessario eseguire il controllo dell'istanza. Laddove un metodo non si applica a una sottoclasse specifica, è sufficiente fornire un'implementazione fittizia di questo metodo. Se non ho frainteso il problema, è così che ho risolto il problema in passato.

Generalmente il motivo per cui " istanza di " l'operatore è disapprovato in un caso del genere (in cui l'istanza di sta verificando le sottoclassi di questa classe di base) è perché ciò che dovresti fare è spostare le operazioni in un metodo e sostituirlo per le sottoclassi appropriate. Ad esempio, se hai:

if (o instanceof Class1)
   doThis();
else if (o instanceof Class2)
   doThat();
//...

Puoi sostituirlo con

o.doEverything();

e quindi l'implementazione di " doEverything () " in Class1 chiama " doThis () " ;, e in Class2 chiama " doThat () " ;, e così via.

Nella moderna versione Java l'operatore instanceof è più veloce come una semplice chiamata di metodo. Ciò significa:

if(a instanceof AnyObject){
}

è più veloce di:

if(a.getType() == XYZ){
}

Un'altra cosa è se devi mettere in cascata molte istanze di. Quindi uno switch che chiama solo una volta getType () è più veloce.

Se la velocità è il tuo unico obiettivo, l'utilizzo delle costanti int per identificare le sottoclassi sembra radere un millisecondo del tempo

static final int ID_A = 0;
static final int ID_B = 1;
abstract class Base {
  final int id;
  Base(int i) { id = i; }
}
class A extends Base {
 A() { super(ID_A); }
}
class B extends Base {
 B() { super(ID_B); }
}
...
Base obj = ...
switch(obj.id) {
case  ID_A: .... break;
case  ID_B: .... break;
}

design OO terribile, ma se l'analisi delle prestazioni indica che questo è il punto in cui si trova il collo di bottiglia, forse. Nel mio codice, il codice di spedizione richiede il 10% del tempo di esecuzione totale e questo potrebbe aver contribuito a un miglioramento della velocità totale dell'1%.

Scrivo un test delle prestazioni basato su archetipo jmh-java-benchmark: 2.21. JDK è openjdk e la versione è 1.8.0_212. La macchina di prova è mac pro. Il risultato del test è:

Benchmark                Mode  Cnt    Score   Error   Units
MyBenchmark.getClasses  thrpt   30  510.818 ± 4.190  ops/us
MyBenchmark.instanceOf  thrpt   30  503.826 ± 5.546  ops/us

Il risultato mostra che: getClass è migliore di instanceOf, il che è contrario ad altri test. Tuttavia, non so perché.

Il codice di prova è di seguito:

public class MyBenchmark {

public static final Object a = new LinkedHashMap<String, String>();

@Benchmark
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public boolean instanceOf() {
    return a instanceof Map;
}

@Benchmark
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public boolean getClasses() {
    return a.getClass() == HashMap.class;
}

public static void main(String[] args) throws RunnerException {
    Options opt =
        new OptionsBuilder().include(MyBenchmark.class.getSimpleName()).warmupIterations(20).measurementIterations(30).forks(1).build();
    new Runner(opt).run();
}
}

Dovresti misurare / profilo se è davvero un problema di prestazioni nel tuo progetto. Se lo è, consiglierei una riprogettazione, se possibile. Sono abbastanza sicuro che non puoi battere l'implementazione nativa della piattaforma (scritta in C). In questo caso dovresti anche considerare l'eredità multipla.

Dovresti dire di più sul problema, forse potresti usare un negozio associativo, ad es. una mappa < Class, Object > se sei interessato solo ai tipi concreti.

Per quanto riguarda la nota di Peter Lawrey secondo cui non hai bisogno di istanza di classi finali e puoi semplicemente usare una parità di riferimento, stai attento! Anche se le classi finali non possono essere estese, non è garantito che vengano caricate dallo stesso classloader. Usa x.getClass () == SomeFinal.class o il suo simile solo se sei assolutamente sicuro che ci sia un solo classloader in gioco per quella sezione di codice.

Preferisco anche un approccio enum, ma userei una classe base astratta per forzare le sottoclassi a implementare il metodo getType().

public abstract class Base
{
  protected enum TYPE
  {
    DERIVED_A, DERIVED_B
  }

  public abstract TYPE getType();

  class DerivedA extends Base
  {
    @Override
    public TYPE getType()
    {
      return TYPE.DERIVED_A;
    }
  }

  class DerivedB extends Base
  {
    @Override
    public TYPE getType()
    {
      return TYPE.DERIVED_B;
    }
  }
}

Ho pensato che valesse la pena sottoporre un contro-esempio al consenso generale in questa pagina che " instanceof " non è abbastanza costoso di cui preoccuparsi. Ho scoperto di avere del codice in un ciclo interno che (in un tentativo storico di ottimizzazione) ha fatto

if (!(seq instanceof SingleItem)) {
  seq = seq.head();
}

dove chiamando head () su un SingleItem restituisce il valore invariato. Sostituzione del codice con

seq = seq.head();

mi dà una velocità da 269ms a 169ms, nonostante ci siano cose abbastanza pesanti nel loop, come la conversione da stringa a doppia. Naturalmente è possibile che l'accelerazione sia più dovuta all'eliminazione del ramo condizionale che all'eliminazione dell'istanza dell'operatore stesso; ma ho pensato che valesse la pena menzionarlo.

Ti stai concentrando sulla cosa sbagliata. La differenza tra instanceof e qualsiasi altro metodo per verificare la stessa cosa probabilmente non sarebbe nemmeno misurabile. Se le prestazioni sono fondamentali, probabilmente Java è la lingua sbagliata. Il motivo principale è che non puoi controllare quando la VM decide di voler raccogliere la spazzatura, il che può portare la CPU al 100% per diversi secondi in un programma di grandi dimensioni (MagicDraw 10 è stato ottimo per questo). A meno che tu non abbia il controllo di tutti i computer su cui verrà eseguito questo programma, non puoi garantire su quale versione di JVM sarà attiva, e molti dei più vecchi avevano grossi problemi di velocità. Se si tratta di una piccola app, potresti essere d'accordo con Java, ma se stai costantemente leggendo e scartando dati, noterai quando il GC entrerà in funzione.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top