Java8:java.time api【原創】

java8 新的時間api


本篇文章分為三個部分:

  1. 基礎的日期時間對象的使用
  2. 操作和解析日期時間對象
  3. 基于時區的調整,使用不同的歷法

由于眾所周知的原因,java中的java.util.Datejava.util.Calendar無論從設計上還是使用上都存在問題,同時也不適應新的函數式編程的新浪潮。出于多方面原因的考慮,最后在java8中新增了java.time,這個專門處理時間相關問題的包。

1. LocalDate, LocalTime, Instant, Duration, Period

想了解time包中的時間api, LocalDate, LocalTime, Instant, Duration, Period 這幾個類庫應該是最基礎的內容。

1.1 使用LocalDate, LocalTime

使用新的日期和時間api,LocalDateLocalTime應該是基礎中的基礎,我們來一個一個了解。

LocalDate

LocalDate 第一次使用肯定會與Date產生聯想,其實作為time包中基礎的類,LocalDate和原Date對象有很大的不同。

首先,LocalDate對象是不可變對象(類似于String,對象的屬性和值不可改變);其次,只提供簡單的日期信息,并不包含日期當天的時分秒等時間信息;當然也不包含任何時區相關的信息。所以簡單來說,LocalDate對象是只記錄了簡單日期信息的不可變對象。

可以通過靜態方法of()獲取指定的日期或者使用工廠now()方法獲取當前日期

/**
    * 創建LocalDate對象的幾種方式
    *
    * 1. of 方式
    * 2. now 方式
    *
    * 輸出:
    * 2018-04-20
    * 2018-05-09
    */
public static void createLocalDateDemo() {

    // 通過of方法創建LocalDate對象
    int year = 2018;
    Month month = Month.of(4);
    // month是內置的枚舉,直接通過具體的值指定月份也可以
    Month month2 = Month.APRIL;
    int day = 20;
    LocalDate ofDate = LocalDate.of(year, month, day);
    System.out.println(ofDate.toString());

    // 通過靜態方法now 創建,當前日期
    LocalDate nowDate = LocalDate.now();
    System.out.println(nowDate);
}

讀取LocalDate的屬性也很簡單:

/**
 * 讀取localDate的屬性
 *
 * 輸出:
 * year:2018  month:5  day-of-month:9
 */
public static void getLocalDateField() {

    LocalDate now = LocalDate.now();

    int year = now.getYear();
    Month month = now.getMonth();
    int day = now.getDayOfMonth();

    System.out.print("year:" + year + "  ");
    System.out.print("month:" + month.getValue() + "  ");
    System.out.println("day-of-month:" + day);
}

除了簡單的年月日之外,LocalDate還記錄了一些額外日期的信息

// 除了簡單的年月日之外,LocalDate還記錄了一些十分有用的和日期相關的信息
boolean isLeapYear = now.isLeapYear(); // 是否是閏年
int lengthOfMonth = now.lengthOfMonth(); // 當前月份有多少天
DayOfWeek dow = now.getDayOfWeek(); // 當前是周幾

如果你有看到LocalDate的源碼,你會發現LocalDate實現了Temporal接口。所以,還可以通過傳遞TemporalField參數給get方法來獲取指定的信息。TemporalField是一個接口,定義了如何訪問temporal對象的某個字段。而ChronoField枚舉則實現了這一接口,所以可以很方便的使用get方法獲取到枚舉元素的值。

/**
* 通過Temporal接口獲取LocalDate的屬性
* 
* 輸出:
* 通過Temporal訪問LocalDate的屬性:2018-5-9 
*/
public static void getFieldByTemporal() {

    LocalDate now = LocalDate.now();

    int year = now.get(ChronoField.YEAR);
    int month = now.get(ChronoField.MONTH_OF_YEAR);
    // 如果想把int類型的month轉為枚舉,可以使用of 方法
    Month month2 = Month.of(month);
    int day = now.get(ChronoField.DAY_OF_MONTH);

    System.out.println("通過Temporal訪問LocalDate的屬性:" + year + "-" +  month + "-" + day);
}

