Java基础

JDK下载

http://java.sun.com/products/archive/


HashMap

hash & (n - 1) -> 效果是跟hash对n取模是一样的,但是运算的性能要高很多;条件:只要保证数组长度是2的n次方

hash冲突问题,链表+红黑树;优化,如果链表长度达到一定值后,会把链表转换为红黑树,遍历一遍红黑树找一个元素,此时O(logn),性能会比链表高一些

ConcurrentHashMap

在JDK1.7及之前的版本里,分段加锁,每个数组都对应一个锁,比如线程1要put的位置是数组1[5],线程2要put的位置是数组2[21]

在JDK1.8及之后,做了优化和改进,锁粒度的细化,一个大的数组,数组里每个元素进行put操作,都是有一个不同的锁,刚开始进行put的时候,如果两个线程都是在数组[5]位置进行put的时候,采取的是CAS策略,同一个时间,只有一个线程能成功执行CAS。

分段加锁,通过对数组每个元素执行CAS策略,如果是很多线程对数组里的不同元素执行put,大家是没有关系的;如果线程失败了,说明这个位置已经被另外的线程放入值了,就需要在这个位置基于链表+红黑树进行处理,synchronized(数组[a]),加锁,基于链表或者红黑树在这个位置插入数据。


synchronized

加锁执行指令monitorenter,释放锁执行指令monitorexit; 每个对象都有一个关联的monitor,比如一个对象实例就有一个monitor,一个类的Class对象也有一个monitor,如果要对这个对象加锁,那么必须获取这个对象关联的monitor的lock锁; monitor里面有一个计数器,从0开始的。 如果一个线程要获取monitor的锁,就看看他的计数器是不是0,如果是0的话,那么说明没人获取锁,他就可以获取锁了,然后对计数器加1; 如果一个线程第一次synchronized那里,获取到了myObject对象的monitor的锁,计数器加1,然后第二次synchronized那里,会再次获取myObject对象的monitor的锁,这个就是重入锁了,然后计数器会再次加1,变成2; 这个时候,其他的线程在第一次synchronized那里,会发现myObject对象的monitor锁的计数器是大于0的,意味着被别人加锁了,然后此时线程就会进入block阻塞状态,什么都干不了,就是等着获取锁;接着如果出了synchronized修饰的代码片段的范围,就会有一个monitorexit的指令,在底层,此时获取锁的线程就会对那个对象的monitor的计数器减1,如果有多次重入加锁就会对应多次减1,直到最后,计数器是0;然后后面block阻塞的线程,会再次尝试获取锁,但是只有一个线程可以获取到锁。

(1)原子性:加锁和释放锁,ObjctMmonitor;(2)可见性:加了Load屏障和Store屏障,释放锁flush数据,加锁会refresh数据;(3)有序性:Acquire屏障和Release屏障,保证同步代码块内部的指令可以重排,但是同步代码快内部指令和外面的指令是不能重排的

synchronized保障原子性底层原理

AQS

多线程同时访问一个共享数据,sychronized,CAS,ConcurrentHashMap,Lock

Abstract Queue Synchronizer 抽象队列同步器

只有一个线程可以加锁,其他线程进入等待队列

AQS原理

CAS

compare and set

底层MESI协议


线程池

避免频繁的创建和销毁线程带来的开销

代表线程池的类是:ThreadPoolExecutor,创建一个线程池就是这样的,corePoolSize,maximumPoolSize,keepAliveTime,queue。

1.如果queue为有界队列比如new ArrayBlockingQueue<Runnable>(200),那么假设corePoolSize个线程都在工作,大量任务进入有界队列,队列满了,此时怎么办?

==》  会创建额外线程,最多maximumPoolSize个线程,当队列的任务被处理完毕,额外的线程会空闲keepAliveTime的一段时间后被销毁。

2.如果额外线程都创建完了去处理任务,队列还是满的,此时有新的任务来怎么办?

