android数据库的简单Demo(原生版+Google版)

本文出自 “阿敏其人” 简书博客,转载或引用请注明出处。

android和ios的数据库都是用SQLite来实现。
在安卓里面数据库怎么用呢,简单来说可用分为以下三步:
1、新建一个数据库帮助类,继承自SQLiteOpenHelper,复写onCreate() 和 onUpgrade()
2、新建一个数据里操作类(dao类),利用 数据库帮助类 得到数据库的实例,然后在dao类里面编写 增删改查 的方法
3、在Activity里面实例化数据库操作类(dao类),调用对应的 增删查改方法。
(对数据进行的操作也就无非四种,即CRUD。其中C代表添加(Create),R代表查询(Retrieve),U代表更新(Update),D代表删除(Delete)。有人叫增删改查,有人叫增删查改)

来图片最直接

增删改查演示.gif

结构简图

数据库demo结构简图.png


一、SQLite数据库简介

  • 轻量级 : SQLite数据库是一个轻量级的数据库, 适用于少量数据的CURD;
  • 文件本质 : SQLite数据库支持大部分SQL语法, 允许使用SQL语句操作数据库, 其本质是一个文件, 不需要安装启动;
  • 数据读写 : SQLite数据库打开只是打开了一个文件的读写流, 如果有大数据量读写, 需要高并发存储, 那么就不应该使用SQLite;

二、安卓数据如何使用

1、编写数据库帮助类

  • 新建一个数据库帮助类,继承自SQLiteOpenHelper
  • 编写构造函数
  • 复写onCreate() 和 onUpgrade()

代码如下:

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

/**
 * 数据库Helper类,必须继承自 SQLiteOpenHelper
 * 当一个继承自 SQLiteOpenHelper 后需要复写两个方法,分别是 onCreate()  和 onUpgrade()
 * onCreate(): onCreate是在数据库创建的时候调用的,主要用来初始化数据表结构和插入数据初始化的记录
 * onUpgrade():onUpGrade是在数据库版本升级的时候调用的,主要用来改变表结构
 *
 *
 *  数据库帮助类要做的事情特别简单:
 *  1、复写onCreate()  和 onUpgrade()方法
 *  2、在这两个方法里面填写相关的sql语句
 *
 *
 */
public class MyDBHelper extends SQLiteOpenHelper{

    public MyDBHelper(Context context) {
        /**
         * 参数说明:
         *
         * 第一个参数: 上下文
         * 第二个参数:数据库的名称
         * 第三个参数:null代表的是默认的游标工厂
         * 第四个参数:是数据库的版本号  数据库只能升级,不能降级,版本号只能变大不能变小
         */
        super(context, "mintest.db", null, 2);
    }


    /**
     * onCreate是在数据库创建的时候调用的,主要用来初始化数据表结构和插入数据初始化的记录
     *
     * 当数据库第一次被创建的时候调用的方法,适合在这个方法里面把数据库的表结构定义出来.
     * 所以只有程序第一次运行的时候才会执行
     * 如果想再看到这个函数执行,必须写在程序然后重新安装这个app
     */

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL("create table contactinfo (id integer primary key autoincrement, name varchar(20), phone varchar(20))");
    }


    /**
     * 当数据库更新的时候调用的方法
     * 这个要显示出来得在上面的super语句里面版本号发生改变时才会 打印  (super(context, "itheima.db", null, 2); )
     * 注意,数据库的版本号只可以变大,不能变小,假设我们当前写的版本号是3,运行,然后又改成1,运行则报错。不能变小
     * @param db
     * @param oldVersion
     * @param newVersion
     */
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        db.execSQL("alter table contactinfo add account varchar(20)");
    }
}


2、数据库操作类(dao类)

dao类在这里做得事情特别简单:

  • 1、定义一个构造方法,利用这个方法去实例化一个 数据库帮助类
  • 2、编写dao类的对应的 增删改查 方法。

代码如下:


package amqr.com.dbanddao.dao;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;

import amqr.com.dbanddao.db.MyDBHelper;

/**
 *  ContactInjfoDao  数据库操作类  dao后缀的都是数据库操作类
 *
 *  我们这里的每一个 增删改查 的方法都通过getWritableDatabase()去实例化了一个数据库,这里必须这么做
 *  不客气抽取 成为一个成员变量, 否则报错,若是觉得麻烦可以通过定义方法来置为null和重新赋值
 *
 *  —— 其实dao类在这里做得事情特别简单:
 *  1、定义一个构造方法,利用这个方法去实例化一个  数据库帮助类
 *  2、编写dao类的对应的 增删改查 方法。
 *
 */
