kot

            #Kotlin之班门弄斧

##面向对象

##java和kotlin的交互

##协程及协程框架 

<br/> 

## 面向对象

  1.kotlin和Dart可以说是真正的面向对象,一切皆对象,包括属性和函数,也就是说声明和引用属性和函数并不需要一个类型引用,建议把项目中出现频率高的函数定义为全局函数(也叫包级函数),属性也是如此(例子演示) 

```

var name:String =""

fun globalScopeName():String{

}

// 上面声明两个成员可以不在任何一个类中, 但可以在整个library中使用

```

  2.data一种封装类型 做网络结果的返回类很方便 并支持结构化声明,java 通过对相应的属性添加函数component1返回结果,在kotlin 中也可以用结构化的方式使用(例子演示)

```

data class Person(val name: String) {

    var age: Int = 0

}

//使用方式和常规一样并支持结构化声明

val jack = User(name = "Jack", age = 1)

fun copy(name: String = this.name, age: Int = this.age) = User(name, age)

val olderJack = jack.copy(age = 2)

```

说到data支持结构化声明当一个lambda表达式的参数是一个data类型的数据我们也可以用结构化声明的方式 

``` 

fun main() {

    funData { (name, age) ->

    }

}

fun funData(block: (N) -> Unit) {

}

data class N(var name: String, var age: Int)

```

说到这当初从java转kotlin 写kotlin的lambda总写不对总把lambda中的参数用小括号包括,错误也正是这个原因,因为在Kotlin中的lambda中参数用小括号的是支持结构化声明的对象,kotlin有哪些支持结构化的声明?

<br/> 

  3.sealed 对象有限的类型 和枚举很像但枚举不方便设置属性,所以在实际使用中如需要保存或标记属性值推荐用sealed 

``` 

sealed class Expr

data class Const(val number: Double) : Expr()

data class Sum(val e1: Expr, val e2: Expr) : Expr()

object NotANumber : Expr() 

fun eval(expr: Expr): Double = when(expr) {

    is Const -> expr.number

    is Sum -> eval(expr.e1) + eval(expr.e2)

    NotANumber -> Double.NaN

    // 不再需要 `else` 子句,因为我们已经覆盖了所有的情况

}

```

4.运算符 

  Kolin中每一个运算符都对应一个函数 

  ```

  a.plus(b)

  //如自定义函数用operator修饰

fun main() {

    println("")

    val a1 = A("hello", 18)

    val a2 = A("word", 62)

    println(a1 + a2)

}

class A(var name: String, var age: Int) {

    operator fun plus(a: A): A {

        return A("$name-${a.name}", age + a.age)

    }

    override fun toString(): String {

        return "[name:$name-age:$age]"

    }

}

// 其他的运算符同样

  ``` 

  5.扩展

  扩展kotlin支持属性和方法扩展 

扩展方法的语法  要扩展的对象.自定义方法名称(){}

```

fun Boolean.T(block: () -> Unit): Boolean {

    if (this) {

        block()

        return true

    }

    return false

}

```

因为kotlin是完全的面向对象 当然方法也是对象可以对方法扩展方法,如Function1代表一个参数一个返回值 Function10代表10个参数10返回值以此类推,如果不知道某个函数的类型可以用print输出一下,目前最多支持一个方法有22参数,因为发现只到Function22

`Function1<T,R>.hello(){} `对Kotlin中所有一个参数的函数扩展了hello函数 

扩展属性 

val/var  要接收类型名称.扩展属性名称:该扩展属性的名称如: 

`val <T> List<T>.lastIndex: Int` 

扩展的和原有的同样的用法一样,但扩展不支持多态,扩展的不能覆盖原有的(例子演示)

``` 

open class C

class D: C()

fun C.foo() = "c"

fun D.foo() = "d"

printFoo(D())

fun printFoo(c: C) {

    println(c.foo())

}

//这个例子会输出 "c",因为调用的扩展函数只取决于参数 c 的声明类型,该类型是 C 类。

class C {

    fun foo() { println("member") }

}

fun C.foo() { println("extension") }

//调用 C().foo(1) 将输出 "extension"

```

一个简单的例子演示扩展属性的方便之处