==》  能reject,有几种reject策略,可以传入RejectedExecutionHandler(AbortPolicy,DiscardPolicy,DiscardOldestPolicy,CallerRunsPolicy,自定义)DiscardPolicy:用于被拒绝任务的处理程序,默认情况下它将丢弃被拒绝的任务。CallerRunsPolicy:用于被拒绝任务的处理程序,它直接在 execute 方法的调用线程中运行被拒绝的任务;如果执行程序已关闭,则会丢弃该任务。    自定义reject策略,如果线程池无法执行更多的任务了,此时建议把这个任务信息持久化写入磁盘里去,后台专门启动一个线程,后续等待线程池中的工作负载降低了,可以慢慢从磁盘里读取之前持久化的任务,重新提交到线程池里执行。

3.如果在线程池中使用无界阻塞队列会发生什么问题?

==》  调用超时,队列变得越来越大,此时会导致内存飙升,可能会导致内存溢出OOM

4.如果线程池的队列满了会发生什么?

==》  可以无限制的创建额外的线程出来,一台机器上,有几千个线程,甚至几万个线程,每个线程都有自己的栈内存,占用一定的内存资源,会导致内存资源耗尽,系统也会崩溃掉;即使内存没有崩溃,也会导致机器的cpu负载特别高。

5.如果线上机器突然宕机,线程池的阻塞队列中的请求怎么办?

==》 必然会导致线程池里的积压的任务都丢失;解决:在提交一个任务到线程池之前,先在数据库里插入这个任务的信息,更新他的状态:未提交、已提交、已完成。提交成功后,更新他的状态是已提交状态。系统重启,后台线程去扫描数据库里的未提交和已提交状态的任务,可以把任务的信息读取出来,重新提交到线程池里去,继续进行执行。


Java内存模型

1.read、load、use、assign、store、write

2.内存模型中的原子性、有序性、可见性是什么?

==》 原子性:data++,必须是独立执行的,没有线程可以影响的,一定是线程执行成功之后,其他线程才能进行下一次data++的执行。    有序性:对于代码,有一个指令重排的问题,编译器和指令器,有时候为了提高代码执行效率,会将指令重排序。

3.volatile关键字的原理?

==》  volatile关键字是用来解决可见性和有序性,在有些罕见的条件下,可以有限的保证原子性。

4.指令重排以及happens-before原则?

==》  happens-before原则:编译器、指令器可能对代码重排序要守一定的规则,只要符合happens-before的原则,那就不能重排,如果不符合这些规则就可以自己排序。这些规则有(1)程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作。(2)一个unlock操作发生于后面对同一个锁的lock操作。(3)volatile变量规则:对一个volatile变量的写操作先行发生于后面对这个volatile变量的读操作。(4)传递规则:如果操作A先行发生于操作B,而操作B又先发生于操作C,则可以得出操作A先行发生于操作C。(5)线程启动规则:Thread对象的start() 方法先行发生于此线程的每一个动作,thread.start()。(6)线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断时间的发生。(7)线程终结规则:线程中所有的操作都先行发生于线程的终止检测。(8)对象中介规则:一个对象的初始化完成先行发生于他的finalize()方法的开始。

5.volatile底层是如何基于内存屏障保证可见性和有序性的?

==》  (1)lock指令:volatile保证可见性,对volatile修饰的变量,执行写操作的话,JVM会发送一条lock前缀指令给cpu,cpu在计算完之后会立即将这个值写回主内存,同时因为有MESI缓存一致性协议,所以各个线程都会对总线进行嗅探,自己本地缓存中的数据是否被别人修改。(2)内存屏障:volatile禁止指令重排序


Spring

1.Spring的IOC机制的理解?

==》  tomcat在启动的时候会启动spring容器,springIOC,spring容器,根据xml配置,或者注解,实例化bean对象,然后根据xml配置或者注解,去对bean对象之间的引用关系,进行依赖注入,某个bean依赖了另一个bean。底层的核心技术:会通过反射的技术,根据你的类自己构建对应的对象出来,用的就是反射技术。springIOC,系统的类与类之间彻底的解耦和。

spring IOC

2.Spring的AOP机制的理解?

==》  将相同逻辑的重复代码横向抽取出来,使用动态代理技术将这些重复代码织入到目标对象方法中,实现和原来一样的功能。