public class ContactInjfoDao {

    private MyDBHelper mMyDBHelper;

    /**
     * dao类需要实例化数据库Help类,只有得到帮助类的对象我们才可以实例化 SQLiteDatabase
     * @param context
     */
    public ContactInjfoDao(Context context) {
        mMyDBHelper=new MyDBHelper(context);
    }

    // 将数据库打开帮帮助类实例化,然后利用这个对象
    // 调用谷歌的api去进行增删改查

    // 增加的方法吗,返回的的是一个long值
    public long addDate(String name,String phone){
        // 增删改查每一个方法都要得到数据库,然后操作完成后一定要关闭
        // getWritableDatabase(); 执行后数据库文件才会生成
        // 数据库文件利用DDMS可以查看,在 data/data/包名/databases 目录下即可查看
        SQLiteDatabase sqLiteDatabase =  mMyDBHelper.getWritableDatabase();
        ContentValues contentValues=new ContentValues();

        contentValues.put("name",name);
        contentValues.put("phone", phone);
        // 返回,显示数据添加在第几行
        // 加了现在连续添加了3行数据,突然删掉第三行,然后再添加一条数据返回的是4不是3
        // 因为自增长
        long rowid=sqLiteDatabase.insert("contactinfo",null,contentValues);

        sqLiteDatabase.close();
        return rowid;
    }


    // 删除的方法,返回值是int
    public int deleteDate(String name){
        SQLiteDatabase sqLiteDatabase = mMyDBHelper.getWritableDatabase();
        int deleteResult = sqLiteDatabase.delete("contactinfo", "name=?", new String[]{name});
        sqLiteDatabase.close();
        return deleteResult;
    }

    /**
     * 修改的方法
     * @param name
     * @param newPhone
     * @return
     */
    public int updateData(String name,String newPhone){
        SQLiteDatabase sqLiteDatabase = mMyDBHelper.getWritableDatabase();
        ContentValues contentValues =new ContentValues();
        contentValues.put("phone", newPhone);
        int updateResult = sqLiteDatabase.update("contactinfo", contentValues, "name=?", new String[]{name});
        sqLiteDatabase.close();
        return updateResult;
    }

    /**
     * 查询的方法(查找电话)
     * @param name
     * @return
     */
    public String alterDate(String name){
        String phone = null;

        SQLiteDatabase readableDatabase = mMyDBHelper.getReadableDatabase();
        // 查询比较特别,涉及到 cursor
        Cursor cursor = readableDatabase.query("contactinfo", new String[]{"phone"}, "name=?", new String[]{name}, null, null, null);
        if(cursor.moveToNext()){
            phone=cursor.getString(0);
        }
        cursor.close(); // 记得关闭 corsor
        readableDatabase.close(); // 关闭数据库
        return phone;
    }


}


3、在Activity里面利用dao类操作数据库

xml布局文件

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              xmlns:tools="http://schemas.android.com/tools"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical"
              tools:context=".MainActivity" >
    <EditText
        android:id="@+id/mEtName"
        android:hint="请输入联系人的姓名"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        >
    </EditText>
    <EditText
        android:id="@+id/mEtPhone"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="请输入联系人的电话"
        android:inputType="phone" />
    <Button
        android:onClick="add"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="添加" />
    <Button
        android:onClick="delete"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="删除" />
    <Button
        android:onClick="update"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="修改" />
    <Button
        android:onClick="query"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="查询" />
</LinearLayout>


4、Activity代码:

package amqr.com.dbanddao;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;

import amqr.com.dbanddao.dao.ContactInjfoDao;