LocalTime

LocalTime存儲的是單純的時間信息,不包含日期。除此之外基本和LocalDate的屬性相似,都是不可變對象,實現了Temporal接口等等。

首先來看LocalTime的創建

/**
* 創建LocalTime
*
* 輸出:
* 10:39:44.951
* 12:12:12.000100
*/
public static void createLocalTime() {

    // 通過工廠方法now創建
    LocalTime now = LocalTime.now();
    System.out.println(now);

    // 通過制定參數of 方法創建
    LocalTime ofTime = LocalTime.of(12, 12, 12,100000);
    System.out.println(ofTime);
}

然后是對應的屬性的讀取,同樣也是和LocalDate類似

/**
* 讀取LocalTime的值
*
* 輸出:
* 10:50:54
* 10:50:54
*/
public static void getLocalTimeField() {

    LocalTime nowTime = LocalTime.now();

    int hour = nowTime.getHour();
    int minute = nowTime.getMinute();
    int second = nowTime.getSecond();
    System.out.println(hour + ":" + minute + ":" + second);

    // 同樣,LocalTime 也可以通過Temporal來獲取指定屬性的值
    int tempOfHour = nowTime.get(ChronoField.HOUR_OF_DAY);
    int tempOfMinute = nowTime.get(ChronoField.MINUTE_OF_HOUR);
    int tempOfSecond = nowTime.get(ChronoField.SECOND_OF_MINUTE);
    System.out.println(tempOfHour + ":" + tempOfMinute + ":" + tempOfSecond);

}

以上是LocalDateLocalTime的基本使用,但是實際開發中其實我們用的最多的是格式化的String轉為日期和時間對象。當然,新的時間api在這方面的支持也是相當完善的,而且比以前的效果更好更簡潔:

LocalDate date = LocalDate.parse("2018-12-12");
LocalTime time = LocalTime.parse("12:12:12");
System.out.println(date + " " + time); //輸出:2018-12-12 12:12:12

查看源碼可以看出,這里其實是使用默認的標準的ISO formatter,DateTimeFormatter是新版的時間格式化類,規定的如何將StringLocal 系列的日期時間對象對應起來,實際使用中可以使用該對象來完成字符串和日期時間對象之間的互轉。

LocalDate parse 源碼

1.2 使用LocalDateTime

實際開發中,我們很少會將日期和時間拆開使用,大多數情況下兩者都是存在的。新的time包中有LocalDateTime 這一組合對象,此時的LocalDateTime有一點點類似于Date了,同時存有日期和時間。不同之處在于LocalDateTime仍然是不可變對象,且不包含任何時區信息。

LocalDateTime 內部使用的是LocalDate和LocalTime

創建LocalDateTime的方式和LocalDateLocalTime類似

/**
* 創建LocalDateTime 對象
*
* 指定時間為:2018-5-11T12:12:12
*/
public static void createLocalDateTime() {

    // 通過of 方法直接創建
    LocalDateTime dateTime = LocalDateTime.of(2018, 5, 11, 12, 12, 12);

    // 通過LocalDate和Time 合并實現
    LocalDate date = LocalDate.of(2018, 5, 11);
    LocalTime time = LocalTime.of(12, 12, 12);
    LocalDateTime dateTime2 = LocalDateTime.of(date, time);

    // 通過LocalDate的 atTime 創建
    LocalDateTime dateTime3 = date.atTime(time);
    LocalDateTime dateTime4 = date.atTime(12, 12, 12);

    // 通過LocalTime的 atDate 創建
    LocalDateTime dateTime5 = time.atDate(date);
}

因為是組合對象,所以可讀取一部分來獲取LocalDate或者LocalTime

LocalDateTime now = LocalDateTime.now();
LocalDate date = now.toLocalDate();
LocalTime time = now.toLocalTime();

到目前為止我們了解了LocalDateLocalTime 以及 LocalDateTime ,它們的關系如下:

三者的關系

1.3 Instant,關于機器的時間

作為人類,我們理解時間的概念都是幾年幾月幾天幾分幾秒等等,毫無疑問機器肯定已經不會以這種方式處理時間,這一點從老的DateCalendar就可以看出來。所以在time包中,類似于時間戳的這種底層的處理時間的類為Instant

當然,我們最好的理解應該是:Instant是與機器交互的時間處理類。因此Instant不需要記錄年,月,日等等,類似于時間戳,Instant記錄的是從Unix元年(UTC時區1970年1月1日午夜零分)到現在的秒數,可以通過ofEpochSecond工廠方法創建,當然還存在一個增強版本,可以額外的接口一個以納秒為單位的數值,來精確的記錄時間。

/**
* 創建Instant
*/
public static void createInstant() {

    // 通過工廠方法now創建
    Instant instant = Instant.now();

    // 通過工廠方法ofEpochSecond創建
    Long timestamp = instant.getEpochSecond();
    Instant instant1 = Instant.ofEpochSecond(timestamp);

    // 增強版本,可以傳遞一個納秒,
    Instant instant2 = Instant.ofEpochSecond(1000);
    Instant instant3 = Instant.ofEpochSecond(1000, 0L);
    Instant instant4 = Instant.ofEpochSecond(999, 1_000_000_000L);
    Instant instant5 = Instant.ofEpochSecond(998, 2_000_000_000L);
    Instant instant6 = Instant.ofEpochSecond(1001, -1_000_000_000L);
}

關于ofEpochSecond(int second, long nanoSecond)的增強版本,會將納秒調整在0~999,999,999 之間。所以當納秒數超過這個范圍的時候,程序會根據具體的值進行調整。所以,demo代碼中的這幾種方式創建的Instant都是相等的。

增強重載版本的源碼

Instant是設計用于和機器交互的時間類,雖然實現了Temporal接口,但是內部是沒有年月日,時分秒等屬性的,因此一下代碼的調用會扔出Runtime異常

// 會扔出運行時異常
int day = Instant.now().get(ChronoField.DAY_OF_MONTH);
image.png

1.4 Duration和Period

處理常規的表達時間點的概念之外,time新增了表示時間段的類DurationPeriod。在java8之前,我們只能通過數字加人為規定的單位來表達時間段這一概念。但是在實際開發中,老的時間api計算時間段是真的不方便,而且效率低下。使用的新表達時間段的類就可以很方便的解決這個問題

創建Duration的方式很簡單,使用between方法即可,可以傳入兩個Instant,兩個LocalDateTime或者兩個Localtime對象來進行創建

LocalDateTime from = LocalDateTime.of(2018, 4, 1, 0, 0, 0);
LocalDateTime to = LocalDateTime.now();
Duration duration = Duration.between(from, to);
System.out.println(duration); // PT953H28M16.279S 代表:953小時28分鐘16.279秒

為什么不能將InstantLocalDateTime混用呢,因為Instant是給機器設計的,LocalDateTime是給人設計的,兩個目的不一樣,因此不能混用。除此之外,Duration類是主要以秒和納秒來表達時間段的,從單位上來說比較精確,因此也不能使用LocalDate來計算兩個日期之間的時間段。

當然,如果要表達最小以天為單位的時間段,就可以使用Period

LocalDate from = LocalDate.of(2018, 4, 1);
LocalDate to = LocalDate.of(2018, 5, 2);
Period period = Period.between(from, to);
System.out.println(period); // P1M1D  表示:1個月零2天

到這里,我們就很明白了。DurationPeriod都可以表示一段時間。兩者最主要的卻別在于度量的單位不同,Duration主要是以時分秒甚至于毫秒來較為精確的度量一段時間,而Period則是從年月日的角度來表示一段時間。實際開發中,可以視不同的業務需求來使用。

除了between之外,DurationPeriod還有很多工廠方法來獲取實例化的時間對象