spring核心框架里面,最关键的两个机制就是ioc和aop,根据xml配置或注解,去实例化所有的bean,管理bean之间解耦,维护代码的时候可以更加轻松便利。代码里有重复的代码,可以使用aop机制,做一个切面,如何定义?MyServiceXXXX的这种类,在这些类的所有方法中,都织入一些代码,在所有这些方法刚开始运行的时候,都先去开启一个事务,在所有这些方法运行完毕之后,去根据是否抛出异常来判断一下,如果抛出异常,就回滚事务,如果没有异常,就提交事务。

spring在运行的时候,动态代理技术,AOP的核心技术,就是动态代理

  事务,mysql,数据库里斗提供一个事务机制,如果开启一个事务,在这个事务里执行多条增删改的sql语句,这个过程中,如果任何一条sql语句失败了,会导致这个事务的回滚,把其他sql做的数据更改都恢复回去。在一个事务里的所有sql,要么一起成功,要么一起失败,事务功能可以保证我们的数据一致性,在业务逻辑组件去加入事务。

3.了解cglib动态代理吗?它合jdk动态代理的区别是什么?

==》  就是动态的创建一个代理类出来,创建这个代理类的实例对象,在这个里面引用真正自己写的类,所有方法的调用都是先走代理类的对象,它负责做代码上的增强,再去调用自己写的类

spring里使用AOP,比如对一批类和他们的方法做了一个切面,定义好了要在这些类的方法里增强代码,spring必然要对那些类生成动态代理,在动态代理中去执行你定义的一些增强代码。

如果你的类是实现了某个接口,spring aop会使用jdk动态代理,生成一个跟你实现同样接口的代理类,构造一个实例对象出来,jdk动态代理是在你的类有接口的时候来使用的。

如果某个类没有实现接口,spring aop会改用cglib来生成动态代理,它是生成你的类的一个子类,它可以动态生成字节码,覆盖你的方法,在方法利加入增强代码。

4.spring中的bean是线程安全的吗?

==》 Spring容器中的bean可以分为5个范围:(1)singleton:默认,每个容器中只有一个bean的实例(2)prototype:为每一个bean请求提供一个实例  [一般来说下面几种作用域,在开发的时候一般都不会用,99.99%的时候都是用singleton单例作用域](3)request:为每一个网络请求创建一个实例,在请求完成以后,bean会失效并被垃圾回收器回收(4)session:与request范围类似,确保每个session中有一个bean的实例,在session过期后,bean会随之失效(5)global-session

答案是否定的,绝对不可能是线程安全的,spring bean默认来说,singleton,都是线程不安全的,java web系统,一般来说很少在spring bean里放一些实例变量,一般来说他们都是多个组件互相调用,最终去访问数据库的

5.spring的事务实现原理是什么?事务的传播机制是什么?

==》事务的实现原理,事务传播机制,如果加上@Transactional注解,此时spring使用AOP思想,对这个方法执行之前,先开启事务,执行完毕之后,根据方法是否报错来决定回滚还是提交事务。

①PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。

② PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。

③ PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常

④ PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务。

⑤ PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

⑥ PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。

⑦ PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUIRED属性执行。

6.springboot的核心架构?

==》 spring boot内嵌一个tomcat去直接让我们一下子就可以把写好的java web系统给启动起来,直接运行一个main方法,spring boot就直接把tomcat服务器给跑起来,把我们的代码运行起来了

自动装配,比如说我们可以引入mybatis,其实主要引入一个starter依赖,他会一定程度上自动完成mybatis的一些配置和定义,不需要我们手工去做大量的配置了,一定程度上简化我们搭建一个工程的成本

引入一些mybatis的jar包,还有mybatis依赖的一些其他的jar包,然后动手编写一些xml配置文件,然后定义一些bean,写一些sql语句,写一些dao代码,此时就可以使用mybatis去执行sql语句了

只要引入一个starter,他会自动给你引入需要的一些jar包,做非常简单的、必须的一些配置,比如数据库的地址,几乎就不用你做太多的其他额外的配置了,他会自动帮你去进行一些配置,定义和生成对应的bean

生成的bean自动注入比如你的dao里去,让你免去一些手工配置+定义bean的一些工作

spring boot + spring + spirng mvc + mybatis + XXX之类的技术去进行开发,后续很多配置和定义的一些繁琐的重复性的工作就免去了,自动装配的一些功能,自动给你把一些事情干完了,不需要你去做了