public class MainActivity extends AppCompatActivity {
   private EditText mEtName;
   private EditText mEtPhone;
   private ContactInjfoDao mDao;

   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);

       mDao=new ContactInjfoDao(MainActivity.this);
       mEtName= (EditText) findViewById(R.id.mEtName);
       mEtPhone= (EditText) findViewById(R.id.mEtPhone);

   }

   public void add(View view){

       String name=mEtName.getText().toString().trim();
       String phone=mEtPhone.getText().toString().trim();
       if(TextUtils.isEmpty(name)||TextUtils.isEmpty(phone)){
           Toast.makeText(this,"填写不完整",Toast.LENGTH_SHORT).show();
           return;
       }else{
           long addLong = mDao.addDate(name, phone);
           if(addLong==-1){
               Toast.makeText(this,"添加失败",Toast.LENGTH_SHORT).show();
           }else{
               Toast.makeText(this,"数据添加在第  "+addLong+"   行",Toast.LENGTH_SHORT).show();
           }

       }
   }

   public void delete(View view){
       String name=mEtName.getText().toString().trim();


       if(TextUtils.isEmpty(name)){
           Toast.makeText(this,"填写不完整",Toast.LENGTH_SHORT).show();
           return;
       }else{
           int deleteDate = mDao.deleteDate(name);
           if(deleteDate==-1){
               Toast.makeText(this,"删除失败",Toast.LENGTH_SHORT).show();
           }else{
               Toast.makeText(this,"成功删除  "+deleteDate+"   条数据",Toast.LENGTH_SHORT).show();
           }

       }

   }


   public void update(View view){

       String name=mEtName.getText().toString().trim();
       String phone=mEtPhone.getText().toString().trim();
       if(TextUtils.isEmpty(name)||TextUtils.isEmpty(phone)){
           Toast.makeText(this,"填写不完整",Toast.LENGTH_SHORT).show();
           return;
       }else{
           int count=mDao.updateData(name, phone);
           if(count==-1){
               Toast.makeText(this,"更新失败",Toast.LENGTH_SHORT).show();
           }else{
               Toast.makeText(this,"数据更新了  "+count+"   行",Toast.LENGTH_SHORT).show();
           }

       }
   }


   public void query(View view){

       String name=mEtName.getText().toString().trim();
       
       if(TextUtils.isEmpty(name)){
           Toast.makeText(this,"填写不完整",Toast.LENGTH_SHORT).show();
           return;
       }else{
           String phoneResult = mDao.alterDate(name);

           Toast.makeText(this,"手机号码为:    "+phoneResult,Toast.LENGTH_SHORT).show();


       }
   }

}

至此完成。

三、数据库文件在那里

当我们调用dao里面的方法,dao里面的方法就都会实例化数据库,比如
JAVA SQLiteDatabase sqLiteDatabase = mMyDBHelper.getWritableDatabase();
当这句代码的getWritableDatabase();执行后生成数据库文件

具体位置在data/data/包名/databases路径下

数据库文件.png

四、简单的数据库语句知识

在Android平台上,集成了一个嵌入式关系型数据库—SQLite,SQLite3支持 NULL、INTEGER、REAL(浮点数字)、TEXT(字符串文本)和BLOB(二进制对象)数据类型。
虽然它支持的类型只有五种,但实际上sqlite3也接受varchar(n)、char(n)、decimal(p,s) 等数据类型,只不过在运算或保存时会转成对应的五种数据类型。
SQLite最大的特点是你可以把各种类型的数据保存到任何字段中,而不用关心字段声明的数据类型是什么。例如:可以在Integer类型的字段中存放字符串,或者在布尔型字段中存放浮点数,或者在字符型字段中存放日期型值。 但有一种情况例外:定义为INTEGER PRIMARY KEY的字段只能存储64位整数, 当向这种字段保存除整数以外的数据时,将会产生错误。 另外,在编写CREATE TABLE 语句时,你可以省略跟在字段名称后面的数据类型信息,如下面语句你可以省略 name字段的类型信息:

CREATE TABLE person (personid integer primary key autoincrement, name varchar(20))

.

SQLite可以解析大部分标准SQL语句,如:

  • 查询语句:select * from 表名 where 条件子句 group by 分组字句 having ... order by 排序子句

如: select * from person
select * from person order by id desc
select name from person group by name having count(*)>1

分页SQL与mysql类似,下面SQL语句获取5条记录,跳过前面3条记录

select * from Account limit 5 offset 3 或者 select * from Account limit 3,5

  • 插入语句:insert into 表名(字段列表) values(值列表)。如: insert into person(name, age) values(‘张三’,3)

  • 更新语句:update 表名 set 字段名=值 where 条件子句。如:update person set name=‘张三‘ where id=10

  • 删除语句:delete from 表名 where 条件子句。如:delete from person where id=10

获取添加记录后自增长的ID值:SELECT last_insert_rowid()


五、谷歌专版的增删改查

在上面的代码中,我们的dao类的里面的增删改查的每一个方法都需要编写你sql语句,sql语句比较麻烦,容易出错,所以google封装了一下sql语句,方便我们调用。

SQLiteDatabase中有几个方法,可用于对数据库进行增删查改的操作。

1、SQLiteDatabase 增,insert()

  • 增 insert()
    insert()方法,这个方法就是专门用于添加数据的。

