Java迭代器spliterator

tags : java-collectionsjava

spliterator是java1.8引入的一種並行遍歷的機制,Iterator提供也提供了對集合數據進行遍歷的能力,但一個是順序遍歷,一個是並行遍歷。 數組

如上圖所示, Arrays的分割迭代函數有2種形式, spliterator(xxx []), spliterator(xxx [] , int, int), 具體的 xxx包括 int, long, double, T,下文就以 int類型做爲demo進行分析。

舉個栗子

Spliterator.OfInt sInt = Arrays.spliterator(arr);
	IntConsumer consumer = new IntConsumer() {
		
		@Override
		public void accept(int value) {
			// TODO Auto-generated method stub
			System.out.println(value);
		}
	};
	sInt.tryAdvance(consumer);
複製代碼

程序輸出:1微信

將代碼修改一下:ide

int [] arr = {1,2,3,4,5,6,7,8,9};
	Spliterator.OfInt sInt = Arrays.spliterator(arr, 2, 5);
	IntConsumer consumer = new IntConsumer() {
		
		@Override
		public void accept(int value) {
			// TODO Auto-generated method stub
			System.out.println(value);
		}
	};
	sInt.tryAdvance(consumer);
複製代碼

程序輸出:3 忽然有點感受,spliterator的第二個和第三個參數多是指原數組的起始和結束下標,再把代碼修改一下:函數

int [] arr = {1,2,3,4,5,6,7,8,9};
	Spliterator.OfInt sInt = Arrays.spliterator(arr, 2, 5);
	IntConsumer consumer = new IntConsumer() {
		
		@Override
		public void accept(int value) {
			// TODO Auto-generated method stub
			System.out.println(value);
		}
	};
	sInt.tryAdvance(consumer);
	sInt.tryAdvance(consumer);
	sInt.tryAdvance(consumer);
	sInt.tryAdvance(consumer);
	sInt.tryAdvance(consumer);
複製代碼

程序輸出:3 4 5 能夠肯定,spliterator在迭代時,第二個參數指向數組的起始下標(包括),第三個參數指向原數組的結束下標(不包括)。仍是進入代碼好好研究一下。oop

具體實現

如下是Arraysspliterator函數:測試

public static Spliterator.OfInt spliterator(int[] array, int startInclusive, int endExclusive) {
        return Spliterators.spliterator(array, startInclusive, endExclusive,
                                        Spliterator.ORDERED | Spliterator.IMMUTABLE);
    }
複製代碼

繼續跟蹤下Spliterators.spliteratorui

public static Spliterator.OfInt spliterator(int[] array, int fromIndex, int toIndex, int additionalCharacteristics) {
        checkFromToBounds(Objects.requireNonNull(array).length, fromIndex, toIndex);
        return new IntArraySpliterator(array, fromIndex, toIndex, additionalCharacteristics);
    }
複製代碼

已經到底層實現了,因爲具體實現時代碼量並不大,故能夠直接看下IntArraySpliterator類的源碼了this

static final class IntArraySpliterator implements Spliterator.OfInt {
        // 指向原數組的array
        private final int[] array;
        // 分組迭代的起始下標(包括)
        private int index;        
        // 分組迭代的結束下標(不包括)
        private final int fence;  // one past last index
        // 分組迭代的特徵
        private final int characteristics;

        
        public IntArraySpliterator(int[] array, int additionalCharacteristics) {
            this(array, 0, array.length, additionalCharacteristics);
        }

        
        public IntArraySpliterator(int[] array, int origin, int fence, int additionalCharacteristics) {
            this.array = array;
            this.index = origin;
            this.fence = fence;
            this.characteristics = additionalCharacteristics | Spliterator.SIZED | Spliterator.SUBSIZED;
        }

        @Override
        public OfInt trySplit() {
            int lo = index, mid = (lo + fence) >>> 1;
            return (lo >= mid)
                   ? null
                   : new IntArraySpliterator(array, lo, index = mid, characteristics);
        }

        @Override
        public void forEachRemaining(IntConsumer action) {
            int[] a; int i, hi; // hoist accesses and checks from loop
            if (action == null)
                throw new NullPointerException();
            if ((a = array).length >= (hi = fence) &&
                (i = index) >= 0 && i < (index = hi)) {
                do { action.accept(a[i]); } while (++i < hi);
            }
        }

        @Override
        public boolean tryAdvance(IntConsumer action) {
            if (action == null)
                throw new NullPointerException();
            if (index >= 0 && index < fence) {
                action.accept(array[index++]);
                return true;
            }
            return false;
        }

