Exemple d'utilisation d'Iterator en Java (Bonus: Super pratique! Synthétiser Iterator ~ Faites quelque chose comme flatMap to Iterator)

introduction

Iterator est utile lors du traitement de grandes quantités de données. Surtout lors du traitement d'une grande quantité de données qui coûte beaucoup de temps de calcul et d'utilisation de la mémoire, si toutes les données sont converties à l'avance et converties en une collection telle que List, la mémoire sera insuffisante et OutOfMemory se produira, ou GC Des occurrences fréquentes peuvent ralentir considérablement le processus.

Lorsque vous n'utilisez pas d'itérateur

Par exemple, supposons que vous ayez une interface qui reçoit des données de liste d'un magasin de données comme suit: (On suppose que les clés primaires sont disposées dans l'ordre d'enregistrement des enregistrements)

Magasin de données.java


interface MyRepository<E>{
  /**
  *La valeur de la clé primaire est[fromInclusive, untilInclusive]Renvoie une liste d'enregistrements dans la plage de
  */
  List<E> findByIdRange(long fromInclusive,long untilInclusive);
}

Par exemple, lorsque vous souhaitez convertir tous les enregistrements écrits un jour (une plage d'une certaine clé primaire) et plonger dans un autre magasin de données, vous pouvez écrire comme suit à l'aide de l'API Stream, par exemple. ..

Obéissant.java


MyRepository<MyRecord> myRepository;
void extractTransferAndLoad(long fromInclusive,long untilInclusive){
  //Image d'un traitement intensif en charge en parallèle
  myRepository.findByIdRange(fromInclusive,untilInclusive).parallelStream().map(this::transfer).foreach(this::load);
}

Cependant, si vous avez une grande quantité de données, myRepository.findByIdRange (long, long) peut provoquer OutOfMemory, ou il peut consommer une grande quantité de mémoire et affecter les performances.

Essayez d'utiliser l'itérateur

Par conséquent, je vais créer mon propre itérateur. Il y a deux interfaces à retenir pour créer un itérateur.

Interface de maîtrise des itérateurs.java


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

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

Écrivons une classe qui obtient le findByIdRange ci-dessus séparément en utilisant un itérateur.

Implémentation d'itérateur.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 pour renvoyer une liste
  public MyIterableRepository(MyRepository<A> myRepository,long fromInclusive,long untilInclusive,int splitSize){
    //Constructeur juste pour DI ... omis
  }
  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;
      }
    };
  }
}

Vous avez maintenant un itérateur. C'est un modèle simple, donc c'est facile si vous vous y habituez et vous en souvenez. Lorsque Iterable est implémenté, l'appelant peut utiliser l'instruction Extended for pour écrire:

Votre interlocuteur.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){ 
    //Image d'un traitement intensif en charge en parallèle
    myDataList.parallelStream().map(this::transfer).forEach(this::load);
  }
}

En changeant de cette manière, vous pouvez réduire le nombre d'objets générés à la fois, et à la fin de l'instruction for, myDataList (et sa destination de référence) peut être modifiée à partir de GC, et vous pouvez éviter de tomber dans OoM. Je vais.

Extra: super pratique! Synthèse d'itérateur

Il y a des moments où vous souhaitez synthétiser un itérateur, par exemple lorsque vous devez récupérer des données à partir de plusieurs sources de données. Par exemple, si vous avez des informations sur l'utilisateur, un historique des achats associé à l'utilisateur et un historique de validation associé à l'utilisateur, et que vous créez une page statique intégrée dans le traitement par lots, double extension pour C'est juste une phrase (ci-dessous, une image du contenu du processus que vous souhaitez réaliser)

Je veux faire ce qui suit en tant que traitement.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( /*Ignorez l'ID que vous avez déjà traité*/ ).map( /*Convertir*/ ).forEach( /*Exportation*/ );
    }
  }
}

Si vous recevez le processus de réception d'un itérateur et de sa conversion, et que vous devez l'appeler, vous avez des problèmes. (Exemple d'appelé ci-dessous)

Appelé.java


//Méthode appelée
void extractTransferAndLoad2(Iterable<List<UserId>> userIdsListIterable){
  for(List<UserId> userIds : userIdsListIterable){
      userIds.parallelStream().filter( /*Ignorez l'ID que vous avez déjà traité*/ ).map( /*Convertir*/ ).forEach( /*Exportation*/ );
  }
}

//Image de ce que l'appelant veut faire
extractTransferAndLoad2(repositories.flatMap(repository -> new MyIterableRepository2(repository,fromInclusive,untilInclusive,42).getUserIdsIterable()));
//Iterator n'a pas de flatMap, vous ne pouvez donc pas faire cela.

Donc, si vous utilisez la méthode de synthèse de l'itérateur super incroyable que j'ai pensé cette fois

Synthèse d'itérateur.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();
      }			
    };
  }
}

Puis

Lors de l'appel.java


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

Vous pouvez synthétiser et passer l'itérateur comme! !!

Au fait, je pensais que si j'arrêtais Iterator et retournais tout avec Stream, ce serait un coup avec flatMap, mais il n'y a aucun moyen de créer un Stream fini avec Java autre que de créer Spliterator basé sur Iterator, donc je suis désolé. Cependant, cela n'a pas pu être réalisé.

Recommended Posts

Exemple d'utilisation d'Iterator en Java (Bonus: Super pratique! Synthétiser Iterator ~ Faites quelque chose comme flatMap to Iterator)
Je veux faire quelque chose comme "cls" en Java
Implémentation d'une fonction similaire en Java
Implémenter quelque chose comme une pile en Java
Un exemple où il est plus rapide d'utiliser l'addition que d'utiliser StringBuilder (Java)
Comment obtenir le nom de classe de l'argument de LoggerFactory.getLogger lors de l'utilisation de SLF4J en Java