Duration threeMinutes = Duration.ofMinutes(3);  // 三分鐘
Duration fiveMinutes = Duration.of(5, ChronoUnit.MINUTES); //五分鐘

Period threeDays = Period.ofDays(3); //三天
Period twoWeeks = Period.ofWeeks(2); // 兩周
Period oneYear = Period.ofYears(1); // 一年
Period fiveMonth = Period.ofMonths(5); //五個月
Period towYearsOneMonthTenDays = Period.of(2, 1, 10); // 兩年一個月零十天

上述代碼中只是簡單地舉了一個例子,其實DurationPeriod中有很多相似的工廠方法來創建實例化的時間段。

方法名 是否是靜態方法 方法描述
between 創建兩個時間點之間的interval
from 由一個臨時節點創建interval
of 由它的組成部分創建interval的實例
parse 由字符串創建nterval
addTo 創建該interval的副本,并將其疊加到某個指定的Temporal對象
get 讀取該interval的狀態
isNegative 檢查該interval是否為負值,不包含0
isZero 檢查該interval是否為0
minus 減去一定的時間創建interval的副本
multipliedBy 將interval乘以某個標量來創建其副本
negated 以忽略某個時長的方式創建interval的副本
plus 以增加某個時長的方式創建interval的副本
subtractFrom 從指定的temporal對象中減去該interval來創建其副本

2. 操作和解析日期與時間

除了創建和讀取日期時間對象,實際開發中不可避免的存在修改,解析日期時間對象的需求,下面對這方面的內容進行講解。

2.1 操作日期和時間對象

with操作

首先是修改日期時間對象。第一部分反復強調,以上我們提到的所有的日期時間對象都是固定的不可更改的對象。所以,下文除非特殊說明的情況下都是基于原對象修改后返回的新日期時間對象,而原對象的屬性值都不變。

最常用的基本的修改日期和時間對象屬性的方法是withAttribute 類型的方法。

// 2018-04-01
LocalDate date = LocalDate.of(2018, 4 , 1);

// 使用 withAttribute 類型的方法可基于已有對象的屬性修改創建得到新的日期對象,原對象不變
LocalDate date2 = date.withDayOfMonth(12); // 2018-04-12
System.out.println(date + " => " + date2);
LocalDate date3 = date2.withYear(2019); // 2019-04-12
System.out.println(date2 + " => " + date3);

當然,除了固定的修改某個字段的with方法之外還有通用的with方法,因為我們上面提到的所有的日期時間對象都實現了Temporal接口,這個就不在贅述,舉例如下:

// 也可以使用通用的with方法來對指定的屬性進行修改, 比如之類指定修改月份這一屬性
LocalDate date4 = date3.with(ChronoField.MONTH_OF_YEAR, 10); // 2019-10-12
System.out.println(date3 + " => " + date4);

加減操作

with類型的方法是直接基于原有屬性修改為指定的屬性,除此之外開發中也會存在基于已有時間的加減操作。比如兩周之后,五個月之前等等。

// 2018-04-01
LocalDate date = LocalDate.of(2018, 4, 1);

LocalDate date2 = date.plusDays(10); // 2018-04-11
System.out.println(date + " => " + date2);
LocalDate date3 = date2.minusMonths(2).plusYears(1); // 2018-02-11 => 2019-02-11
System.out.println(date2 + " => " + date3);

// 19年2月為28天,所以四周后為 2019-03-11
LocalDate date4 = date3.plus(4, ChronoUnit.WEEKS);
System.out.println(date3 + " => " + date4);

總結一下:到這里我們講了兩種操作方法with類型的方法加減類型的方法。需要說明的是LocalDateLocalTimeLocalDateTime 都是支持上述方法的。且with和加減方法都支持指定單位修改和傳入指定單位兩種修改模式。前者簡單直接調用,后者則更為通用,實際開發中可視具體情況調用。