spring boot

7.Spring的核心架构?

==》spring bean生命周期,从创建 -› 使用 -› 销毁

你在系统里用xml或者注解,定义一大堆的bean

(1)实例化Bean:如果要使用一个bean的话

(2)设置对象属性(依赖注入):他需要去看看,你的这个bean依赖了谁,把你依赖的bean也创建出来,给你进行一个注入,比如说通过构造函数,setter

(3)处理Aware接口:如果这个Bean已经实现了ApplicationContextAware接口,spring容器就会调用我们的bean的setApplicationContext(ApplicationContext)方法,传入Spring上下文,把spring容器给传递给这个bean

(4)BeanPostProcessor:如果我们想在bean实例构建好了之后,此时,我们想要对Bean进行一些自定义的处理,那么可以让Bean实现了BeanPostProcessor接口,那将会调用postProcessBeforeInitialization(Object obj, String s)方法。

(5)InitializingBean 与 init-method:如果Bean在Spring配置文件中配置了 init-method 属性,则会自动调用其配置的初始化方法。

(6)如果这个Bean实现了BeanPostProcessor接口,将会调用postProcessAfterInitialization(Object obj, String s)方法

(7)DisposableBean:当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean这个接口,会调用其实现的destroy()方法;

(8)destroy-method:最后,如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法。

8.Spring中都使用了哪些设计模式?

==》工厂,单例,代理。工厂模式,spring ioc核心的设计模式的思想提现,他自己就是一个大的工厂,把所有的bean实例都给放在了spring容器里(大工厂),如果你要使用bean,就找spring容器就可以了,你自己不用创建对象了。spring默认来说,对每个bean走的都是一个单例模式,确保说你的一个类在系统运行期间只有一个实例对象,只有一个bean,用到了一个单例模式的思想,保证了每个bean都是单例的。代理模式,如果要对一些类的方法切入一些增强的代码,会创建一些动态代理的对象,让你对那些目标对象的访问,先经过动态代理对象,动态代理对象先做一些增强的代码,调用你的目标对象

9.spring MVC核心架构?

==》 

(1)tomcat的工作线程将请求转交给spring mvc框架的DispatcherServlet

(2)DispatcherServlet查找@Controller注解的controller,我们一般会给controller加上@RequestMapping的注解,标注哪些controller用来处理哪些请求,此时根据请求的uri,去定位到哪个controller来进行处理

(3)根据@RequestMapping去查找,使用这个controller内的哪个方法来进行请求的处理,对每个方法一般也会加@RequestMapping的注解

(4)他会直接调用我们的controller里面的某个方法来进行请求的处理

(5)我们的controller的方法会有一个返回值,以前的时候,一般来说还是走jsp、模板技术,我们会把前端页面放在后端的工程里面,返回一个页面模板的名字,spring mvc的框架使用模板技术,对html页面做一个渲染;返回一个json串,前后端分离,可能前端发送一个请求过来,我们只要返回json数据

(6)再把渲染以后的html页面返回给浏览器去进行显示;前端负责把html页面渲染给浏览器就可以了

10.spring cloud核心架构?

==》


架构图

spring boot、spring、spring mvc、spring cloud,让你开发那种单体架构的系统,spring cloud是让你去开发分布式系统,让你把系统拆分为很多的子系统,子系统互相之间进行请求和调用。  eureka、ribbon、feign、zuul、hystrix、链路追踪、其他组件,服务于分布式系统的,hystrix主要用于服务之间调用的熔断、隔离、降级


安全相关

1.平时我们开发的系统,有可能被黑客以哪些方式来攻击?

==》

2.XSS攻击方式背后的原理是什么?SQL注入背后的原理是什么?

==》

3. 针对常见的黑客攻击方式,你平时开发系统的时候都有哪些方案可以去保护你的系统安全,避免被黑客攻破

==》

4. 平时你们微服务架构里,网关系统用的是什么?在网关层面如何防止黑客攻击?

==》

5. 哪怕网关不是你负责的,你负责的一些系统的接口,如何保证你设计的接口的安全性呢?

==》