它接收三个参数,
第一个参数是表名,我们希望向哪张表里添加数据,这里就传入该表的名字。
第二个参数用于在未指定添加数据的情况下给某些可为空的列自动赋值NULL,一般我们用不到这个功能,直接传入null即可。第三个参数是一个ContentValues对象,它提供了一系列的put()方法重载,用于向ContentValues中添加数据,只需要将表中的每个列名以及相应的待添加数据传入即可。

    /**
     * 添加一条记录
     * @param name 联系人姓名
     * @param phone 联系人电话
     * @return 返回的是添加后在数据库的行号  -1代表添加失败
     */
    public long add(String name, String phone){
        SQLiteDatabase db = helper.getWritableDatabase();
        //db.execSQL("insert into contactinfo (name,phone) values (?,?)", new Object[]{name,phone});
        ContentValues values = new ContentValues();
        values.put("name", name);
        values.put("phone", phone);
        //内部是组拼sql语句实现的.
        long rowid = db.insert("contactinfo", null, values);
        //记得释放数据库资源
        db.close();
        return rowid;
    }

2、SQLiteDatabase 删,delete()

  • 删 delete()

delete()方法专门用于删除数据
这个方法接收三个参数,
第一个参数是表名,
第二、第三个参数又是用于去约束删除某一行或某几行的数据,不指定的话默认就是删除所有行。


/**
     * 根据姓名删除一条记录
     * @param name 要删除的联系人的姓名
     * @return 返回0代表的是没有删除任何的记录 返回整数int值代表删除了几条数据
     */
    public int delete(String name){
        //判断这个数据是否存在.
        SQLiteDatabase db = helper.getWritableDatabase();
        //db.execSQL("delete from contactinfo where name=?", new Object[]{name});
        int rowcount = db.delete("contactinfo", "name=?", new String[]{name});
        db.close();
        //再从数据库里面查询一遍,看name是否还在
        return rowcount;
    }

2、SQLiteDatabase 改,update()

  • 改 update()方法用于对数据进行更新。
    这个方法接收四个参数,
    第一个参数是表名,在这里指定去更新哪张表里的数据。
    第二个参数是ContentValues对象,要把更新数据在这里组装进去。
    第三、第四个参数用于去约束更新某一行或某几行中的数据,不指定的话默认就是更新所有行。

/**
     * 修改联系人电话号码
     * @param newphone 新的电话号码
     * @param name 要修改的联系人姓名
     * @return 0代表一行也没有更新成功, >0 整数代表的是更新了多少行记录
     */
    public int update(String newphone , String name){
        SQLiteDatabase db = helper.getWritableDatabase();
        //db.execSQL("update contactinfo set phone =? where name=?", new Object[]{newphone,name});
        ContentValues values = new ContentValues();
        values.put("phone", newphone);
        int rowcount =  db.update("contactinfo", values, "name=?", new String[]{name});
        db.close();
        return rowcount;
    }

4、SQLiteDatabase 查, query()

  • 查询 query()

查询是数据库的重头戏,SQL的全称是Structured Query Language,翻译成中文就是结构化查询语言。它的大部功能都是体现在“查”这个字上的,而“增删改”只是其中的一小部分功能。

SQLiteDatabase中还提供了一个query()方法用于对数据进行查询。这个方法的参数非常复杂,最短的一个方法重载也需要传入七个参数。

query()方法参数 对应SQL部分 描述
table from table_name 指定查询的表名
columns select column1, column2 指定查询的列名
selection where column = value 指定where的约束条件
selectionArgs - 为where中的占位符提供具体的值
groupBy group by column 指定需要group by的列
having having column = value 对group by后的结果进一步约束
orderBy order by column1, column2 指定查询结果的排序方式
/**
     * 查询联系人的电话号码
     * @param name 要查询的联系人
     * @return 电话号码
     */
    public String getPhoneNumber(String name){
        String phone = null;
        SQLiteDatabase db = helper.getReadableDatabase();
        //Cursor  cursor = db.rawQuery("select phone from contactinfo where name=?", new String[]{name});
        Cursor  cursor =  db.query("contactinfo", new String[]{"phone"}, "name=?", new String[]{name}, null, null, null);
        if(cursor.moveToNext()){//如果光标可以移动到下一位,代表就是查询到了数据
            phone = cursor.getString(0);
        }
        cursor.close();//关闭掉游标,释放资源
        db.close();//关闭数据库,释放资源
        return phone;
    }

5 完整的代码示例

下面贴入一个使用google的SQLiteDatabase提供的insert等四个方法进行增删查改的dao类(上面的布局文件,打开帮助类不变)的完整代码:

package com.amqr.test.googledbdao.dao;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;