        @Override
        public long estimateSize() { return (long)(fence - index); }

        @Override
        public int characteristics() {
            return characteristics;
        }

        @Override
        public Comparator<? super Integer> getComparator() {
            if (hasCharacteristics(Spliterator.SORTED))
                return null;
            throw new IllegalStateException();
        }
    }
複製代碼

trySplit函數很好理解,將原始IntArraySpliterator的起始下標index和結束下標fence的和的1/2做爲新的分割迭代spliterator的結束下標,構造新的分割迭代對象,原始對象的index也修改成起始下標index和結束下標fence的和的1/2。spa

tryAdvance函數對index下標指向的元素執行accept操做而且index自增1,這就能夠解釋上面例子的結果了。

forEachReaming函數從index下標開始,對fence以前的每個元素執行accept操做。

estimateSize函數獲取剩餘尚未進行accept操做的元素的數量。

IntConsumer

public interface IntConsumer {
       
        void accept(int value);
       
        default IntConsumer andThen(IntConsumer after) {
            Objects.requireNonNull(after);
            return (int t) -> { accept(t); after.accept(t); };
        }
    }
複製代碼

在前面介紹分割迭代時已經講到accept操做,這個操做是IntConsumer接口定義的函數。IntConsumer定義兩個函數,acceptandThenaccept函數接收數組的元素,並對元素進行操做,可是操做自己不改變原數組元素的值;andThen接口容許提供一個新的IntConsumer對象,原對象執行完accept函數後會執行新的對象的accept函數,看下demo

int [] arr = {1,2,3,4,5,6,7,8,9};
	Spliterator.OfInt sInt = Arrays.spliterator(arr, 2, 5);
	IntConsumer consumer = new IntConsumer() {
		
		@Override
		public void accept(int value) {
			// TODO Auto-generated method stub
			System.out.println(value);
		}
	};
	sInt.tryAdvance(consumer.andThen(new IntConsumer() {
		
		@Override
		public void accept(int value) {
			// TODO Auto-generated method stub
			System.out.println("i am after");
		}
	}));
複製代碼

程序輸出:3 i am after

使用場景

既然說spliterator是並行遍歷機制,接下來給一個並行遍歷的demo,先定義一個SpliteratorThread:

public class SpliteratorThread<T> extends Thread {
	private Spliterator<T> mSpliterator;
	public SpliteratorThread(Spliterator<T> spliterator) {
		mSpliterator = spliterator;
	}
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		super.run();
		if (mSpliterator != null) {
			mSpliterator.forEachRemaining(new Consumer<T>() {

				@Override
				public void accept(T t) {
					// TODO Auto-generated method stub
					System.out.println( Thread.currentThread().getName() + "-" + t + " ");
				}
			});
		}
	}
}
複製代碼

而後是一個測試入口:

public class Client {
	public static void main(String [] args) {
		int [] arr = {1,2,3,4,5,6,7,8,9,10};
		Spliterator<Integer> spliterator0 = Arrays.spliterator(arr);
		Spliterator<Integer> spliterator1 = spliterator0.trySplit();
		Spliterator<Integer> spliterator2 = spliterator1.trySplit();
		Thread t0 = new SpliteratorThread<>(spliterator0);
		t0.setName("t0");
		
		Thread t1 = new SpliteratorThread<>(spliterator1);
		t1.setName("t1");
		
		Thread t2 = new SpliteratorThread<>(spliterator2);
		t2.setName("t2");
		
		t0.start();
		t1.start();
		t2.start();
	}
}
複製代碼

執行一下: t1-3 t2-1 t0-6 t2-2 t1-4 t0-7 t1-5 t0-8 t0-9 t0-10

對結果進行梳理一下,線程t0遍歷的元素:6,7,8,9,10;線程t1遍歷的元素:3,4,5;線程t2遍歷的元素:1,2。每一個線程內部元素的遍歷是有序的,可是線程的調度是無序的,以上結果顯示了3個線程並行遍歷的流程,這就是分割遍歷最經常使用的場景。

總結

  • 分割遍歷的主要目的是爲了實現並行遍歷
  • 分割遍歷是在原數組的基礎上進行遍歷,遍歷的過程當中不會對原數組的元素進行修改
  • 分割的規則是從待遍歷的範圍的中間位置進行分割
  • 執行tryAdvance以後待遍歷的位置後移一位

關注微信公衆號,最新技術乾貨實時推送

image
相關文章
相關標籤/搜索