6. 缓存穿透,假如说有黑客攻击你,每次使用的缓存的Key是不同的,传统的缓存穿透的方案无法防御,此时怎么办呢?

==》

7. 加密算法,公钥密钥是怎么回事,如何进行加密的网络通信?数据加密?

==》

8. 除了公钥密钥以外,你们有没有完整的一套系统安全性防御机制呢,防火墙,网站安全漏洞扫描,密钥存储是如何做的

==》


HTTPS

https的工作原理大概是这样的:

(1)浏览器把自己支持的加密规则发送给网站

(2)网站从这套加密规则里选出来一套加密算法和hash算法,然后把自己的身份信息用证书的方式发回给浏览器,证书里有网站地址、加密公钥、证书颁发机构

(3)浏览器验证证书的合法性,然后浏览器地址栏上会出现一把小锁;浏览器接着生成一串随机数密码,然后用证书里的公钥进行加密,这块走的非对称加密;用约定好的hash算法生成握手消息的hash值,然后用密码对消息进行加密,然后把所有东西都发给网站,这块走的是对称加密

(4)网站,从消息里面可以取出来公钥加密后的随机密码,用本地的私钥对消息解密取出来密码,然后用密码解密浏览器发来的握手消息,计算消息的hash值,并验证与浏览器发送过来的hash值是否一致,最后用密码加密一段握手消息,发给浏览器

(5)浏览器解密握手消息,然后计算消息的hash值,如果跟网站发来的hash一样,握手就结束,之后所有的数据都会由之前浏览器生成的随机密码,然后用对称加密来进行进行加密。

https原理

MySQL

1.存储引擎 MyISAM和InnoDB

===》myisam不支持事务,不支持外键约束,索引文件和数据文件分开,这样在内存里可以缓存更多的索引,对查询的性能更好,使用于少量插入,大量查询的场景。innodb支持事务,强制要求有主键,支持外键约束,高并发、大数据量、高可用等。

2.MySQL索引原理

==》B+树。innodb存储引擎,要求必须有主键,并建立索引,叫聚簇索引,innodb的数据文件本身就是索引文件。  索引最左前缀匹配原则

3.事务的几个特性,有哪几种隔离级别?

==》事务的ACID,atomic原子性,consistency一致性,isolation隔离性,durablity持久性

隔离级别:(1)读未提交(2)读已提交(不可重复读)(3)可重复读:根据事务ID读数据(4)串行化:事务A在执行期间不让事务B执行

幻读:针对的是读出来的行数,有增减,称为幻读。

可重复读实现机制:MVCC机制,类比git的commitID

4.分库分表

==》

数据库中间件:独立部署和 client两种;独立部署:cobar  atlas(360)  mycat;client:TDDL(淘宝)  sharding-jdbc(当当)  

数据库如何拆分:垂直拆分和水平拆分;垂直拆分:把一个表的 字段拆分成多个数据量相同的表;水平拆分:表结构 相同,数据不同的多个表。


Java程序运行过程中发生指令重排的几个地方

指令重排的几个地方

(1)自己写的源代码中的执行顺序:这个是我们自己写的代码,一般来说就是按照我们自己脑子里想的样子来写

(2)编译后的代码的执行顺序:java里有两种编译器,一个是静态编译器(javac),一个是动态编译器(JIT)。javac负责把.java文件中的源代码编译为.class文件中的字节码,这个一般是程序写好之后进行编译的。JIT负责把.class文件中的字节码编译为JVM所在操作系统支持的机器码,一般在程序运行过程中进行编译。在这个编译的过程中,编译器是很有可能调整代码的执行顺序的,为了提高代码的执行效率,很可能会调整代码的执行顺序,JIT编译器对指令重排的还是挺多的

(3)处理器的执行顺序:哪怕你给处理器一个代码的执行顺序,但是处理器还是可能会重排代码,更换一种执行顺序,JIT编译好的指令的时候,还是可能会调整顺序

(4)内存重排序:有可能你这个处理器在实际执行指令的过程中,在高速缓存和写缓冲器、无效队列等等,硬件层面的组件,也可能会导致你的指令的执行看起来的顺序跟想象的不太一样

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

推荐阅读更多精彩内容