```

import android.content.Context

import kotlin.properties.ReadWriteProperty

import kotlin.reflect.KProperty

class Preference<T>(val context: Context, val name: String, val default: T, val prefName: String = "default")

    : ReadWriteProperty<Any?, T>{

    private val prefs by lazy {

        context.getSharedPreferences(prefName, Context.MODE_PRIVATE)

    }

    override fun getValue(thisRef: Any?, property: KProperty<*>): T {

        return findPreference(findProperName(property))

    }

    private fun findProperName(property: KProperty<*>) = if(name.isEmpty()) property.name else name

    private fun findPreference(key: String): T{

        return when(default){

            is Long -> prefs.getLong(key, default)

            is Int -> prefs.getInt(key, default)

            is Boolean -> prefs.getBoolean(key, default)

            is String -> prefs.getString(key, default)

            else -> throw IllegalArgumentException("Unsupported type.")

        } as T

    }

    override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {

        putPreference(findProperName(property), value)

    }

    private fun putPreference(key: String, value: T){

        with(prefs.edit()){

            when(value){

                is Long -> putLong(key, value)

                is Int -> putInt(key, value)

                is Boolean -> putBoolean(key, value)

                is String -> putString(key, value)

                else -> throw IllegalArgumentException("Unsupported type.")

            }

        }.apply()

    }

}

//  再进一步封装,kotLint支持具体化的泛型

inline fun <reified T> TreeNode.findParentOfType(): T? {

    var p = parent

    while (p != null && p !is T) {

        p = p.parent

    }

    return p as T?

}

//只有函数被inline修饰,同时泛型类型被reified修饰才能这样写,这样泛型类型和基本类型使用一样基于泛型的这种特性在此封装 

inline fun <reified F, T> .pre(default: T) = Preference(AppContext, "", default, R::class.jvmName)

//使用

var name by {Preference(")}

// 这样对name赋值就会存储到sp中 获取也是从sp中获取

``` 

上面通过扩展又引出另外一个知识点委托

1.类型委托 一个类实现一个借口但并不实现方法通过 by 的形式接收的对象是一个接口的实现类如 

``` 

  interface Base {

    fun printMessage()

    fun printMessageLine()

}

class BaseImpl(val x: Int) : Base {

    override fun printMessage() { print(x) }

    override fun printMessageLine() { println(x) }

}

class Derived(b: Base) : Base by b {

    override fun printMessage() { print("abc") }

}

fun main() {

    val b = BaseImpl(10)

    Derived(b).printMessage()

    Derived(b).printMessageLine()

}

interface Base {

    val message: String

    fun print()

}

class BaseImpl(val x: Int) : Base {

    override val message = "BaseImpl: x = $x"

    override fun print() { println(message) }

}

class Derived(b: Base) : Base by b {

    // 在 b 的 `print` 实现中不会访问到这个属性

    override val message = "Message of Derived"

}

fun main() {

    val b = BaseImpl(10)

    val derived = Derived(b)

    derived.print()

    println(derived.message)

}

//

通道

组合挂起函数

协程上下文与调度器

异常处理

Select 表达式

共享的可变状态与并发

    完整 Kotlin 参考(PDF)

    完整 Kotlin 参考(字大PDF)

    完整 Kotlin 参考(ePUB)

    完整 Kotlin 参考(Mobi)

编辑本页

委托

属性委托

属性委托在单独一页中讲:属性委托。

由委托实现

委托模式已经证明是实现继承的一个很好的替代方式, 而 Kotlin 可以零样板代码地原生支持它。 Derived 类可以通过将其所有公有成员都委托给指定对象来实现一个接口 Base:

interface Base {

    fun print()

}

class BaseImpl(val x: Int) : Base {

    override fun print() { print(x) }

}

class Derived(b: Base) : Base by b

fun main() {

    val b = BaseImpl(10)

    Derived(b).print()

}

Target platform: JVMRunning on kotlin v. 1.3.11

Derived 的超类型列表中的 by-子句表示 b 将会在 Derived 中内部存储, 并且编译器将生成转发给 b 的所有 Base 的方法。

覆盖由委托实现的接口成员

覆盖符合预期:编译器会使用 override 覆盖的实现而不是委托对象中的。如果将 override fun printMessage() { print("abc") } 添加到 Derived,那么当调用 printMessage 时程序会输出“abc”而不是“10”:

interface Base {

    fun printMessage()

    fun printMessageLine()

}

class BaseImpl(val x: Int) : Base {

    override fun printMessage() { print(x) }

    override fun printMessageLine() { println(x) }

}

class Derived(b: Base) : Base by b {

    override fun printMessage() { print("abc") }

}

fun main() {

    val b = BaseImpl(10)

    Derived(b).printMessage()

    Derived(b).printMessageLine()

}

//但请注意,以这种方式重写的成员不会在委托对象的成员中调用 ,委托对象的成员只能访问其自身对接口成员实现:

``` 