此外,提到了兩個Chrono開頭的枚舉,一個是ChronoField,這個指定的日期時間對象的具體屬性(比如:時間對象中的一小時的秒數,一秒鐘的納秒數等等,with方法修改的就是直接日期和時間對象的屬性)。另一個ChronoUnit,這個指的是日期的長度單位(比如:年,月,周等等,加減類型的方法則是基于時間單位進行運算,從而修改日期時間對象的屬性)。

LocalDateLocalTimeLocalDateTime, Instant 這幾個類中還存在著大量通用型的方法,實際開發中可以針對具體的需求來查看和使用,這里不再一一贅述。

TemporalAdjuster

本來講到這里,關于日期和時間對象的修改已經滿足了大部分的需求了。但是,實際開發中我們遇到的變態需求往往才是我們關注的重點,如何滿足這一部分的需求才是重點需要描述的內容。

舉例一下情況:

  • 當前日期后的下一個周日
  • 五月的第二個周四
  • 當前月的最后一天
  • 明年的第一天是周幾

以上四個類似基于目前我們了解到的內容處理起來還是比較棘手的,因為這些邏輯都相對來說比較復雜,不是很直接。這個時候就需要TemporalAdjuster類來幫助我們更加靈活的處理和計算日期。

首先,TemporalAdjuster中預置了很多日常開發中比較常見的調整模式,我們可以借助通用的with方法,來對已有日期進行計算。下面我們對上面的四個例子來進行實現和說明。

// 假設當前是:2018-04-02
LocalDate date = LocalDate.of(2018, 4, 2);

// 當前日期的下一個周日
LocalDate date2 = date.with(nextOrSame(DayOfWeek.SUNDAY));
System.out.println(date2); // 2018-04-08

// 五月的第二個周四
LocalDate date3 = date.plusMonths(1).withDayOfMonth(1) // 先修改日期至5月1日
        .with(nextOrSame(DayOfWeek.THURSDAY)) //如果5月1日為周四,則不往后,所以這里用nextOrSame
        .with(next(DayOfWeek.THURSDAY)); // 第二次,這個日期肯定是周四,所以強制往后,使用next
System.out.println(date3); // 2018-05-10

// 當月的最后一天的日期
LocalDate date4 = date.with(lastDayOfMonth());
System.out.println(date4); // 2018-04-30

// 明年的第一天是周幾
DayOfWeek date5 = date.with(firstDayOfNextYear()).getDayOfWeek();
System.out.println(date5.getValue()); // 2 周二

定制TemporalAdjuster

當然了,這種復雜的日期調整規則除了常見的之外,還有很多奇奇怪怪的需求,這些需求都是預置的規則滿足不了的。這個時候我們就需要根據自己的需求來實現對應的邏輯。

要實現自己的TemporalAdjuster也十分容易,首先來看一下其源碼:

TemporalAdjuster 函數式接口

很明顯,這是一個函數式申明的接口,對應的輸入輸出都是Temporal對象。所以,我們只需要針對這個接口實現對應的邏輯即可,如果項目中實現的邏輯較為復雜且多處調用,就可以抽象為靜態的工具方法;否則直接使用lambda表達式即可。

這里我們舉個例子,實現一個TemporalAdjuster,返回當前日期往后的第一個工作日。這里不考慮法定節假日(當然,如果實際項目中有這樣的需求,則必須有法定節假日相關的接口或者配置數據,否則沒有辦法動態實現,因為目前來說國內的節假日都是國家根據當前的情況調整的)

規則抽象:

如果當前是周一到周四,則返回當前日期的下一天,否則返回下一個周一

實現:

// 下一個工作日的實現
TemporalAdjuster nextWorkingDay = (Temporal temporal) -> {
    Objects.requireNonNull(temporal);
    int dayOfWeek = temporal.get(ChronoField.DAY_OF_WEEK);
    if (dayOfWeek >= 1 && dayOfWeek <= 4) {
        return temporal.plus(1L, ChronoUnit.DAYS);
    }
    return temporal.with(next(DayOfWeek.MONDAY));
};

