Akka之Flow相關API總結

(1)viaMat[T, Mat2, Mat3](flow: Graph[FlowShape[Out, T], Mat2])(combine: (Mat, Mat2) ? Mat3): ReprMat[T, Mat3]
通過附加給定的步驟轉換此Flow。

combine函數用于組合此流和另外一個流的物化值,并放入Flow結果的物化值。

one of the values.建議使用內部優化 Keep.leftKeep.right 保持, 而不是手動編寫傳入其中一個值的函數。

(2)toMat[Mat2, Mat3](sink: Graph[SinkShape[Out], Mat2])(combine: (Mat, Mat2) ? Mat3): Sink[In, Mat3]
Flow連接到Sink,連接兩者的處理步驟。

combine函數用于組合此flow和Sink的物化值,并放入Sink結果的物化值。

     +----------------------------+
     | Resulting Sink[In, M2]     |
     |                            |
     |  +------+        +------+  |
     |  |      |        |      |  |
 In ~~> | flow | ~Out~> | sink |  |
     |  |   Mat|        |     M|  |
     |  +------+        +------+  |
     +----------------------------+

(3)to[Mat2](sink: Graph[SinkShape[Out], Mat2]): Sink[In, Mat]
toMat(sink)(Keep.left)。生成的Sink的物化值將是現在Flow的物化值(忽略提供的Sink的物化值);如果需要不同的策略,使用toMat

(4)mapMaterializedValue[Mat2](f: Mat ? Mat2): ReprMat[Out, Mat2]
轉換此Flow的物化值。

(5)joinMat[Mat2, Mat3](flow: Graph[FlowShape[Out, In], Mat2])(combine: (Mat, Mat2) ? Mat3): RunnableGraph[Mat3]
Join this [[Flow]] to another [[Flow]], by cross connecting the inputs and outputs, creating a [[RunnableGraph]]
通過連接輸入和輸出, 將此Flow加入到另一個Flow, 創建 RunnableGraph。

 +------+        +-------+
 |      | ~Out~> |       |
 | this |        | other |
 |      | <~In~  |       |
 +------+        +-------+

例如:

val connection = Tcp().outgoingConnection(localhost)
      //#repl-client

      val replParser =
        Flow[String].takeWhile(_ != "q")
          .concat(Source.single("BYE"))
          .map(elem ? ByteString(s"$elem\n"))

      val repl = Flow[ByteString]
        .via(Framing.delimiter(
          ByteString("\n"),
          maximumFrameLength = 256,
          allowTruncation = true))
        .map(_.utf8String)
        .map(text ? println("Server: " + text))
        .map(_ ? readLine("> "))
        .via(replParser)

      connection.join(repl).run()
      //#repl-client

(6)join[Mat2](flow: Graph[FlowShape[Out, In], Mat2]): RunnableGraph[Mat]
joinMat(flow)(Keep.left)