2.属性委托 

``` 

val 的属性 by lazy val lazyValue: String by lazy {    println("computed!")    "Hello"}

//lazy里面的代码之在当前对象的当前属性第一次被赋值的时候执行

class Example {

    var p: String by Delegate()

}

class Delegate {

    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {

        return "$thisRef, thank you for delegating '${property.name}' to me!"

    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {

        println("$value has been assigned to '${property.name}' in $thisRef.")

    }

}

// 上面的例子也讲过了 把属性委托给一个对象但该对象要实现getValue和setValue方法

import kotlin.properties.Delegates

class User {

    var name: String by Delegates.observable("<no name>") {

        prop, old, new ->

        println("$old -> $new")

    }

}

fun main() {

    val user = User()

    user.name = "first"

    user.name = "second"

}

//如果你想能够截获一个赋值并“否决”它,就使用 vetoable() 取代 observable()。 在属性被赋新值生效之前会调用传递给 vetoable 的处理程序

``` 

通过扩展再讲解下 函数的科理化,偏函数, 方法组合等

##Kotlin和java的交互 

实际常用简单的介绍

Kotlin中的companion 的属性或方法分别用@JvmField和@JvmStatic修饰方便java直接通过类名的形式调用 

Kotlin中的具名参数 加上@JvmOverloads方便java以重载的方式调用 

包级函数也是通过类名的形式调用

Kotlin调用java如对对用的属性添加get set方法 如是kotlin支持结构化的声明的方式调用java的对象就对java对象用component1修饰

##协程 

  协程是什么东西,不知道含义也不知到怎么用,官方解释是简易版的线程很朦胧 不少用户使用完协程也是含糊 接下来通过例子讲解 

  需求复制一个文件夹的全部文件(大概六七百兆)到另一个文件中 

  用协程和线程分别执行相同的操作比较耗时

  ```

/**

* 获取执行线程的名称 并判断是否是主线程

*/

fun myLog(tag: Any) {

    val name = Thread.currentThread().name

    println("$tag:$name 是否主线程:${name == mainThread}")

}

  // 获取主线程名称

  val mainThread by lazy {

    println("属性初始化")

    Thread.currentThread().name

}

/**

*开启一个线程

* 因为线程执行的随机性只能通过 回调的形式获取执行结果

* 这种形式可阅读性不是好

*/

fun runTask(time_: Long, taslCount: Int, block: () -> Unit, endListener: (Long, Int) -> Unit) {

    Thread {

        block()

        myLog("多线程name:")

        endListener(time_, taslCount)

    }.start()

}

// 线程执行完的回调

val verifyTask: (Long, Int) -> Unit = { it, taslCount ->

    counter_.addAndGet(1)

    println("短行程${counter_.get()}")

    if (counter_.get() == taslCount) {

        println("多m线程执行完成:${System.currentTimeMillis() - it}")

    }

/**

* 开启两个线程执行复制文件的任务

*  2 表示开启的线程数目

*  大致看很不直观把

*/

runTask(currentTimeMillis, 2,

            {

                File("$rootfileName/ta").copyRecursively(File("$rootfileName/target_a"))

            }, verifyTask)

    runTask(currentTimeMillis, 2,

            {

                File("$rootfileName/tb").copyRecursively(File("$rootfileName/target_b"))

            }, verifyTask) 

//File.copyRecursively 方法是kotlin扩展的文件递归复制文件 

  ```

  协程方式实现 

  ```

    public fun <T> runBlocking(context: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> T): T   

  ``` 

runBlocking是一个包级函数可以在程序任意地方直接调 

第一个参数有默认实现先不管它 第二个参数是一个用suspend修饰的 lambda表达式表达式 表达式值也是次函数的返回值 

  suspend修饰函数被称为挂起函数 并可以操作协程的任何方法

我们调用就传一个表达式 这样就开启了一个协程很简单 

```

fun main() {

  myLog("-1")

    runBlocking {

        myLog("heolloword")

    }

}

// 通过日志输出我们知道 协程中执行的表达式执行在主线程中

```

接下来我们让协程中的代码执行在子线程中 