// 測試
LocalDate date = LocalDate.of(2018, 4, 1);
System.out.println(date.with(nextWorkingDay)); // 2018-04-02
LocalDate date2 = date.plusMonths(3).plusDays(2);
System.out.println(date2.with(nextWorkingDay)); // 2018-07-04

2.2 解析和格式化日期和時間對象

處理日期和時間相關方面的業務,還有一個很重要的方面就是格式化輸出日期和解析日期相關的字符串。在java8中,java.time.format包就是用來格式化和解析日期相關的內容。

上文我們提到過格式化輸出日期的的類DateTimeFormatter就是java.tiem.format包下最常用的格式化日期時間的類。接下來的內容就圍繞DateTimeFormatter來進行講解。

DateTimeFormatter基本使用

DateTimeFormatter和原來的java.util.DateFormat最大的不同就是其是線程安全的。這是一個十分重要的點,線程安全意味著能夠以單例的模式創建格式化的容器,并在多個線程之間共享。除此之外,其實新的time包中幾乎所有的設計都在強調不可變性,這就意味著在多線程的情況下,新的time包中的內容我們可以大膽放心的使用,這在多線程流的配合下,處理大量的日期時間類數據時十分有效的。

關鍵字: 線程安全

因為是線程安全的,所以DateTimeFormatter內置了很多常用的實例,如下:

// 2018-04-01
LocalDate date = LocalDate.of(2018, 4, 1);

// 格式化輸出
String basicIsoDateStr = date.format(DateTimeFormatter.BASIC_ISO_DATE); // 20180401
String isoLocalDate = date.format(DateTimeFormatter.ISO_LOCAL_DATE); // 2018-04-01
System.out.println("格式化輸出:\n" + basicIsoDateStr + "\n" + isoLocalDate);

// 解析
LocalDate date2 = LocalDate.parse(basicIsoDateStr, DateTimeFormatter.BASIC_ISO_DATE);
LocalDate date3 = LocalDate.parse(isoLocalDate, DateTimeFormatter.ISO_LOCAL_DATE);
System.out.println("解析輸出:\n" + date2 + "\n" + date3);

這里需要說明的是,將日期時間格式化輸出為字符串和將字符串解析為對應的日期時間對象往往同時出現的。換個角度理解,DateTimeFormatter 存在的意義就是將日期時間對象和特定格式的日期時間字符串聯系起來,成為兩者互轉的一個紐帶。

紐帶

當然,實際開發中自定義格式化的格式也是不可避免的,如下:

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日");
LocalDate date = LocalDate.of(2018, 4, 1);

String dateStr = date.format(formatter);
System.out.println(dateStr);

LocalDate date2 = LocalDate.parse(dateStr, formatter);
System.out.println(date2);

除了自定格式之外,本地化也是一個十分重要的點,如下 :

LocalDate date = LocalDate.of(2018, 4, 1);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yy年 MMMM d", Locale.CHINA);

String dateStr = date.format(formatter);
System.out.println(dateStr); // 18年 四月 1
LocalDate date2 = LocalDate.parse(dateStr, formatter);
System.out.println(date2); // 2018-04-01

最后,需要說明的是formatter還支持builder模式,這樣創建自定的格式時將會非常的高效和使用,如下:

LocalDate date = LocalDate.of(2018, 04, 01);
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
        .appendText(ChronoField.YEAR)
        .appendLiteral("年")
        .appendText(ChronoField.MONTH_OF_YEAR, TextStyle.FULL)
        .appendText(ChronoField.DAY_OF_MONTH, TextStyle.FULL_STANDALONE)
        .appendLiteral("日 ")
        .appendText(ChronoField.DAY_OF_WEEK, TextStyle.FULL)
        .parseCaseInsensitive()
        .toFormatter(Locale.CHINESE);

String dateStr = date.format(formatter);
System.out.println(dateStr); // 2018年四月1日 星期日
LocalDate date2 = LocalDate.parse(dateStr, formatter);
System.out.println(date2); // 2018-04-01

3. 處理不同的時區和歷法

未完待續。。。


參考


demo 代碼


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

推薦閱讀更多精彩內容