Beispiel für die Verwendung von Iterator in Java (Bonus: Super praktisch! Iterator synthetisieren ~ Machen Sie so etwas wie flatMap zu Iterator)

Einführung

Iterator ist nützlich bei der Verarbeitung großer Datenmengen. Insbesondere bei der Verarbeitung einer großen Datenmenge, die eine hohe Berechnungszeit und Speicherauslastung erfordert, ist der Speicher nicht ausreichend, wenn alle Daten im Voraus konvertiert und in eine Sammlung wie List konvertiert werden, und OutOfMemory oder GC wird ausgeführt Häufige Vorkommnisse können den Prozess erheblich verlangsamen.

Wenn Sie keinen Iterator verwenden

Angenommen, Sie haben eine Schnittstelle, die Listendaten aus einem Datenspeicher wie folgt empfängt: (Es wird davon ausgegangen, dass die Primärschlüssel in der Reihenfolge der Datensatzregistrierung angeordnet sind.)

Datenspeicher.java


interface MyRepository<E>{
  /**
  *Der Wert des Primärschlüssels ist[fromInclusive, untilInclusive]Gibt eine Liste von Datensätzen im Bereich von zurück
  */
  List<E> findByIdRange(long fromInclusive,long untilInclusive);
}

Wenn Sie beispielsweise alle an einem Tag geschriebenen Datensätze (einen Bereich eines bestimmten Primärschlüssels) konvertieren und in einen anderen Datenspeicher eintauchen möchten, können Sie beispielsweise mithilfe der Stream-API wie folgt schreiben. ..

Gehorsam.java


MyRepository<MyRecord> myRepository;
void extractTransferAndLoad(long fromInclusive,long untilInclusive){
  //Bild der parallelen Ausführung einer lastintensiven Verarbeitung
  myRepository.findByIdRange(fromInclusive,untilInclusive).parallelStream().map(this::transfer).foreach(this::load);
}

Wenn Sie jedoch über eine große Datenmenge verfügen, kann "myRepository.findByIdRange (long, long)" OutOfMemory verursachen oder viel Speicherplatz beanspruchen und die Leistung beeinträchtigen.

Versuchen Sie es mit dem Iterator

Daher werde ich meinen eigenen Iterator erstellen. Es gibt zwei Schnittstellen, an die Sie denken müssen, um einen Iterator zu erstellen.

Schnittstelle zum Mastering von Iteratoren.java


package java.util;
interface Iterator<A>{
  boolean hasNext();
  A next();
}

package java.lang;
interface Iterable<A>{
  Iterator<A> iterator();
}

Schreiben wir eine Klasse, die den obigen findByIdRange separat mit einem Iterator erhält.

Iterator-Implementierung.java


class MyIterableRepository<A> implements Iterable<List<A>>{
  final private MyRepository<A> myRepository; 
  final private long fromInclusive;
  final private long untilInclusive;
  final private int splitSize; //split by splitSize, um eine Liste zurückzugeben
  public MyIterableRepository(MyRepository<A> myRepository,long fromInclusive,long untilInclusive,int splitSize){
    //Konstruktor nur für DI ... weggelassen
  }
  public Iterator<List<A>> iterator() {
    return new Iterator<List<A>>() {
      long currentFromInclusive = fromInclusive;
      public boolean hasNext() {
        return untilInclusive >= currentFromInclusive;
      }
      public List<A> next() {
        long currentUntilInclusive = Math.min(untilInclusive, nextFromInclusive+splitSize-1);
        List<A> ret = myRepository.findById(currentFromInclusive,currentUntilInclusive);
        currentFromInclusive = currentUntilInclusive+1;
        return ret;
      }
    };
  }
}

Jetzt haben Sie einen Iterator. Es ist ein einfaches Muster, also ist es einfach, wenn Sie sich daran gewöhnen und sich daran erinnern. Wenn Iterable implementiert ist, kann der Aufrufer die erweiterte for-Anweisung verwenden, um Folgendes zu schreiben:

Anrufer.java


MyRepository<MyRecord> myRepository;
void extractTransferAndLoad(long fromInclusive,long untilInclusive){
  MyIterableRepository<MyData> myIterableRepository = new MyIterableRepository(myRepository,fromInclusive,untilInclusive, MagicNumbers.SPLIT_SIZE);
  for(List<MyData> myDataList : myIterableRepository){ 
    //Bild der parallelen Ausführung einer lastintensiven Verarbeitung
    myDataList.parallelStream().map(this::transfer).forEach(this::load);
  }
}