```

//子线程执行

fun runTask2(block: () -> Unit) {

    Thread {

        block()

    }.start()

fun main() {

    runTask2 {

        runBlocking {

            myLog("子线程执行协程")

        }

    }

}

// 通过日志我们知道被协程执行的代码 

  runTask2 {

        val start_ = System.currentTimeMillis()

        var start = 0L

        var end = 0L

        runBlocking {

            myLog("线程1")

            start = System.currentTimeMillis()

            val j1 = launch {

                myLog("协程1")

                File("$rootfileName/t1").copyRecursively(File("$rootfileName/target1"))

            }

            val j2 = launch {

                myLog("协程2")

                File("$rootfileName/t2").copyRecursively(File("$rootfileName/target2"))

            }

            j1.join()

            j2.join()

            end = System.currentTimeMillis()

            println("协程执行时间:${end - start}")

        }

    }//这是用协程执行和线程同样的任务 很直观也较简单

``` 

通过比较比较一个子线程中开启两个协程执行任务和两个子线程执行同样的任务发现线程的方式较快 

大家有什么感想 

下一个例子在两个子线程中开启了两个协程和两个子线程执行同样的任务 

```

runTask2 {

        val start_ = System.currentTimeMillis()

        var start = 0L

        var end = 0L

        runBlocking {

            myLog("线程1")

            start = System.currentTimeMillis()

            val j1 = launch(Dispatchers.IO) {

                myLog("协程1")

                File("$rootfileName/t1").copyRecursively(File("$rootfileName/target1"))

            }

            val j2 = launch(Dispatchers.IO) {

                myLog("协程2")

                File("$rootfileName/t2").copyRecursively(File("$rootfileName/target2"))

            }

            j1.join()

            j2.join()

            end = System.currentTimeMillis()

            println("协程执行时间:${end - start}")

        }

``` 

通过例子我们判断协程的方式比线程的方式快一点点 通过这个例子大家有什么想法 

```

public fun CoroutineScope.launch(

    context: CoroutineContext = EmptyCoroutineContext,

    start: CoroutineStart = CoroutineStart.DEFAULT,

    block: suspend CoroutineScope.() -> Unit

): Job {

    val newContext = newCoroutineContext(context)

    val coroutine = if (start.isLazy)

        LazyStandaloneCoroutine(newContext, block) else

        StandaloneCoroutine(newContext, active = true)

    coroutine.start(start, coroutine, block)

    return coroutine

}

``` 

我们又看到一种协程的写法

  launch 的方式注意 launch只能执行在某一个协程中

  上面的例子我们通过 Dispatchers.IO 来执行协程中的代码执行的线程 

  有几个实现类 

  1.Dispatchers.IO 指定协程中的代码执行在线程池中 

  2.Dispatchers.Unconfined 也是一个线程池大家自己输出看看其执行的线程 

  3.Dispatchers.Default 默认 

  4.Dispatchers.Main 是Android特有指定运行在Android的UI线程 要使用它

  除了引入官方指定库 

  implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.0' 

    还要引入下面这个 

  implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.0" 

  5.newSingleThreadContext("MyOwnThread") 一个单例的线程池 字符串参数为线程的名称 

  大家可以看它们的实现方式自己声明一个线程池 

  我们又看到 Job.join() Job为launch协程的返回值

  在一个协程中开启的子协程的执行时随机的我们可以通过Job.join()等待当前协程执行完成 

下面再介绍一种开启协程的方式 

async的方式使用方式和launch一样不过它的返回值为Deferred<T> Deferred<T>.await()泛型的值也是 协程执行任务的返回值  例子演示 

```

suspend fun doSomethingUsefulOne(): Int {

    delay(1000L) // pretend we are doing something useful here

    return 13

}

suspend fun doSomethingUsefulTwo(): Int {

    delay(100L) // pretend we are doing something useful here, too

    return 29

}

fun main() = runBlocking<Unit> {

    //sampleStart

    val time = measureTimeMillis {

        val one = async { doSomethingUsefulOne() }

        val two = async { doSomethingUsefulTwo() }

        // some computation

        println("The answer is ${one.await() + two.await()}")

    }

    println("Completed in $time ms")

//sampleEnd   

``` 

协程的取消 

协程的取消和java的线程取消很类似 我们知道线程在sleep或休眠时此时才有一定几率被取消,协程类似在delay是(协程休眠)执行Job.cancel取消 同时我们在任务执行结束的时候 在协程中用isActive判断当前协程是否被执行取消操作,如果被执行了取消操作 下面的操作就不执行了和java线程的添加标记判断很类似 

