Ben bir Streambilinmeyen numara (dosya sayısı açık olarak bilinmemektedir) uzaktan depolanan JSON dosyaları heterojen bir dizi işleme paralelleştirmek için kullanmak istiyorum . Dosyaların boyutu, dosya başına 1 JSON kaydından diğer bazı dosyalarda 100.000 kayda kadar büyük ölçüde değişebilir. Bu durumda bir JSON kaydı , dosyada bir satır olarak temsil edilen bağımsız bir JSON nesnesi anlamına gelir.
Bunun için Akışları gerçekten kullanmak istiyorum ve bu yüzden bunu uyguladım Spliterator:
public abstract class JsonStreamSpliterator<METADATA, RECORD> extends AbstractSpliterator<RECORD> {
abstract protected JsonStreamSupport<METADATA> openInputStream(String path);
abstract protected RECORD parse(METADATA metadata, Map<String, Object> json);
private static final int ADDITIONAL_CHARACTERISTICS = Spliterator.IMMUTABLE | Spliterator.DISTINCT | Spliterator.NONNULL;
private static final int MAX_BUFFER = 100;
private final Iterator<String> paths;
private JsonStreamSupport<METADATA> reader = null;
public JsonStreamSpliterator(Iterator<String> paths) {
this(Long.MAX_VALUE, ADDITIONAL_CHARACTERISTICS, paths);
}
private JsonStreamSpliterator(long est, int additionalCharacteristics, Iterator<String> paths) {
super(est, additionalCharacteristics);
this.paths = paths;
}
private JsonStreamSpliterator(long est, int additionalCharacteristics, Iterator<String> paths, String nextPath) {
this(est, additionalCharacteristics, paths);
open(nextPath);
}
@Override
public boolean tryAdvance(Consumer<? super RECORD> action) {
if(reader == null) {
String path = takeNextPath();
if(path != null) {
open(path);
}
else {
return false;
}
}
Map<String, Object> json = reader.readJsonLine();
if(json != null) {
RECORD item = parse(reader.getMetadata(), json);
action.accept(item);
return true;
}
else {
reader.close();
reader = null;
return tryAdvance(action);
}
}
private void open(String path) {
reader = openInputStream(path);
}
private String takeNextPath() {
synchronized(paths) {
if(paths.hasNext()) {
return paths.next();
}
}
return null;
}
@Override
public Spliterator<RECORD> trySplit() {
String nextPath = takeNextPath();
if(nextPath != null) {
return new JsonStreamSpliterator<METADATA,RECORD>(Long.MAX_VALUE, ADDITIONAL_CHARACTERISTICS, paths, nextPath) {
@Override
protected JsonStreamSupport<METADATA> openInputStream(String path) {
return JsonStreamSpliterator.this.openInputStream(path);
}
@Override
protected RECORD parse(METADATA metaData, Map<String,Object> json) {
return JsonStreamSpliterator.this.parse(metaData, json);
}
};
}
else {
List<RECORD> records = new ArrayList<RECORD>();
while(tryAdvance(records::add) && records.size() < MAX_BUFFER) {
// loop
}
if(records.size() != 0) {
return records.spliterator();
}
else {
return null;
}
}
}
}
Sahip olduğum sorun, Akış ilk başta güzel bir şekilde paralel olsa da, en sonunda en büyük dosyanın tek bir iş parçacığında işlenmeye bırakılmasıdır. Proksimal nedenin iyi belgelendiğine inanıyorum: ayırıcı "dengesiz".
Daha somut olarak, trySplityöntemin Stream.forEachyaşam döngüsünde belirli bir noktadan sonra çağrılmadığı anlaşılır , bu nedenle sonunda küçük partileri dağıtmak için ekstra mantık trySplitnadiren yürütülür.
TrySplit'ten dönen tüm ayırıcıların aynı pathsyineleyiciyi nasıl paylaştığına dikkat edin . Bunun tüm bölücülerdeki işi dengelemek için gerçekten akıllıca bir yol olduğunu düşündüm, ancak tam paralelliğe ulaşmak için yeterli değildi.
Paralel işlemenin önce dosyalar arasında ilerlemesini istiyorum ve daha sonra birkaç büyük dosya bölünmeye bırakıldığında, kalan dosyaların parçaları arasında paralellik yapmak istiyorum. elseSonunda bloğun amacı buydu trySplit.
Bu sorunun kolay / basit / kanonik bir yolu var mı?
Long.MAX_VALUEaşırı ve gereksiz bölünmeye neden olurken, daha Long.MAX_VALUEfazla bölünmenin durmasına neden olmaktan başka bir tahmin , paralelliği öldürür. Doğru tahminlerin bir karışımını döndürmek akıllı optimizasyonlara yol açıyor gibi görünmüyor.
AbstractSpliteratorama geçersiz kılıyorsunuz . Bundan sonra , boyut tahmini, bölünmüş elemanların sayısıyla azaltılmalıdır. trySplit()Long.MAX_VALUEtrySplit()trySplit()