Durch diese Änderung können Sie die Anzahl der gleichzeitig generierten Objekte reduzieren. Am Ende der for-Anweisung kann myDataList (und sein Referenzziel) über GC geändert werden, und Sie können vermeiden, in OoM zu fallen. Ich werde.

Extra: Super praktisch! Iteratorsynthese

Es gibt Zeiten, in denen Sie einen Iterator synthetisieren möchten, z. B. wenn Sie Daten aus mehreren Datenquellen abrufen müssen. Wenn Sie beispielsweise über Benutzerinformationen, einen mit dem Benutzer verknüpften Kaufverlauf und einen mit dem Benutzer verknüpften Buchungsverlauf verfügen und eine integrierte statische Seite in der Stapelverarbeitung erstellen, doppelte Erweiterung für Es ist nur ein Satz (unten ein Bild des Inhalts des Prozesses, den Sie realisieren möchten)

Ich möchte Folgendes als Verarbeitung ausführen.java


Iterable<MyRepository2> repositories;
void extractTransferAndLoad(MyTime fromInclusive,MyTime untilInclusive){
  for(MyRepository2 repository: repositories){
    Iterable<List<UserId>> ittrable = new MyIterableRepository2(repository,fromInclusive,untilInclusive,42).getUserIdsIterable();
    for(List<UserId> userIds : ittrable){
      userIds.parallelStream().filter( /*Ignorieren Sie die ID, die Sie bereits verarbeitet haben*/ ).map( /*Konvertieren*/ ).forEach( /*Export*/ );
    }
  }
}

Wenn Sie den Prozess erhalten, einen Iterator zu empfangen und zu konvertieren, und Sie ihn aufrufen müssen, sind Sie in Schwierigkeiten. (Beispiel eines Angerufenen unten)

Angerufene.java


//Aufgerufene Methode
void extractTransferAndLoad2(Iterable<List<UserId>> userIdsListIterable){
  for(List<UserId> userIds : userIdsListIterable){
      userIds.parallelStream().filter( /*Ignorieren Sie die ID, die Sie bereits verarbeitet haben*/ ).map( /*Konvertieren*/ ).forEach( /*Export*/ );
  }
}

//Bild von dem, was der Anrufer tun möchte
extractTransferAndLoad2(repositories.flatMap(repository -> new MyIterableRepository2(repository,fromInclusive,untilInclusive,42).getUserIdsIterable()));
//Iterator hat keine flatMap, daher können Sie dies nicht tun.

Also, wenn Sie die Methode verwenden, den super erstaunlichen Iterator zu synthetisieren, den ich diesmal dachte

Iteratorsynthese.java


public class IteratorUtils {
  public static <A,B> Iterator<B> composedIterator(Iterator<A> aittr, Function<A,Iterator<B>> func){		
    return new Iterator<B>(){

      Iterator<B> bittr = Collections.emptyIterator();
			
      public boolean hasNext() {
        while(!bittr.hasNext() && aittr.hasNext()){
          bittr = func.apply(aittr.next());
        }
        return bittr.hasNext();
      }
      public B next() {
        while(!bittr.hasNext() && aittr.hasNext()){
          bittr = func.apply(aittr.next());
        }
        return bittr.next();
      }			
    };
  }
}

Dann

Beim Anruf.java


extractTransferAndLoad2(IteratorUtils.composedIterator(repositories, repository -> new MyIterableRepository2(repository,fromInclusive,untilInclusive,42).getUserIdsIterable()));

Sie können den Iterator als synthetisieren und übergeben! !!

Übrigens dachte ich, wenn ich Iterator stoppen und alles mit Stream zurückgeben würde, wäre es eine Aufnahme mit flatMap, aber es gibt keine andere Möglichkeit, einen endlichen Stream mit Java zu erstellen, als Spliterator auf Iterator-Basis zu erstellen. Es tut mir leid. Es konnte jedoch nicht realisiert werden.

Recommended Posts

Beispiel für die Verwendung von Iterator in Java (Bonus: Super praktisch! Iterator synthetisieren ~ Machen Sie so etwas wie flatMap zu Iterator)
Ich möchte so etwas wie "cls" in Java machen
Implementierung einer ähnlichen Funktion in Java
Implementieren Sie so etwas wie einen Stack in Java
Ein Beispiel, bei dem die Verwendung von Addition schneller ist als die Verwendung von StringBuilder (Java)
So erhalten Sie den Klassennamen des Arguments von LoggerFactory.getLogger, wenn Sie SLF4J in Java verwenden