import com.amqr.test.googledbdao.db.MyDBHelper;


/**
* 联系人数据库表的访问类
*/
public class ContactInfoDao {
   /**
    * 数据库打开的帮助类
    */
   private MyDBHelper helper;

   /**
    * 在构造方法里面完成 必须要用的类的初始化
    * @param context
    */
   public ContactInfoDao(Context context) {
       helper = new MyDBHelper(context);
   }

   /**
    * 添加一条记录
    * @param name 联系人姓名
    * @param phone 联系人电话
    * @return 返回的是添加后在数据库的行号  -1代表添加失败
    */
   public long add(String name, String phone){
       SQLiteDatabase db = helper.getWritableDatabase();
       //db.execSQL("insert into contactinfo (name,phone) values (?,?)", new Object[]{name,phone});
       ContentValues values = new ContentValues();
       values.put("name", name);
       values.put("phone", phone);
       //内部是组拼sql语句实现的.
       long rowid = db.insert("contactinfo", null, values);
       //记得释放数据库资源
       db.close();
       return rowid;
   }
   /**
    * 根据姓名删除一条记录
    * @param name 要删除的联系人的姓名
    * @return 返回0代表的是没有删除任何的记录 返回整数int值代表删除了几条数据
    */
   public int delete(String name){
       //判断这个数据是否存在.
       SQLiteDatabase db = helper.getWritableDatabase();
       //db.execSQL("delete from contactinfo where name=?", new Object[]{name});
       int rowcount = db.delete("contactinfo", "name=?", new String[]{name});
       db.close();
       //再从数据库里面查询一遍,看name是否还在
       return rowcount;
   }
   /**
    * 修改联系人电话号码
    * @param newphone 新的电话号码
    * @param name 要修改的联系人姓名
    * @return 0代表一行也没有更新成功, >0 整数代表的是更新了多少行记录
    */
   public int update(String newphone , String name){
       SQLiteDatabase db = helper.getWritableDatabase();
       //db.execSQL("update contactinfo set phone =? where name=?", new Object[]{newphone,name});
       ContentValues values = new ContentValues();
       values.put("phone", newphone);
       int rowcount =  db.update("contactinfo", values, "name=?", new String[]{name});
       db.close();
       return rowcount;
   }
   /**
    * 查询联系人的电话号码
    * @param name 要查询的联系人
    * @return 电话号码
    */
   public String getPhoneNumber(String name){
       String phone = null;
       SQLiteDatabase db = helper.getReadableDatabase();
       //Cursor  cursor = db.rawQuery("select phone from contactinfo where name=?", new String[]{name});
       Cursor  cursor =  db.query("contactinfo", new String[]{"phone"}, "name=?", new String[]{name}, null, null, null);
       if(cursor.moveToNext()){//如果光标可以移动到下一位,代表就是查询到了数据
           phone = cursor.getString(0);
       }
       cursor.close();//关闭掉游标,释放资源
       db.close();//关闭数据库,释放资源
       return phone;
   }
}


.

6、源码下载

源码分成了两个module,一个是原生版的,一个是android提供的api版本的。

下载源码(Android Studio格式)

以下是两个module的说明图片


compare.png

六、数据库的事务

  • 数据库的事务有什么作用? —— 保证某些操作要么全部成功,要么都不成功。
  • 数据库是什么时候用? 比如金额交易,数据的批量删除和存储等。比如现在要进行银行转账,不可能扣了A的钱,但是B没有收到,然后A的钱就不知道哪里去了,再比如,现在要备份短信,要么成功备份到磁盘,要么备份失败,而不可能在没有备份成功的情况就把本地给删除了。
  • 数据库的事务怎么用?下面展示一份例子:
        btn.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                SQLiteDatabase db = dbHelper.getWritableDatabase();
                db.beginTransaction(); // 开启事务
                try {
                    // 执行删除操作  db.delete(..........)
                    if (true) {
                        // 在这里手动抛出一个异常,让事务失败
                        throw new NullPointerException();
                    }
                    // 执行添加操作
                    db.setTransactionSuccessful(); // 事务已经执行成功
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    db.endTransaction(); // 结束事务
                }
            }
        });

1、调用SQLiteDatabase的 beginTransaction() 方法来开启一个事务,然后在一个异常捕获的代码块中去执行具体的数据库操作,
2、当所有的操作都完成之后,调用 setTransactionSuccessful() 表示事务已经执行成功了
3、最后在finally代码块中调用 endTransaction() 来结束事务。

本篇完。

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

推荐阅读更多精彩内容