J'ai le problème suivant: supposons que j'ai un répertoire contenant des répertoires compressés contenant plusieurs fichiers, stockés sur HDFS. Je veux créer un RDD composé d’objets de type T, c’est-à-dire:
context = new JavaSparkContext(conf);
JavaPairRDD<String, String> filesRDD = context.wholeTextFiles(inputDataPath);
JavaPairRDD<String, String> filesRDD = context.wholeTextFiles(inputDataPath);
JavaRDD<T> processingFiles = filesRDD.map(fileNameContent -> {
// The name of the file
String fileName = fileNameContent._1();
// The content of the file
String content = fileNameContent._2();
// Class T has a constructor of taking the filename and the content of each
// processed file (as two strings)
T t = new T(content, fileName);
return t;
});
Maintenant, lorsque inputDataPath
est un répertoire contenant des fichiers, cela fonctionne parfaitement, c’est-à-dire quand il s’agit de quelque chose comme:
String inputDataPath = "hdfs://some_path/*/*/"; // because it contains subfolders
Mais, quand il y a un tgz contenant plusieurs fichiers, le contenu du fichier (fileNameContent._2()
) me procure une chaîne binaire inutile (tout à fait prévu). J'ai trouvé une question similaire sur SO , mais ce n'est pas le même cas, car la solution consiste à ce que chaque compression soit composée d'un seul fichier et, dans mon cas, de nombreux autres fichiers que je vouloir lire individuellement sous forme de fichiers entiers. J'ai aussi trouvé une question à propos de wholeTextFiles
, mais cela ne fonctionne pas dans mon cas.
Des idees pour faire cela?
EDIT:
J'ai essayé avec le lecteur de ici (essayant de tester le lecteur de ici , comme dans la fonction testTarballWithFolders()
), mais chaque fois que j'appelle
TarballReader tarballReader = new TarballReader(fileName);
et je reçois NullPointerException
:
Java.lang.NullPointerException
at Java.util.Zip.InflaterInputStream.<init>(InflaterInputStream.Java:83)
at Java.util.Zip.GZIPInputStream.<init>(GZIPInputStream.Java:77)
at Java.util.Zip.GZIPInputStream.<init>(GZIPInputStream.Java:91)
at utils.TarballReader.<init>(TarballReader.Java:61)
at main.SparkMain.lambda$0(SparkMain.Java:105)
at main.SparkMain$$Lambda$18/1667100242.call(Unknown Source)
at org.Apache.spark.api.Java.JavaPairRDD$$anonfun$toScalaFunction$1.apply(JavaPairRDD.scala:1015)
at scala.collection.Iterator$$anon$11.next(Iterator.scala:328)
at scala.collection.Iterator$class.foreach(Iterator.scala:727)
at scala.collection.AbstractIterator.foreach(Iterator.scala:1157)
at scala.collection.generic.Growable$class.$plus$plus$eq(Growable.scala:48)
at scala.collection.mutable.ArrayBuffer.$plus$plus$eq(ArrayBuffer.scala:103)
at scala.collection.mutable.ArrayBuffer.$plus$plus$eq(ArrayBuffer.scala:47)
at scala.collection.TraversableOnce$class.to(TraversableOnce.scala:273)
at scala.collection.AbstractIterator.to(Iterator.scala:1157)
at scala.collection.TraversableOnce$class.toBuffer(TraversableOnce.scala:265)
at scala.collection.AbstractIterator.toBuffer(Iterator.scala:1157)
at scala.collection.TraversableOnce$class.toArray(TraversableOnce.scala:252)
at scala.collection.AbstractIterator.toArray(Iterator.scala:1157)
at org.Apache.spark.rdd.RDD$$anonfun$collect$1$$anonfun$12.apply(RDD.scala:927)
at org.Apache.spark.rdd.RDD$$anonfun$collect$1$$anonfun$12.apply(RDD.scala:927)
at org.Apache.spark.SparkContext$$anonfun$runJob$5.apply(SparkContext.scala:1858)
at org.Apache.spark.SparkContext$$anonfun$runJob$5.apply(SparkContext.scala:1858)
at org.Apache.spark.scheduler.ResultTask.runTask(ResultTask.scala:66)
at org.Apache.spark.scheduler.Task.run(Task.scala:89)
at org.Apache.spark.executor.Executor$TaskRunner.run(Executor.scala:214)
at Java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.Java:1142)
at Java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.Java:617)
at Java.lang.Thread.run(Thread.Java:745)
La ligne 105 dans MainSpark
est celle que j'ai affichée en haut de mon édition du message, et la ligne 61 de TarballReader
est
GZIPInputStream gzip = new GZIPInputStream(in);
qui donne une valeur nulle pour le flux d'entrée in
dans la ligne supérieure:
InputStream in = this.getClass().getResourceAsStream(tarball);
Suis-je sur le bon chemin ici? Si oui, comment puis-je continuer? Pourquoi ai-je cette valeur nulle et comment puis-je la résoudre?
Une solution possible consiste à lire les données avec binaryFiles
et à extraire le contenu manuellement.
Scala :
import org.Apache.commons.compress.compressors.gzip.GzipCompressorInputStream
import org.Apache.commons.compress.archivers.tar.TarArchiveInputStream
import org.Apache.spark.input.PortableDataStream
import scala.util.Try
import Java.nio.charset._
def extractFiles(ps: PortableDataStream, n: Int = 1024) = Try {
val tar = new TarArchiveInputStream(new GzipCompressorInputStream(ps.open))
Stream.continually(Option(tar.getNextTarEntry))
// Read until next exntry is null
.takeWhile(_.isDefined)
// flatten
.flatMap(x => x)
// Drop directories
.filter(!_.isDirectory)
.map(e => {
Stream.continually {
// Read n bytes
val buffer = Array.fill[Byte](n)(-1)
val i = tar.read(buffer, 0, n)
(i, buffer.take(i))}
// Take as long as we've read something
.takeWhile(_._1 > 0)
.map(_._2)
.flatten
.toArray})
.toArray
}
def decode(charset: Charset = StandardCharsets.UTF_8)(bytes: Array[Byte]) =
new String(bytes, StandardCharsets.UTF_8)
sc.binaryFiles("somePath").flatMapValues(x =>
extractFiles(x).toOption).mapValues(_.map(decode()))
libraryDependencies += "org.Apache.commons" % "commons-compress" % "1.11"
Exemple d'utilisation complète avec Java: https://bitbucket.org/zero323/spark-multifile-targz-extract/src
Python :
import tarfile
from io import BytesIO
def extractFiles(bytes):
tar = tarfile.open(fileobj=BytesIO(bytes), mode="r:gz")
return [tar.extractfile(x).read() for x in tar if x.isfile()]
(sc.binaryFiles("somePath")
.mapValues(extractFiles)
.mapValues(lambda xs: [x.decode("utf-8") for x in xs]))
Une légère amélioration par rapport à la réponse acceptée est de changer
Option(tar.getNextTarEntry)
à
Try(tar.getNextTarEntry).toOption.filter( _ != null)
lutter avec .tar.gz
s mal formé/tronqué de manière robuste.
BTW, y at-il quelque chose de spécial à propos de la taille du tableau de mémoire tampon? Serait-il plus rapide en moyenne s'il était plus proche de la taille moyenne du fichier, peut-être 500 Ko dans mon cas? Ou est-ce le ralentissement que je vois plus probablement la surcharge de Stream
par rapport à une boucle while
qui était plus Java-ish, je suppose.