``` 

fun main() = runBlocking {

//sampleStart

    val job = launch {

        repeat(1000) { i ->

            println("I'm sleeping $i ...")

            delay(500L)

        }

    }

    delay(1300L) // delay a bit

    println("main: I'm tired of waiting!")

    job.cancel() // cancels the job

    job.join() // waits for job's completion

    println("main: Now I can quit.")

//sampleEnd   

}

```

在Android中使用协程 如在Activitydestroy的时候 要取消协程防止内存泄露 例子如下 

``` 

import kotlin.coroutines.*

import kotlinx.coroutines.*

class Activity : CoroutineScope {

    lateinit var job: Job

    fun create() {

        job = Job()

    }

    fun destroy() {

        job.cancel()

    }

    // to be continued ...

    // class Activity continues

    override val coroutineContext: CoroutineContext

        get() = Dispatchers.Default + job

    // to be continued ...

    // class Activity continues

    fun doSomething() {

        // launch ten coroutines for a demo, each working for a different time

        repeat(10) { i ->

            launch {

                delay((i + 1) * 200L) // variable delay 200ms, 400ms, ... etc

                println("Coroutine $i is done")

            }

        }

    }

} // class Activity ends

fun main() = runBlocking<Unit> {

//sampleStart

    val activity = Activity()

    activity.create() // create an activity

    activity.doSomething() // run test function

    println("Launched coroutines")

    delay(500L) // delay for half a second

    println("Destroying activity!")

    activity.destroy() // cancels all coroutines

    delay(1000) // visually confirm that they don't work

//sampleEnd   

}

``` 

看到 Dispatchers.Default + job这个操作 这是因为CoroutineContext实现了 如下方法 

```

public operator fun plus(context: CoroutineContext): CoroutineContext =

        if (context === EmptyCoroutineContext) this else // fast path -- avoid lambda creation

            context.fold(this) { acc, element ->

                val removed = acc.minusKey(element.key)

                if (removed === EmptyCoroutineContext) element else {

                    // make sure interceptor is always last in the context (and thus is fast to get when present)

                    val interceptor = removed[ContinuationInterceptor]

                    if (interceptor == null) CombinedContext(removed, element) else {

                        val left = removed.minusKey(ContinuationInterceptor)

                        if (left === EmptyCoroutineContext) CombinedContext(element, interceptor) else

                            CombinedContext(CombinedContext(left, element), interceptor)

                    }

                }

            }

``` 

想理解的自己看看 总之CoroutineContext通过再”+“Job的形式 通过一个便操作的job来取消协程,这个协程的另一种取消方式 

下面再看一个较”古老”的协程的实现方式 

```

fun _startCoutinue(block: suspend () -> Unit) {

    block.startCoroutine(MyBaseContinuation()) // 只有suspend修饰的方法才有 startCoroutine的方法

}

suspend fun executeTask() = suspendCoroutine<String> { continuation ->

    MyTask {

        Thread.sleep(1000)

        myLog("协程里面")

//            SwingUtilities.invokeLater { 线程切换

        continuation.resume("张三")

    }.execute()

}

fun main() {

    myLog(-1)

    _startCoutinue {

        val executeTask: String = executeTask()

        myLog(1)

        myLog(executeTask)

    }

    Thread.sleep(4000)

}

class MyTask(val block: () -> Unit) {

    private val threadPool by lazy {

        Executors.newCachedThreadPool()

    }

    fun execute() = threadPool.execute(block)

}

``` 

网上基本找不到这种方法开启协程的 官方实例也没有 但一些三方协程框架都能看到它的影子 下面介绍一种用法

没写

协程的数据安全同线程一样建议使用 如AtomicInteger原子性的操作 

之前我们网络请求都是Retrofit结合Rxjava我们队Retrofit添加 retrofit2:adapter-rxjava适配 现在针对Retrofit的协程库retrofit2-kotlin-coroutines-adapter在Github也有1200多的star,使用协程结合Retrofit 会不会把网络请求写的太直观了,太简单那

今年协程10月kotlin1.3协程才成为正式成员 之前使用基本异常不会有日志输出调试很苦恼,现在已经解决

  1.3中协程又添加了 channel(通道),select表达式这都是实验性的 不稳定正式库中api会改动,大家可以看kotlin官方文档说明不建议在正式项目中使用 

  协程讲完了,大家对协程是怎么理解?

            

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容