(7)joinMat[I2, O2, Mat2, M](bidi: Graph[BidiShape[Out, O2, I2, In], Mat2])(combine: (Mat, Mat2) ? M): Flow[I2, O2, M]
將此 [[Flow]] 連接到 [[[BidiFlow]] 以關閉協議棧的 "頂部":

 +---------------------------+
 | Resulting Flow            |
 |                           |
 | +------+        +------+  |
 | |      | ~Out~> |      | ~~> O2
 | | flow |        | bidi |  |
 | |      | <~In~  |      | <~~ I2
 | +------+        +------+  |
 +---------------------------+

例如:

val bidi = BidiFlow.fromFlows(
    Flow[Int].map(x ? x.toLong + 2).withAttributes(name("top")),
    Flow[ByteString].map(_.decodeString("UTF-8")).withAttributes(name("bottom")))
val f = Flow[String].map(Integer.valueOf(_).toInt).join(bidi)
val result = Source(List(ByteString("1"), ByteString("2"))).via(f).limit(10).runWith(Sink.seq)

結果應該是:Seq(3L, 4L)

(8)join[I2, O2, Mat2](bidi: Graph[BidiShape[Out, O2, I2, In], Mat2]): Flow[I2, O2, Mat]
joinMat(bidi)(Keep.left)

(9)withAttributes(attr: Attributes): Repr[Out]
將此 [[流]] 的屬性更改為給定部分, 并密封屬性列表。這意味著進一步的調用將無法刪除這些屬性, 而是添加新的。請注意, 此操作對空流沒有影響 (因為這些屬性只適用于已包含的處理階段)。
例如:

val flow = Flow[Int]
      .filter(100 / _ < 50).map(elem ? 100 / (5 - elem))
      .withAttributes(ActorAttributes.supervisionStrategy(decider))

(10)addAttributes(attr: Attributes): Repr[Out]
將給定的屬性添加到此流中。進一步調用 "withAttributes" 不會刪除這些屬性。請注意, 此操作對空流沒有影響 (因為這些屬性只適用于已包含的處理階段)。
例如:

val section = Flow[Int].map(_ * 2).async
      .addAttributes(Attributes.inputBuffer(initial = 1, max = 1)) // the buffer size of this map is 1
val flow = section.via(Flow[Int].map(_ / 2)).async // the buffer size of this map is the default

(11)named(name: String): Repr[Out]
addAttributes(Attributes.name(name))。給Flow增加一個name屬性。

(12)async: Repr[Out]
即,addAttributes(Attributes.asyncBoundary)。在這個Flow周圍放置一個異步邊界。

(13)runWith[Mat1, Mat2](source: Graph[SourceShape[In], Mat1], sink: Graph[SinkShape[Out], Mat2])(implicit materializer: Materializer): (Mat1, Mat2)
將Source連接到Flow,然后連接到Sink并運行它。返回的元組包含了Source和Sink的物化值。

(14)toProcessor: RunnableGraph[Processor[In @uncheckedVariance, Out @uncheckedVariance]]
將此流轉換為一個RunnableGraph(物化到一個響應式流的org.reactivestreams.Processor, 它實現了由此流程封裝的操作)。每個物化都會產生一個新的Processor實例, 即返回的 RunnableGraph是可重用的。

(15)recover[T >: Out](pf: PartialFunction[Throwable, T]): Repr[T]
恢復允許在發生故障時發送最后一個元素,并優雅地完成流。由于底層故障信號onError到達帶外(out-of-band),它可能跳過現有元素。 該階段可以恢復故障信號,但不能恢復跳過的元素(它們將被丟棄)。

recover中拋出異常將自動記錄日志在ERROR級別。

當上游元素可用或者上游失敗偏函數pf返回一個元素時,發送元素。

當下游背壓時背壓。

當下游完成或者引起上游失敗的異常pf能夠處理,則完成。

當下游取消時,取消。

例如:

Source(0 to 6).map(n ?
      if (n < 5) n.toString
      else throw new RuntimeException("Boom!")
    ).recover {
      case _: RuntimeException ? "stream truncated"
    }.runForeach(println)

輸出是:

0
1
2
3
4
stream truncated

(16)recoverWithRetries[T >: Out](attempts: Int, pf: PartialFunction[Throwable, Graph[SourceShape[T], NotUsed]]): Repr[T]
RecoverWithRetries 允許Flow失敗時,切換到備用Source。在故障嘗試多次并恢復后, 它將繼續有效, 這樣每次出現故障時, 它就會被送入偏函數"pf", 而一個新的源可能會被物化。請注意, 如果傳入0, 則根本不會嘗試恢復。

attempts設為負數,被解釋為無限。

由于底層故障信號onError到達帶外(out-of-band),它可能跳過現有元素。 該階段可以恢復故障信號,但不能恢復跳過的元素(它們將被丟棄)。

recover中拋出異常將自動記錄日志在ERROR級別。

當上游元素可用或者上游失敗偏函數pf返回一個元素時,發送元素。

當下游背壓時背壓。

當下游完成或者引起上游失敗的異常pf能夠處理,則完成。

當下游取消時,取消。

參數attempts 重試的最大值或者設為-1將無限次重試。

參數偏函數pf 接收失敗原因并返回要物化的新Source, 如果有的話。

如果attempts是一個負數但不是-1拋出IllegalArgumentException 異常。
例如:

val planB = Source(List("five", "six", "seven", "eight"))

    Source(0 to 10).map(n ?
      if (n < 5) n.toString
      else throw new RuntimeException("Boom!")
    ).recoverWithRetries(attempts = 1, {
      case _: RuntimeException ? planB
    }).runForeach(println)

輸出結果是:

0
1
2
3
4
five
six
seven
eight

(17)mapError(pf: PartialFunction[Throwable, Throwable]): Repr[Out]
雖然類似 recover,但此階段可用于將錯誤信號轉換為另一種, 而不將其日志記錄為過程中的錯誤。因此, 從這個意義上說, 它并不完全等同于 recover(t => throw t2),因為 recover將日志記錄 t2錯誤。

由于底層故障信號 onError 到達帶外, 它可能會跳過現有的元素。此階段可以恢復故障信號, 但不能收回將被丟棄的已跳過的元素。

recover類似,在mapError中拋出異常將被記錄。
例如:

val ex = new RuntimeException("ex") with NoStackTrace
val boom = new Exception("BOOM!") with NoStackTrace
Source(1 to 3).map { a ? if (a == 2) throw ex else a }
        .mapError { case t: Exception ? throw boom }

(18)map[T](f: Out ? T): Repr[T]
通過將給定函數應用于每個元素, 使其通過此處理步驟來轉換此流。

遵守ActorAttributes.SupervisionStrategy屬性。

當映射函數返回一個元素時發出。

當下游背壓時背壓。

當下游完成時完成。

當下游取消時取消。

(19)statefulMapConcat[T](f: () ? Out ? immutable.Iterable[T]): Repr[T]
將每個輸入元素轉換為輸出元素的 Iterable, 然后將其平坦化到輸出流中。該轉換意味著是有狀態的, 這是通過為每個物化重新創建轉換函數來啟用的-返回的函數通常會掩蓋可變對象以在調用之間存儲狀態。對于無狀態變量, 請參見 [[FlowOps. mapConcat]]。

返回的Iterable不能包含null值, 因為它們是非法的流元素-根據反應流規范。

遵守ActorAttributes.SupervisionStrategy屬性。

當映射函數返回一個元素或者先前計算的集合仍有剩余的元素時發出。

當下游背壓或者先前計算的集合仍有剩余的元素時背壓。

當下游完成并且剩余的元素已經發出時完成。

當下游取消時,取消。

例如:

"be able to restart" in {
      Source(List(2, 1, 3, 4, 1)).statefulMapConcat(() ? {
        var prev: Option[Int] = None
        x ? {
          if (x % 3 == 0) throw ex
          prev match {
            case Some(e) ?
              prev = Some(x)
              (1 to e) map (_ ? x)
            case None ?
              prev = Some(x)
              List.empty[Int]
          }
        }
      }).withAttributes(ActorAttributes.supervisionStrategy(Supervision.restartingDecider))
        .runWith(TestSink.probe[Int])
        .request(2).expectNext(1, 1)
        .request(4).expectNext(1, 1, 1, 1)
        .expectComplete()
    }

    "be able to resume" in {
      Source(List(2, 1, 3, 4, 1)).statefulMapConcat(() ? {
        var prev: Option[Int] = None
        x ? {
          if (x % 3 == 0) throw ex
          prev match {
            case Some(e) ?
              prev = Some(x)
              (1 to e) map (_ ? x)
            case None ?
              prev = Some(x)
              List.empty[Int]
          }
        }
      }).withAttributes(ActorAttributes.supervisionStrategy(Supervision.resumingDecider))
        .runWith(TestSink.probe[Int])
        .request(2).expectNext(1, 1)
        .requestNext(4)
        .request(4).expectNext(1, 1, 1, 1)
        .expectComplete()
    }

(20)mapConcat[T](f: Out ? immutable.Iterable[T]): Repr[T]
statefulMapConcat(() ? f)。這個轉換函數沒有內部狀態,即沒有掩蓋可變對象。
例如:

val someDataSource = Source(List(List("1"), List("2"), List("3", "4", "5"), List("6", "7")))

//#flattening-seqs
val myData: Source[List[Message], NotUsed] = someDataSource
val flattened: Source[Message, NotUsed] = myData.mapConcat(identity)
//#flattening-seqs

Await.result(flattened.limit(8).runWith(Sink.seq), 3.seconds) should be(List("1", "2", "3", "4", "5", "6", "7"))

(21)mapAsync[T](parallelism: Int)(f: Out ? Future[T]): Repr[T]
通過將給定函數應用于每個元素, 使其通過此處理步驟來轉換此流。 函數返回一個Future并將Future的值發給下游。并行運行的Future數量作為mapAsync的第一個參數給出。這些Future可能以任何順序完成,但下游發出的元素與從上游接收的元素相同。

如果函數`f'引發異常,或者如果Future完成失敗,并且監管(supervision)決定是akka.stream.Supervision.Stop,流將完成失敗。

如果函數`f'拋出異常,或者如果Future完成失敗,監管(supervision)決定是akka.stream.Supervision.Resumeakka.stream.Supervision.Restart,則元素被丟棄,流繼續運行。

函數`f'總是按照元素到達的順序對它們進行調用。

遵守ActorAttributes.SupervisionStrategy屬性。

當由提供的函數返回的Future按順序完成下一個元素時發出。

當future的數量到達配置的并行數并且下游背壓時,或者當第一個future沒有完成時,背壓。

當下游完成并且所有的future完成并且所有元素已經發出,則完成。

當下游取消時,取消。

例如:

//#mapAsync-ask
import akka.pattern.ask
implicit val askTimeout = Timeout(5.seconds)
val words: Source[String, NotUsed] =
      Source(List("hello", "hi"))

words
      .mapAsync(parallelism = 5)(elem ? (ref ? elem).mapTo[String])
      // continue processing of the replies from the actor
      .map(_.toLowerCase)
      .runWith(Sink.ignore)
//#mapAsync-ask

(22)mapAsyncUnordered[T](parallelism: Int)(f: Out ? Future[T]): Repr[T]
通過將給定函數應用于每個元素, 使其通過此處理步驟來轉換此流。 函數返回一個Future并將Future的值發給下游。并行運行的Future數量作為mapAsyncUnordered的第一個參數給出。每個已處理的元素將在準備就緒后立即發送到下游, 也就是說, 元素可能不會按照從上游接收到的相同順序發送到下游。

如果函數`f'引發異常,或者如果Future完成失敗,并且監管(supervision)決定是akka.stream.Supervision.Stop,流將完成失敗。

如果函數`f'拋出異常,或者如果Future完成失敗,監管(supervision)決定是akka.stream.Supervision.Resumeakka.stream.Supervision.Restart,則元素被丟棄,流繼續運行。

函數`f'總是按照元素到達的順序對它們進行調用(即使 "f" 返回的Future的結果可能以不同的順序發出)。

遵守ActorAttributes.SupervisionStrategy屬性。

當任一由給定函數返回的Future完成時,發送。

當future的數量到達配置的并行數并且下游背壓時背壓。

當下游完成并且所有的future已經完成并且所有元素已經發送時,完成。

當下游取消時取消。

(23)filter(p: Out ? Boolean): Repr[Out]
只傳遞滿足給定謂詞的元素。

遵守ActorAttributes.SupervisionStrategy屬性。

當對于某元素,給定的謂詞返回true,則發送該元素。

當對于某元素給定的謂詞返回true并且下游背壓時,背壓。

當下游完成時完成。

當下游取消時取消。

(24)filterNot(p: Out ? Boolean): Repr[Out]
只傳遞那些不滿足給定謂詞的元素。

(25)takeWhile(p: Out ? Boolean, inclusive: Boolean): Repr[Out]
在謂詞第一次返回 false 后終止處理 (并取消上游發布者), 包括第一個失敗的元素 (如果inclusive是真的) 由于輸入緩沖, 某些元素可能已從上游發布者請求, 然后此步驟的下游不處理。

如果第一個流元素的謂詞為 false, 則該流將完成而不生成任何元素。

遵守ActorAttributes.SupervisionStrategy屬性。

當謂詞為true時,發送元素。

當下游背壓時背壓。

如果inclusive為false當謂詞為false時或者當如果inclusive為true而謂詞為false后第一個元素已發出并且下游完成時或者下游完成時,完成。
例如:

Source(1 to 10).takeWhile(_ < 3, true).runWith(TestSink.probe[Int])
        .request(4)
        .expectNext(1, 2, 3)
        .expectComplete()

(26)takeWhile(p: Out ? Boolean): Repr[Out]
即takeWhile(p, false)
在謂詞第一次返回false后,終止處理(并且取消上游發布者)。由于輸入緩沖,一些元素可能已經從上游發布者請求,將不會被此步驟的下游處理。

如果第一個流元素的謂詞為 false, 則該流將完成而不生成任何元素。

當謂詞為true時,發送元素。

當下游背壓時背壓。

當謂詞返回false或者下游取消時,則取消。

例如:

val flowUnderTest = Flow[Int].takeWhile(_ < 5)

val future = Source(1 to 10).via(flowUnderTest).runWith(Sink.fold(Seq.empty[Int])(_ :+ _))
val result = Await.result(future, 3.seconds)
assert(result == (1 to 4))

(27)dropWhile(p: Out ? Boolean): Repr[Out]
例如:
在謂詞為true時,丟棄該元素。
謂詞第一次返回false后,所有元素都將被采用。

Source(1 to 4).dropWhile(_ < 3).runWith(TestSink.probe[Int])
        .request(2)
        .expectNext(3, 4)
        .expectComplete()

Source(1 to 4).dropWhile(a ? if (a < 3) true else throw TE("")).withAttributes(supervisionStrategy(resumingDecider))
        .runWith(TestSink.probe[Int])
        .request(1)
        .expectComplete()

Source(1 to 4).dropWhile {
        case 1 | 3 ? true
        case 4     ? false
        case 2     ? throw TE("")
 }.withAttributes(supervisionStrategy(restartingDecider))
        .runWith(TestSink.probe[Int])
        .request(1)
        .expectNext(4)
        .expectComplete()

(28)collect[T](pf: PartialFunction[Out, T]): Repr[T]
通過將給定的偏函數應用于每個元素, 以便通過此處理步驟來轉換此流。 不匹配的元素被過濾掉。

遵守ActorAttributes.SupervisionStrategy屬性。

如果某元素在偏函數中有定義,則發送該元素。

如果某元素在偏函數中有定義并且下游背壓,則背壓。

如果下游完成,則完成。

如果下游取消,則取消。

例如:

 val emailAddresses: Source[String, NotUsed] =
      authors
        .mapAsync(4)(author ? addressSystem.lookupEmail(author.handle))
        .collect { case Some(emailAddress) ? emailAddress }

(29)grouped(n: Int): Repr[immutable.Seq[Out]]
將該流塊組合成給定大小的組,最后一組可能由于流的結束而小于請求。

n必須是正數, 否則拋出IllegalArgumentException異常。

已累積指定數量的元素或上游完成時發出元素。

已組裝出一組并且下游背壓時背壓。

下游完成時,完成。

下游取消時,取消。

例如:

Source(1 to 4).grouped(2)
      .runWith(Sink.seq)

結果應該是:

Seq(Seq(1, 2), Seq(3, 4))

(30)limitWeighted[T](max: Long)(costFn: Out ? Long): Repr[Out]
通過使用成本函數來評估傳入元素的成本, 確保流有界性。到底有多少元素將被允許前往下游取決于每個元素的評估成本。如果累計成本超過最大值, 它將向上游發出故障 "StreamLimitException" 信號。

由于輸入緩沖,可能已經從上游發布者請求了一些元素,然后將不會在該步驟的下游處理。

遵守ActorAttributes.SupervisionStrategy屬性。

上游發出且已累計的成本沒有達到最大值時發出。

下游背壓時背壓。

累計的成本超過最大值時報錯。

下游取消時取消。

(31)limit(max: Long): Repr[Out]
limitWeighted(max)(_ ? 1)
通過限制上游元素的數量來確保流的有界性。如果傳入元素的數量超過最大值, 它將向上游發出故障 "StreamLimitException" 信號。

由于輸入緩沖,可能已經從上游發布者請求了一些元素,然后將不會在該步驟的下游處理。

上游發出且已發出的元素數量沒有達到最大值時發出。

下游背壓時背壓。

傳入元素的總數超過最大值時報錯。

下游取消時取消。

(32)sliding(n: Int, step: Int = 1): Repr[immutable.Seq[Out]]
在流上應用滑動窗口, 并將窗口作為元素組返回;因為流尾部,最后一個組可能小于請求。

n必須是正數, 否則拋出IllegalArgumentException異常。

step````必須是正數, 否則拋出IllegalArgumentException```異常。

當窗口內已收集足夠的元素或上游已完成,發出元素。

當窗口已收集足夠的元素且下游背壓時背壓。

當上游完成時,完成。

當下游取消時,取消。

(33)scan[T](zero: T)(f: (T, Out) ? T): Repr[T]
Similar to fold but is not a terminal operation,

  • emits its current value which starts at zero and then
  • applies the current and next value to the given function f,
  • emitting the next current value.
    類似于fold, 但不是終端操作, 從zero 作為當前值開始, 然后將當前值和上游傳入的元素應用于給定函數 f, 立即將結果(中間結果)發送到下游,并作為下次計算的當前值。

如果函數```f````拋出異常,并且監督決定是[[akka.stream.Supervision.Restart]],則當前值將再次從零開始,流將繼續。

遵守ActorAttributes.SupervisionStrategy屬性。

當掃描元素的函數返回一個新元素時發出元素。

當下游背壓時背壓。

當上游完成時完成。

當下游取消時取消。

例如:

val scan = Flow[Int].scan(0) { (old, current) ?
        require(current > 0)
        old + current
      }.withAttributes(supervisionStrategy(Supervision.restartingDecider))
Source(List(1, 3, -1, 5, 7)).via(scan)

注意,scan會將每一次計算結果都發給它的下游,此處下游會收到0, 1, 4, 0, 5, 12共六個元素。

(34)scanAsync[T](zero: T)(f: (T, Out) ? Future[T]): Repr[T]
類似于scan,但使用異步函數,它發出其當前值 (從zero 開始), 然后將當前值和下一個數值應用于給定函數 f, 從而發出解析為下一個當前值的Future

如果函數f````拋出異常,并且監督決定是[[akka.stream.Supervision.Restart]],則當前值將再次從zero```開始,流將繼續運行。

如果函數 f 拋出異常, 并且監視決策是akka.stream.Supervision.Resume當前值從上一個當前值開始, 或者當它沒有一個時, 則為zero, 流將繼續。

遵守ActorAttributes.SupervisionStrategy屬性。

f返回future完成時,發送元素。

當下游背壓時背壓。

當上游完成并且最后一個f返回的future完成時,,完成。

下游取消時,取消。

(35)fold[T](zero: T)(f: (T, Out) ? T): Repr[T]
類似于scan,但它只在上游傳入元素完成,并計算出最終結果后,才將最終結果發送到下游。應用給定的函數將當前值和下一個值進行計算,其結果作為下一次計算的當前值(注意此中間結果不會發給下游)。

如果函數f````拋出異常,并且監督決定是[[akka.stream.Supervision.Restart]],則當前值將再次從zero```開始,流將繼續運行。

遵守ActorAttributes.SupervisionStrategy屬性。

當上游完成時,發送計算結果。

當下游背壓時背壓。

當上游完成時,完成。

下游取消時,取消。

例如:

val fold= Flow[Int].fold(0) { (old, current) ?
        require(current > 0)
        old + current
      }.withAttributes(supervisionStrategy(Supervision.restartingDecider))
Source(List(1, 3, -1, 5, 7)).via(fold)

注意下游只會收到一個元素12。

(36)foldAsync[T](zero: T)(f: (T, Out) ? Future[T]): Repr[T]
類似于fold,但使用異步函數,應用給定的函數將當前值和下一個值進行計算,其結果作為下一次計算的當前值(注意此中間結果不會發給下游)。

遵守ActorAttributes.SupervisionStrategy屬性。

如果函數f````拋出異常,并且監督決定是[[akka.stream.Supervision.Restart]],則當前值將再次從zero```開始,流將繼續運行。

當上游完成時,發送計算結果。

當下游背壓時背壓。

當上游完成時,完成。

下游取消時,取消。

(37)reduce[T >: Out](f: (T, T) ? T): Repr[T]
類似于fold但使用收到的第一個元素作為zero元素。應用給定的函數將當前值和下一個值進行計算,其結果作為下一次計算的當前值(注意此中間結果不會發給下游)。

如果流是空的 (即在發送任何元素之前完成), 則 reduce 階段將以NoSuchElementException使下游失敗, 這在語義上與 Scala 的標準庫集合在這種情況下的狀態一致。

遵守ActorAttributes.SupervisionStrategy屬性。

當上游完成時,發送計算結果。

當下游背壓時,背壓。

當上游完成時,完成。

下游取消時,取消。

(38)intersperse[T >: Out](start: T, inject: T, end: T): Repr[T]以及intersperse[T >: Out](inject: T): Repr[T]
使用所提供元素的散流, 類似于scala.collection.immutable.List.mkString在List元素之間插入分隔符的方式。

此外, 還可以向流中注入起始和結束標記元素。
例如:

  val nums = Source(List(1,2,3)).map(_.toString)
  nums.intersperse(",")            //   1 , 2 , 3
  nums.intersperse("[", ",", "]")  // [ 1 , 2 , 3 ]

如果您只想預置或僅追加元素 (但是仍然使用intercept特性在元素之間插入一個分隔符,你可能需要使用下面的模式而不是3個參數版本的intersperse(參見Source .concat用于語義細節):

 Source.single(">> ") ++ Source(List("1", "2", "3")).intersperse(",")

 Source(List("1", "2", "3")).intersperse(",") ++ Source.single("END")

上游發出時發出(或者如果有提供start元素,先發送它)。

當下游背壓時,背壓。

當上游完成時,完成。

下游取消時,取消。

(39)groupedWithin(n: Int, d: FiniteDuration): Repr[immutable.Seq[Out]]
將流進行分組,或按照一個時間窗口內接收到的元素分組,或按照給定數量的限制進行分組,無論哪一個先發生。 如果沒有從上游接收到元素,空組將不會被發出。 流結束之前的最后一個組將包含自先前發出的組以來的緩沖元素。

n必須是正數,d必須大于0秒,否則拋出IllegalArgumentException異常。

從上一個組發射后經過配置的時間或者緩沖n個元素后發送。

當下游背壓且已經有n+1個元素緩沖,則背壓。

當上游完成并且最后一組已發送,完成。

當下游取消,取消。

(40)groupedWeightedWithin(maxWeight: Long, d: FiniteDuration)(costFn: Out ? Long): Repr[immutable.Seq[Out]]
將流進行分組,或按照一個時間窗口內接收到的元素分組,或按照元素權重的限制進行分組,無論哪一個先發生。 如果沒有從上游接收到元素,空組將不會被發出。 流結束之前的最后一個組將包含自先前發出的組以來的緩沖元素。

maxWeight必須是正數,d必須大于0秒,否則拋出IllegalArgumentException異常。

從上一個組發射后經過配置的時間或者到達權重限制后發送。

當下游背壓,并且緩沖組(+等待元素)權重大于maxWeight時,背壓。

當上游完成并且最后一組已發送,完成。

當下游取消,取消。

(41)delay(of: FiniteDuration, strategy: DelayOverflowStrategy = DelayOverflowStrategy.dropTail): Repr[Out]
按一定時間間隔將元素的發射時間改變。在等待下一個元素被發送時,它允許存儲元素在內部緩沖區。如果緩沖區內沒有足夠的空間,根據定義的akka.stream.DelayOverflowStrategy,可以丟棄元素或者背壓上游。

延遲精度為 10ms, 以避免不必要的計時器調度周期。

內部緩沖區的默認容量為16。您可以通過調用addAttributes (inputBuffer)來設置緩沖區大小。

當緩沖區有等待的元素并且為這個元素配置的時間已到達,則發送元素。

OverflowStrategy策略介紹:

  • emitEarly:如果緩沖區已滿,當新的元素可用時,這個策略不等待直接發送下一個元素到下游。
  • dropHead:如果緩沖區已滿,當新的元素到達,丟棄緩沖區中最舊的元素,從而為新元素留出空間。
  • dropTail:如果緩沖區已滿,當新的元素到達,丟棄緩沖區中最新的元素,從而為新元素留出空間。
  • dropBuffer:如果緩沖區已滿,當新的元素到達,丟棄緩沖區中所有元素,從而為新元素留出空間。
  • dropNew:如果緩沖區已滿,當新的元素到達,丟棄這個新元素。
  • backpressure:如果緩沖區已滿,當新的元素到達,則背壓上游發布者直到緩沖區內的空間可用。
  • fail:如果緩沖區已滿,當新的元素到達,則以失敗完成流。

當上游完成并且緩沖元素被耗盡時,完成。

當下游取消時,取消。

(42)drop(n: Long): Repr[Out]
在流的開始處丟棄給定數目的元素。
如果n為零或為負數, 則不會丟棄任何元素。

當已經丟棄了指定數目的元素,則發送元素。

當指定數目的元素已被丟棄并且下游背壓時,背壓。

當上游完成時,完成。

當下游取消時,取消。

(43)dropWithin(d: FiniteDuration): Repr[Out]
在流的開始處在給定的持續時間內,丟棄接收的元素。

當經過指定的時間后并且新的上游元素到達,發送元素。

當下游背壓時,背壓。

當上游完成時,完成。

下游取消時,取消。

(44)take(n: Long): Repr[Out]
在給定數量的元素之后終止處理 (并取消上游發布者)。由于輸入緩沖, 有些元素可能已從上游發布者請求, 這些元素將不會在這一步的下游進行處理。

如果n是0或者負數,流將不產生任何元素完成。

指定的數目還沒到達時,發送元素。

當下游背壓時,背壓。

當指定數目的元素已經處理或者上游完成時,完成。

當指定數目的元素已被處理或者下游取消時,取消。

(45)takeWithin(d: FiniteDuration): Repr[Out]
在給定的持續時間后,終止處理(并取消上游發布者)。由于輸入緩沖, 有些元素可能已從上游發布者請求, 這些元素將不會在這一步的下游進行處理。

請注意,這可以與take結合來限制持續時間內的元素數量。

當上游元素到達時,發送元素。

當下游背壓時,背壓。

在上游完成或計時器觸發時完成。

當下游取消或計時器觸發時取消。

(46)conflateWithSeed[S](seed: Out ? S)(aggregate: (S, Out) ? S): Repr[S]
通過將元素合并到一個摘要中,允許更快的上游獨立于較慢的訂閱者,直到訂閱者準備好接受它們。例如,如果上游的發布者速度更快,那么合并的步驟可能會勻化傳入的數目。

此版本的合并允許從第一個元素派生種子, 并將聚合類型更改為與輸入類型不同的類型。有關不更改類型的更簡單版本, 請參見FlowOps.conflate

此元素僅在上游速度較快時聚合元素, 但如果下游速度較快, 則不會復制元素。

遵守ActorAttributes.SupervisionStrategy屬性。

當下游停止背壓并且有可用的已合并元素,則發送元素。

沒有背壓的時候。

當上游完成時,完成。

當下游取消時,取消。

參數seed,使用第一個未消耗的元素作為開始,為合并值提供第一個狀態。
參數aggregate, 獲取當前聚合的值和當前正在等待的元素,以生成一個新的聚合值。

也請參見FlowOps.conflate, FlowOps.limit, FlowOps.limitWeighted, FlowOps.batch, FlowOps.batchWeighted

(47)conflate[O2 >: Out](aggregate: (O2, O2) ? O2): Repr[O2]
通過將元素合并到一個摘要中,允許更快的上游獨立于較慢的訂閱者,直到訂閱者準備好接受它們。例如,如果上游的發布者速度更快,那么合并的步驟可能會勻化傳入的數目。

此版本的合并不改變流的輸出類型。請參見FlowOps.conflateWithSeed,一個更為復雜的版本,可以在聚合時使用種子函數并轉換元素。

此元素僅在上游速度較快時聚合元素, 但如果下游速度較快, 則不會復制元素。

遵守ActorAttributes.SupervisionStrategy屬性。

當下游停止背壓并且有可用的已合并元素,則發送元素。

沒有背壓的時候。

當上游完成時,完成。

當下游取消時,取消。

參數aggregate, 獲取當前聚合的值和當前正在等待的元素,以生成一個新的聚合值。

也請參見FlowOps.conflateWithSeed, FlowOps.limit, FlowOps.limitWeighted, FlowOps.batch, FlowOps.batchWeighted

(48)batch[S](max: Long, seed: Out ? S)(aggregate: (S, Out) ? S): Repr[S]
通過將元素聚合到批次中,允許更快的上游獨立于較慢的訂閱者,直到訂閱者準備好接受它們。例如,如果上游發布者更快,一個batch步驟可以存儲接收到的元素于一個數組直到最大限制值。

此元素僅在上游速度較快時聚合元素, 但如果下游速度較快, 則不會復制元素。

遵守ActorAttributes.SupervisionStrategy屬性。

當下游停止背壓并且有可用的已聚合元素,則發送元素。

當成批元素達到最大值而有一個待處理元素且下游背壓時,背壓。

當上游完成且沒有成批或待處理元素等待,完成。

參數max 在背壓上游前,可成批的最大元素數
(必須是非0正數)。
參數seed,使用第一個未消耗的元素作為開始,為合并值提供第一個狀態。
參數aggregate, 獲取當前成批的值和當前正在等待的元素,以生成一個新的聚合值。

(49)batchWeighted[S](max: Long, costFn: Out ? Long, seed: Out ? S)(aggregate: (S, Out) ? S): Repr[S]
通過將元素聚合到批次中,允許更快的上游獨立于較慢的訂閱者,直到訂閱者準備好接受它們。例如,如果上游發布者速度更快,則batch步驟可以將“ByteString”元素連接到允許的最大限制。

此元素僅在上游速度較快時聚合元素, 但如果下游速度較快, 則不會復制元素。

成批將應用于所有元素,即使一個單一元素成本比允許的最大值還大。這種情況下,先前成批的元素將被發送,然后這個"重"元素將被發送(在應用了種子函數后)而不用它與其它元素成批處理,然后其余傳入的元素成批處理。

當下游停止背壓并且有可用的已聚合元素,則發送元素。

當成批元素權重值到達最大值而有一個待處理元素且下游背壓時,背壓。

當上游完成且沒有成批或待處理元素等待,完成。

當下游取消時,取消。

參數max 在背壓上游前,成批元素的最大權重值(必須時非0正數)
參數costFn 用于計算單一元素權重值的函數
參數seed,使用第一個未消耗的元素作為開始,為成批值提供第一個狀態。
參數aggregate, 獲取當前成批的值和當前正在等待的元素,以生成一個新的成批值。

(50)expand[U](extrapolate: Out ? Iterator[U]): Repr[U]
通過從較老的元素中外推元素,直到從上游來新元素,可以使更快地下游獨立于較慢的發布者。例如,擴展步驟可能會重復給訂閱者的最后一個元素,直到它從上游接收到更新。

這個元素永遠不會“丟棄”上游元素,因為所有元素都經過至少一個外推步驟。

這意味著如果上游實際上比上游更快,它將被下游用戶背壓。

Expand不支持akka.stream.Supervision.Restartakka.stream.Supervision.Resume

來自seed或者extrapolate函數的異常將使流以失敗完成。

當下游停止背壓時,發送元素。

當下游背壓或者迭代器運行為空時,背壓。

當上游完成時,完成。

當下游取消時,取消。

(51)buffer(size: Int, overflowStrategy: OverflowStrategy): Repr[Out]
在流中添加一個固定大小的緩沖區, 允許從較快的上游存儲元素, 直到它變為滿的。如果沒有可用的空間, 根據已定義的 akka.stream.OverflowStrategy, 它可能會丟棄元素或上背壓上游。

當下游停止背壓,并且在緩沖區有待處理元素,則發送元素。

OverflowStrategy策略介紹:

  • emitEarly:如果緩沖區已滿,當新的元素可用時,這個策略不等待直接發送下一個元素到下游。
  • dropHead:如果緩沖區已滿,當新的元素到達,丟棄緩沖區中最舊的元素,從而為新元素留出空間。
  • dropTail:如果緩沖區已滿,當新的元素到達,丟棄緩沖區中最新的元素,從而為新元素留出空間。
  • dropBuffer:如果緩沖區已滿,當新的元素到達,丟棄緩沖區中所有元素,從而為新元素留出空間。
  • dropNew:如果緩沖區已滿,當新的元素到達,丟棄這個新元素。
  • backpressure:如果緩沖區已滿,當新的元素到達,則背壓上游發布者直到緩沖區內的空間可用。
  • fail:如果緩沖區已滿,當新的元素到達,則以失敗完成流。

當上游完成并且緩沖元素被耗盡時,完成。

當下游取消時,取消。

(52)prefixAndTail[U >: Out](n: Int): Repr[(immutable.Seq[Out], Source[U, NotUsed])]
從流中獲取n個元素 (僅當上游在發出n個元素之前完成比n小) 并返回一個包含所取元素的嚴格序列和一個表示剩余元素的流的pair。如果 "n" 為零或負, 則返回一個空集合和一個流(將包含整個上游的流保持不變)的pair

如果上游出現錯誤, 則取決于當前狀態

  • 如果在獲取n個元素之前,主流標識錯誤,子流尚未發出。
  • 如果在獲取n個元素之后,主流標識錯誤,子流已經發出(在那一刻,主流已經完成)

當達到配置的“前綴”元素數目時,發送“前綴”以及剩余部分組成的子流。

當下游背壓或者子流背壓時,背壓。

當“前綴”元素和子流都已耗盡時,完成。

當下游取消或者子流取消時,取消。

(53)groupBy[K](maxSubstreams: Int, f: Out ? K): SubFlow[Out, Mat, Repr, Closed]
此操作將輸入流解復用為單獨的輸出流,每個元素鍵一個輸出流。 使用給定函數為每個元素計算鍵。 當第一次遇到一個新的鍵時,一個新的子流被打開,并隨后所有屬于該鍵的元素輸入到該流。

從這個方法返回的對象不是一個普通的Source或Flow,而是一個SubFlow。這意味著在此之后,所有的轉換都將以相同的方式應用于所有遇到的子流。SubFlow模式通過關閉子流(即將其連接到一個Sink)或將子流合并在一起而退出;有關更多信息,請參見SubFlow中的to和mergeBack方法。

需要注意的是子流也像任何其他流一樣傳播背壓,這意味著阻塞一個子流將阻塞“groupBy”運算符本身——從而阻塞所有子流——一旦所有的內部或顯式緩沖區被填滿。

如果 groupby 函數 f拋出一個異常, 并且監管策略是 akka.stream.Supervision.Stop, 則流和 substreams 將以失敗完成。

如果 groupby 函數 f拋出一個異常, 并且監管策略是 akka.stream.Supervision.Resume或者akka.stream.Supervision.Restart, 該元素被丟棄,流和 substreams 將繼續運行。

函數f不可以返回null。這將拋出異常并觸發監管決策機制。

遵守ActorAttributes.SupervisionStrategy屬性。

當分組函數返回尚未創建的組的元素時發出。發出新組。

當某個組有尚未處理的元素,而該組的子流背壓時,背壓。

當上游完成時,完成。

當下游取消并且所有子流取消時,取消。

參數maxSubstreams 配置支持的最大子流數/鍵數。如果遇到更多不同的鍵, 則流將失敗。

(54)splitWhen(substreamCancelStrategy: SubstreamCancelStrategy)(p: Out ? Boolean): SubFlow[Out, Mat, Repr, Closed]
此操作將給定謂詞應用于所有傳入元素, 并將它們發送到輸出流中的一個流, 如果給定謂詞返回 true, 則總是用當前元素開始新的一個子流。這意味著, 對于以下一系列謂詞值, 將產生三個子流, 長度為1、2和 3:

    false,             // 元素進入第一個子流
    true, false,       // 元素進入第二個子流
    true, false, false // 元素進入第三個子流

如果流的 * 第一個 * 元素與謂詞匹配, 則 splitWhen 發出的第一個流將從該元素開始。例如:

     true, false, false // 第一個流從拆分元素開始
     true, false        // 隨后的子流操作方式相同

從這個方法返回的對象不是一個普通的Source或Flow,而是一個SubFlow。這意味著在此之后,所有的轉換都將以相同的方式應用于所有遇到的子流。SubFlow模式通過關閉子流(即將其連接到一個Sink)或將子流合并在一起而退出;有關更多信息,請參見SubFlow中的to和mergeBack方法。

需要注意的是子流也像任何其他流一樣傳播背壓,這意味著阻塞一個子流將阻塞“splitWhen”運算符本身——從而阻塞所有子流——一旦所有的內部或顯式緩沖區被填滿。

如果拆分謂詞 p拋出一個異常, 并且監管策略是 akka.stream.Supervision.Stop, 則流和 substreams 將以失敗完成。

如果拆分謂詞 p拋出一個異常, 并且監管策略是 akka.stream.Supervision.Resume或者akka.stream.Supervision.Restart, 該元素被丟棄,流和 substreams 將繼續運行。

當元素對于謂詞為true時,為隨后的元素打開并發出新的子流。

當某個子流有待處理的元素而之前的元素尚未完全消耗時,或子流背壓時,背壓。

當上游完成時,完成。

當下游取消并且子流以SubstreamCancelStrategy.drain取消時,或者下游取消或任何子流以SubstreamCancelStrategy.propagate取消時,取消。

也請參見FlowOps.splitAfter

(55)splitWhen(p: Out ? Boolean): SubFlow[Out, Mat, Repr, Closed]
splitWhen(SubstreamCancelStrategy.drain)(p)

(56)splitAfter(substreamCancelStrategy: SubstreamCancelStrategy)(p: Out ? Boolean): SubFlow[Out, Mat, Repr, Closed]
此操作將給定謂詞應用于所有傳入元素, 并將它們發送到輸出流中的一個流, 如果給定謂詞返回 true, 則結束當前子流。這意味著, 對于以下一系列謂詞值, 將產生三個子流, 長度為2、2和 3:

     false, true,        // 元素進入第一個子流
     false, true,        // 元素進入第二個子流
     false, false, true  // 元素進入第三個子流

需要注意的是子流也像任何其他流一樣傳播背壓,這意味著阻塞一個子流將阻塞“splitAfter”運算符本身——從而阻塞所有子流——一旦所有的內部或顯式緩沖區被填滿。

如果拆分謂詞 p拋出一個異常, 并且監管策略是 akka.stream.Supervision.Stop, 則流和 substreams 將以失敗完成。

如果拆分謂詞 p拋出一個異常, 并且監管策略是 akka.stream.Supervision.Resume或者akka.stream.Supervision.Restart, 該元素被丟棄,流和 substreams 將繼續運行。

當元素經過時發出。當提供的謂詞為真時, 它發出元素并為后續元素打開一個新的流。

當某個子流有待處理的元素而之前的元素尚未完全消耗時,或子流背壓時,背壓。

當上游完成時完成。

當下游取消并且子流以SubstreamCancelStrategy.drain取消時,或者下游取消或任何子流以SubstreamCancelStrategy.propagate取消時,取消。

也請參見FlowOps.splitWhen

(57)splitAfter(p: Out ? Boolean): SubFlow[Out, Mat, Repr, Closed]
splitAfter(SubstreamCancelStrategy.drain)(p)

(58)flatMapConcat[T, M](f: Out ? Graph[SourceShape[T], M]): Repr[T]
將每個輸入元素轉換為輸出元素的Source,然后通過串聯將其平鋪為輸出流,從而一個接著一個的完全處理Source。

當當前消費的子流有一個元素可用時發出。

當下游背壓時,背壓。

當上游完成且所有子流完成時,完成。

當下游取消時,取消。

(59)flatMapMerge[T, M](breadth: Int, f: Out ? Graph[SourceShape[T], M]): Repr[T]
將每個輸入元素轉換為輸出元素的“Source”,然后通過合并將輸出元素展平成輸出流,其中在任何給定時間最大“廣度”子流被處理。

當當前消費的子流有一個元素可用時發出。

當下游背壓時,背壓。

當上游完成且所有子流完成時,完成。

當下游取消時,取消。

例如:

val async = Flow[Int].map(_ * 2).async

Source(0 to 9)
        .map(_ * 10)
        .flatMapMerge(5, i ? Source(i to (i + 9)).via(async))
        .grouped(1000)
        .runWith(Sink.head)
        .futureValue
        .sorted should ===(0 to 198 by 2)

(60)initialTimeout(timeout: FiniteDuration): Repr[Out]
如果在提供的超時之前,第一個元素還沒有經過這個階段,則流以scala.concurrent.TimeoutException失敗。

當上游發出元素時,發出。

當下游背壓時,背壓。

當上游完成時完成或者在第一個元素到達前超時已過則失敗。

當下游取消時取消。

(61)completionTimeout(timeout: FiniteDuration): Repr[Out]
當超時已過時,流尚未完成,則流以scala.concurrent.TimeoutException失敗。

當上游發出元素時,發出。

當下游背壓時,背壓。

當上游完成時完成或者在上游完全前超時已過則失敗。

當下游取消時取消。

(62)idleTimeout(timeout: FiniteDuration): Repr[Out]
如果兩個處理的元素之間的時間超過了提供的超時時間,則流以scala.concurrent.TimeoutException失敗。定期檢查超時,所以檢查的分辨率是一個周期(等于超時值)。

當上游發出元素時,發出。

當下游背壓時,背壓。

當上游完成時完成或者如果在兩個發出的元素之間超時失敗。

當下游取消時取消。

(63)backpressureTimeout(timeout: FiniteDuration): Repr[Out]
如果一個元素的發出和下一個下游需求之間的時間超過了提供的超時時間,則流以scala.concurrent.TimeoutException失敗。定期檢查超時,所以檢查的分辨率是一個周期(等于超時值)。

當上游發出元素時,發出。

當下游背壓時,背壓。

當上游完成時完成或者如果一個元素的發出和下一個下游需求之間的時間超時失敗。

當下游取消時取消。

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,401評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,011評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,263評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,543評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,323評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,874評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,968評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,095評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,605評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,551評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,720評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,242評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,961評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,358評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,612評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,330評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,690評論 2 370

推薦閱讀更多精彩內容