Java面试题总结

JDK、JRE、JVM的区别

  • JDK,是Java开发工具包,用于开发Java程序,提供了编译和运行Java程序的各种工具和资源、类库等
  • JRE,Java程序的运行环境,用于解释执行Java的字节码文件,想要运行Java程序必须要安装JRE
  • JVM,Java虚拟机,Java程序跨平台的核心,负责解析和执行字节码文件
  • JDK包含JRE,JRE包含JVM

重写和重载的区别

  • 重载,发生在同一个类中,方法名相同,参数列表不一致,与返回值无关
  • 重写,发生在父子类中,方法名和参数列表必须一致,返回值和抛出的异常要小于等于父类,访问修饰符要大于等于父类,父类中的private方法不能被子类重写

Java中的==和equals的区别

  • ==
    • 基本数据类型,比较值是否相等
    • 引用数据类型,比较的地址是否相等
  • equals
    • 未重写前,比较的是地址值
    • 重写后,按照重写的逻辑进行比较

String、StringBuffer、StringBuilder的区别

  • String代表字符串,是一个final修饰的不可变类,一旦创建就不能修改。

  • StringBuilder是内容可变的字符串容器,可以进行字符串动态增删,它可以提高字符串操作的效率。如果是多线程开发,会有线程安全问题。

  • StringBuffer是内容可变的字符串容器,可以进行字符串动态增删,可以提高字符串操作的效率。它是线程安全的类,实现线程安全的方式是所有方法都加上synchronized,效率低,不推荐使用。

什么是单例模式,有实现几种?

程序运行中,同一个类的的实例只有一个,就是单例模式。

  • 懒汉式
  • 饿汉式
  • 饿汉式 + 锁
  • 饿汉式 + 双重锁
  • 静态内部类实现单例
  • 枚举实现单例
  • 静态Map工厂实现单例

接口和抽象类的区别?

  • 抽象类,需要被类继承。接口需要被类实现
  • 接口中的变量只能是公共的静态常量,而抽象类中的变量则可以是普通变量
  • 接口可以继承接口,可以多继承,而抽象类只能单继承

List和Map、Set的区别?

  • List和Set都是单列集合,Map是双列集合
  • List存储的元素是有序的,并且允许重复
  • Set存储的元素是无序的,并且不允许重复,HashSet依靠对象的hashCode和equals方法来确定元素位置,而TreeSet依靠元素实现Comparable接口或Compartor比较器确定位置
  • Map存储的元素是无序的,键是唯一的,不允许重复,而值是允许重复的

创建线程的方式有?

  • 继承Thread类,重写run方法
  • 实现Runnable接口,重写run方法。这种方式没有返回值,不允许抛出编译时异常
  • 实现Callable接口,重写call方法。这种方式有返回值,允许抛出编译时异常
  • 使用线程池创建线程

Ruanable和Callable的区别?

  • Runnable接口的run方法没有返回值,Callable接口的call方法有返回值,并且支持泛型
  • Runnable接口的run方法不能抛出编译时异常,必须捕获处理。而Callable接口的call方法允许抛出编译时异常

如何启动一个线程?调用start()和run()有什么区别?

  • 直接调用run()方法,只是在当前线程调用了对象的普通方法,并不是开启线程,run()方法运行在调用方的线程中
  • 调用start()方法,则是让jvm开启一个线程,然后在新开启的线程中调用run()方法,run()方法运行在新线程中

线程有哪几种状态?状态之间是怎么流转的?

  • NEW:新建

  • RUNABLE:可运行(就绪)

  • TERMINATED:结束

  • BLOCK:阻塞

  • WATING:无限等待

  • TIME_WATING:定时等待

  • 线程Thread类对象被new后,就是新建状态

  • 线程对象调用start()后就流转为可运行状态,等待CPU调度

  • 线程的run()方法执行完毕,线程就会流转为结束状态,然后线程被销毁

  • 线程执行到同步代码块时

    • 如果没有抢到锁,则进入阻塞状态
    • 如果抢到锁,则可以继续执行
  • 在阻塞状态时,如果重新抢到锁,则回归到可运行状态

  • 可运行状态时,如果调用了wait()方法,则进入无限等待状态,如果调用了sleep(timeount)或wait(timeout)则进入定时等待状态

  • 当另外一个线程调用了notify()或notifyAll(),并且重新抢到锁后,则回归到可运行状态

  • 如果是定时等待状态,重新抢到了锁,则自动回归到可运行状态

wait()和sleep()的区别?

  • 来自不同的类

    • wait()来自Object,sleep()来自Thread
  • 关于锁的释放

    • wait()在等待过程中会释放锁
    • sleep()在等待过程中不会释放锁
  • 关于使用范围

    • wait()必须在同步代码块中调用
    • sleep()可以在任何地方调用
  • 是否需要捕获异常

    • wait()不需要捕获异常
    • sleep()需要捕获异常

常用线程池种类

  • newCacheThreadPool,创建一个可进行缓存,可重用的线程池
  • newFixThreadTool,创建一个固定线程的,可重用的线程池
  • newSingleThreadExecutor,创建一个单工作线程的Executor,它使用无界队列,该线程池最多执行一个线程
  • newSingleThreadScheduledExcutor,创建一个单工作线程的Executor,它可以延时执行任务或定期执行任务
  • newScheduledThreadPool,创建一个线程池,它可延时执行任务或定期执行任务
  • newWorkStealingPool,创建一个并行级别的线程池,并行级别决定了同一时刻最多有多少个线程在执行,如果不传并行级别参数,那么默认为当前系统的CPU核心数 * 2

线程池创建时的参数作用?以及执行流程

  • corePoolSize:核心线程数
  • maxPoolSize:最大线程数
  • keepAliveTime:空闲线程的空闲时间
  • unit:空闲时间的时间单位
  • workQueue:工作队列,保存任务的阻塞队列
  • threadFactory:线程工厂
  • handler:饱和策略(也叫拒绝策略)

ArrayList和LinkedList的区别

  • ArrayList和LinkedList都实现了List接口,都用于存储元素,并提供对元素增删查改的方法。
  • ArrayList底层使用数组实现,它的查找速度很快,并且提供索引进行访问元素的方法,当在尾部插入或删除元素时,耗时时间是一致的。但如果在中间添加或删除元素时,ArrayList需要移动元素,就会比较耗时。
  • LinkedList的底层使用链表实现,它的增删元素只需要改变前后元素的前指针和后指针,所以效率比较高,而查找时需要移动指针,相对效率会比较低。
  • ArrayList的空间浪费体现在它需要在尾部预留一定容量空间。而LinkedList的空间浪费体现在每个元素都要有一个前指针和一个后指针。

数据库的四大特性

  • 原子性:多条指令要么同时成功,要么同时失败
  • 一致性:事务前和事务后,数据是一致的
  • 隔离性:事务提交前,不会影响其他事务
  • 持久性:事务提交后,就会永久存入磁盘

事务的隔离级别

  • 读未提交
  • 读已提交
  • 可重复读
  • 可串行化

MyBatis的#{}${}有什么区别?

  • # 是一个占位符,$是拼接符
  • #{},会将内容替换为?号,使用预编译,使用PreparedStatement来执行SQL,可以防止SQL注入,提高安全性
  • ${},是字符串拼接,会有SQL注入的风险,不能避免注入攻击

MyBatis的resultType和resultMap的区别?

  • 如果数据库表的字段名和实体类的成员变量名一致,就可以使用resultType,MyBatis会自动映射查询结果到实体类中
  • 如果不一致,那么则要使用resultMap,单独配置2者的映射关系

MyBatis常用动态SQL标签有哪些?有什么作用?

  • <if>标签,动态SQL的多条件动态拼接
  • <where>标签,相当于where条件,当有条件时拼接上where关键字,没有条件时则不拼接(相当于不需要使用 where 1 = 1 来保证SQL语法正确),可以去掉多余的andor
  • <foreach>标签,遍历传入的集合或数组,将遍历的每一项拼接为一个字符串(例如在 IN 和 NOT IN 中使用)
  • <sql><include>标签,sql标签可以把SQL的公共片段抽取,例如查询的字段列信息。include标签用于在SQL中引入sql标签的SQL片段,复用SQL片段,减少重复代码
  • <set>标签,相当于set关键字,用于update语句中,能够去掉多余的逗号(例如最后一个条件的逗号)

Get请求和Post请求的区别

  • Get请求不安全,它的请求参数在请求行中,会显示在浏览器的地址栏,用户是可见的,而Post的请求参数在请求体中,不会显示在地址栏中,用户不可见,相对安全
  • Get请求传输数据量小,由于不同的浏览器,限制URL的长度不同,因为限制大小也不同,但一般在18kb以内。而Post请求默认不限制大小,所以可以传输更多的数据
  • Get请求限制数据必须为ASCII字符,而POST请求则支持整个ISO10646字符集
  • Get请求没有请求体,而Post请求有请求体,所以Get请求的效率更高,form表单默认使用Get请求
  • 总结:传输非敏感数据,数据量小,使用Get请求。传输敏感数据,数据量大,使用Post请求

Servlet生命周期

  • Servlet生命周期,就是Web服务器创建Servlet对象到销毁的过程
  • 生命周期方法有:init()初始化方法,service()处理请求方法,destroy销毁方法
  • init()初始化方法,创建Servlet对象时调用,只会调用一次
  • service()处理请求方法,每次请求该Servlet资源都会调用一次
  • destroy()销毁方法,Web服务器关闭或重启时调用,只会调用一次
  • Servlet对象创建的时机,在第一次访问该Servlet资源时创建,它使用单例模式,所以只会创建一次,节省内存
  • 通过配置load-on-startup参数,就可以让Web服务器启动时,就自动创建该Servlet对象,提升用户的访问速度

请求转发和重定向的区别

  • 请求转发只有1次请求,而重定向会有2次请求
  • 请求转发不会改变浏览器地址栏地址,而重定向会改变地址栏地址
  • 请求转发是服务器内部跳转,而重定向是浏览器跳转
  • 请求转发只能跳转当前项目内的资源,重定向可以跳转任何资源,包括外部资源
  • 请求转发可以共享Request请求域内的数据,而重定向不可以

什么是HTTP协议,有什么特点和优缺点?

  • HTTP协议,就是HyperText Transfer Protocol 超文本传输协议,规定了浏览器和服务器之间数据传输的规则
  • 特点:
  • 基于TCP协议,面向连接,安全
  • 基于请求-响应模型,一次请求只有一次响应
    • 请求数据包括,请求行、请求头、请求体
    • 响应数据报错,响应行、响应头、响应体
  • HTTP是无状态的协议,对于事务处理没有记忆能力,每次请求、响应都是独立的
  • 优点:速度快
  • 缺点:多次请求间不能共享数据

Cookie和Session的区别?

  • 存储问题不同
    • Cookie存储在浏览器,Session存储在服务器
  • 存储容量不同
    • 单个Cookie保存的数据只能<=4kb,一个网站一般能保存20~50个Cookie,不同的浏览器也有区别
    • Session没有上限,受限于服务器的内存
  • 存储方式的数据类型不同
    • Cookie只能存储字符串数据
    • Session可以存储任何类型的数据
  • 隐私策略不同
    • Cookie对客户端是可见的,可能会出现篡改Cookie数据进行欺骗,所以它是不安全的
    • Session因为是存储在服务器,不存在敏感信息泄露的风险

什么是Session的钝化和活化?

  • 钝化是指服务器正常关闭后,Tomcat会自动将Session数据写入到磁盘,钝化要求Session保存的数据必须实现Serializable序列化接口
  • 活化是再次启动服务器后,从磁盘中读取文件,加载数据到Session中

什么是Ajax,有什么优势?

  • Ajax,就是Asynchronous JavaScript And XML,异步的JavaScript和XML
  • 由前端实现异步请求服务端接口,进行通信交互
  • 优势:通过异步非阻塞方式进行请求,用户不需要等待,提升用户体验
  • 优化了浏览器和服务器之间的传输,减少了不必要的数据往返,减少了带宽占用,性能好(后端只返回数据,而不需要返回整个网页)

JavaWeb的三大组件及其作用

  • Servlet:用于处理资源的请求和响应
  • Filter:过滤器,用于拦截请求,以及统一操作每个请求的一些通用处理,例如权限控制、统一编码等
  • Listener:监听器,用于监听ServletContext、Request、Session的创建和销毁,以及这3个域对象中数据变化,数据变化时进行额外的业务处理

JSP和Servlet的区别

相同点,JSP编译后,本质就是一个Servlet,由于JVM只能识别Java类,所以需要Web服务器将JSP编译为Servlet,当请求到来时,调用生命周期方法进行处理
不同点,JSP侧重于视图和展现数据,Servlet侧重于逻辑控制和获取数据

Spring的IOC、DI、AOP分别是什么?IOC和DI有什么关系?

  • IOC是控制反转,将对象的创建,交给Spring容器,不再亲自new对象,而是Spring根据我们的配置文件生成对象,当我们需要对象时,再通过IOC容器进行获取
  • DI是依赖注入,是运行过程中,对IOC中的对象的成员属性进行赋值
  • AOP是面向切面编程,是将项目中非业务代码的抽取,进行最大程度的解耦,Spring的AOP使用JDK动态代理,或使用CGLIB进行动态代理
  • IOC和DI的区别,IOC侧重于对象的创建上的解耦,主要是将对象创建交给Spring容器。而DI侧重于对象使用上的解耦,对象需要依赖哪些对象,向IOC容器进获取

Spring的Bean作用域有哪些?每种作用域是怎样的?

  • Singleton,单例,是默认的作用域,IOC容器启动时就会创建该作用域的bean,每个容器只有一个对象
  • prototype,多例,每次向IOC容器获取对象时,都会创建一个新的bean对象
  • request,在web工程中使用,IOC容器会在每次Request请求时,创建bean对象,并设置到request域中,同一个request对象中共享一个bean对象,request结束中,bean就会销毁
  • session,在web工程中使用,IOC容器在每次会话开启时,创建bean对象,并设置到session域中,同一个session会话中共享一个bean对象,会话结束后,bean对象就会销毁
  • global-session,在web工程中使用,在多台web服务器中,所有的session会话共享一个bean实例

Spring的对象默认是单例还是多例?单例bean存在线程安全问题吗?

  • Spring的bean的默认作用域是单例的,可以设置bean对象的scope为prototype则为多例
  • 在多线程的情况下,操作单例bean的成员属性,会有线程安全问题
  • 解决方案是避免在单例bean中定义成员变量,如果无法避免,则需要将成员属性设置到ThreadLocal中

MyBatis编程步骤是怎样的?

  • 导入MyBatis的依赖
  • 编写Mapper接口
  • 编写Mapper映射XML文件
  • 执行MyBatis的操作
    • 创建SqlSessionFactory
    • 通过该工厂类创建SqlSession
    • 通过SqlSession的getMapper,创建Mapper接口的代理类,并执行数据库操作
    • SqlSession提交事务
    • 关闭SqlSession,释放资源

谈谈你对MyBatis的缓存机制的理解

  • MyBatis有2级缓存
    • 一级缓存是SqlSession级别的,默认开启
    • 二级缓存是Mapper级别的,默认不开启,需要手动开启
  • 一级缓存,是SqlSession级别的,一个SqlSession范围内共享该缓存,第一次查询时会将查询结果缓存,第二个查询直接返回缓存。当进行增、删、改、提交事务时,就会清空一级缓存
  • 二级缓存,是Mapper级别的,多个SqlSession都可以共享该缓存,要使用二级缓存,需要做2个步骤
    • 实体类实现Serializable序列化接口
    • Mapper.xml中添加<cache>标签,才能开启二级缓存
  • 当sqlSession执行提交或关闭时,写入缓存
  • 执行增、删、改操作时,清空二级缓存

Spring中@Autowired和@Resource的区别

  • @Autowired是Spring提供的,而@Resource是javax包下的
  • @Autowired默认按类型匹配,而@Resource默认按名称匹配
  • @Qualifier需要和@Autowired一起使用,而@Resource可以单独使用
  • Java9及其以上版本,@Resource已被删除,所以推荐使用@Qualifier@Autowired

Spring的事务传播行为

  • spring事务的传播行为说的是,当多个事务同时存在的时候,Spring如何处理这些事务的行为。备注(方便记忆): propagation传播

  • require必须的/support支持/mandatory 强制托管/requires-new 需要新建/ not -supported不支持/never从不/nested嵌套的

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

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

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

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

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

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

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

Spring的常用注解

  • IOC注解

    • @Component(任何层) @Controller @Service @Repository(dao): 用于实例化对象
    • @Scope : 设置Spring对象的作用域
    • @PostConstruct、@PreDestroy : 用于设置Spring创建对象在对象创建之后和销毁之前要执行的方法
    • @Bean: 表在方法上,用于将方法的返回值对象放入容器
  • DI注解

    • @Value: 简单属性的依赖注入
    • @Autowired: 对象属性的依赖注入
    • @Qualifier: 要和@Autowired联合使用,代表在按照类型匹配的基础上,再按照名称匹配。
    • @Resource 按照类型和属性名称依赖注入 @Resource =@Autowired+@Qualifier
    • @ComponentScan: 组件扫描
  • AOP注解

    • @Before 前置通知,会在运行原有方法前面执行
    • @AfterReturning 后置通知,会在运行原有方法后面执行,前提原有方法不发生异常。
    • @AfterThrowing 异常通知,会在运行原有方法发生异常的时候运行
    • @After 最终通知,会在运行原有方法后运行, 无论原有方法是否发生异常都会运行
    • @Around 环绕通知,一个环绕就可以实现上面4个位置的增强
    • @Aspect 标识当前类为切面类
    • @Pointcut切入点表达式
  • 事务注解

    • @Transactional 此注解可以标在类上,也可以标在方法上,表示当前类中的方法具有事务管理功能。
  • 其他配置

    • @PropertySource: 用于引入其它的properties配置文件
    • @Import: 在一个配置类中导入其它配置类的内容
    • @Configuration: 被此注解标注的类,会被Spring认为是配置类。Spring在启动的时候会自动扫描并加载所有配置类,然后将配置类中bean放入容器

Spring事务的实现方式和实现原理

  • Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,Spring是无法提供事务功能的。
  • Spring事务会调用数据库设置手动控制事务 set autocommit = 0,之后通过commit提交和ro11back回滚,数据库底层是通过binlog或者redolog实现的。
  • Spring事务实现主要有两种方法
    • 编程式(编码控制事务),使用Spring框架提供的事务管理器模板TransactionTemplate相关的方法实现事务控制,会造成代码重复几余。
    • 声明式,利用注解@Transactiona 或者 aop 配置

Spring中@Autowired和@Resource的区别

  • @Autowired是Spring框架的,默认按照byType自动装配,@Resource是javax包下的和jdk8及以下版本存在,默认byName自动装配
  • @Autowired@Qualifier一起用可以自定义别名注入,@Resource可以单独使用

Spring的Bean生命周期

  • 简单版

    • Spring的Bean生命周期,就是Bean的创建到销毁的过程
    • 初始化容器阶段
      • 创建Bean对象(内存分配),执行构造方法
      • 执行属性注入(set方法、依赖注入)
      • 执行Bean的初始化方法
    • 使用Bean
      • 执行业务操作
    • 关闭容器
      • 执行Bean销毁方法
  • 复杂版

    • Spring启动,查找并加载需要被Spring管理的bean,进行Bean的实例化
    • Bean实例化后对将Bean的依赖和值注入到Bean的属性中
    • 如果Bean实现了BeanNameAware接口的话,Spring将Bean的id传递给setBeanName()方法
    • 如里Bean实现了BeanFactoryAware接口的话,Spring将调用setBeanFactroy()方法,将BeanFactroy容器实例传入
    • 如果Bean实现了ApplicationContextAware接口的话,Spring将调用Bean的setApplicationContext()方法,将Bean所在应用上下文引用传入进来
    • 如果Bean实现了BeanPostProcessor接口,Spring就将调用他们的postProcessBeforelnitialization()方法
    • 如果Bean实现了InitializingBean接口,Spring将会调用他们的afterPropertiesSet方法。类似的,如果Bean使用init-method声明了初始化方法,该方法也会被调用
    • 如果Bean实现了BeanPostProcessor接口,Spring就将调用他们的postProcessAfterlnitialization()万法
    • 此时,Bean已经准备就绪,可以被应用程序使用了。他们将一直驻留在应用上下文中,直到应用上下文被销毁
    • 如果Bean实现了DisposableBean接口,Spring将调用它的destory()接口方法,同样,如果bean使用了destoy-method声明销毁方法,该方法也会被调用

SpringMVC中拦截器的使用步骤?

  • 拦截器,可以拦截器的方法,可以做一些通用增强的功能
  • 定义拦截器类
  • SpringMVC为我们提供了拦截器规范的接口,创建一个类实现HandTerInterceptor,重写接口中的抽象方法
  • preHandle方法:在调用处理器之前调用该方法,如果该方法返回true则请求继续向下进行,否则请求不会继续向下进行,控制器也不会调用
  • afterCompletion方法:在前端控制器渲染页面,完成之后调用此方法

SpringMVC的有哪些主要组件

  • 前端控制器Dispatcherservlet:接收请求、响应结果,相当于转发器,有了Dispatcherservlet就减少了其它组件之间的耦合度
  • 处理器映射器HandlerMapping:根据请求的URL来查找Handler
  • 处理器适配器HandlerAdapter:负责执行Handler
  • 处理器Handler:处理业务逻辑的Java类(Contro1ler类)
  • 视图解析器viewResolver:进行视图的解析,根据视图逻辑名将ModeTAndview解析成真正的视图 (view) 并跳转到视图页面

SpringMVC和SpringBoot的关系

  • SpringMVC,提供了一种轻度耦合的方式来进行Web开发,它是Spring的一个模块,是一个Web层的框架
  • SpringBoot,实现了自动配置,降低了Spring项目搭建的复杂度
  • SpringBoot只是辅助简化Spring项目的搭建过程的,如果搭建的是Web项目,Web层采用SpringMVC,那么SpringMVC的工作原理还是跟原来一样,并没有因为使用SpringBoot而改变

SpringMVC各个组件的执行流程

  • 用户发送请求到前端控制器DispatchServlet
  • 前端控制器DispatchServlet收到请求后,调用处理器映射器HandlerMapping,进行查找处理器Handler
  • 处理器映射器HandlerMapping,根据URL找到具体的处理器Handler,以及对应的拦截器HandlerIntercepter,将它们一起返回给前端控制器DispatchServlet
  • 前端控制器DispatchServlet,调用处理器适配器HandlerAdapter,进行处理
  • 处理器适配器HandlerAdapter则调用处理器Handler(也就是Controller)进行处理,首先将请求参数映射到处理器的方法参数上,然后调用处理方法进行处理,以及返回结果(ModelAndView模型视图对象),交回给前端控制器DispatchServlet
  • 前端控制器DispatchServlet则将ModelAndView模型视图对象,交给视图解析器ViewReslover,视图解析器则会解析视图地址,进行请求转发跳转到视图页面,使用视图数据进行渲染视图,最后再渲染结果交回给前端控制器DispatchServlet
  • 前端控制器DispatchServlet,将渲染结果返回给浏览器
  • 如果是异步请求,返回JSON数据,那么则不需要进行视图解析,直接将Json字符串数据返回给浏览器

SpringMVC的常用注解

  • @RequestMapping,用于处理所有请求类型的Url映射的注解。可用于类上或方法上
    • 用于类上时,表示该类的所有方法都是以该地址作为父路径
    • 用于方法上时,表示该方法能处理的资源路径
  • @RequestMapping还衍生出4个常用的注解
    • @GetMapping,该注解表示处理Get请求
    • @PutMapping,该注解表示处理Put请求
    • @DeleeteMapping,该注解表示处理Delete请求
    • @PostMapping,该注解表示处理Post请求
  • @RequestBody,该注解表示接收Http请求传递的json格式的请求体,并将json格式请求体转换为Java对象
  • @ResponseBody,该注解表示该controller方法返回的对象,会转换为json字符串返回给客户端
  • @PathVariable,该注解表示方法绑定的url中的占位符参数到指定的方法参数,通过@Pathvariable("要获取的参数名")来指定
  • @RequestParam,该注解表示方法参数接收Http请求的参数之前,做一些限制
    • value属性,指定该方法参数接收哪一个请求参数
    • required属性,指定该方法参数对应的请求参数是否必传
    • defaultValue属性,当该方法参数对应的请求参数未传时,参数的默认值
  • @ControllerAdvice,用于在类上,表示该类是一个全局异常处理器类
  • @ExceptionHandler(Exception.class),用于异常处理器的方法上,表示该方法能处理的异常类型

SpringBoot的常用注解

  • @SpringBootApplication注解,SpringBoot项目的核心注解,每个SpringBoot启动类上都有,用于引导SpringBoot项目启动和加载
  • @ComponentScan注解,用于扫描Spring的组件,并将其加入IOC容器
  • @Configuration注解,声明该类为配置类
  • @ConditionOnClass注解,一般与@Configuration注解同时使用,项目导入@ConditionOnClass注解声明的类时,@Configuration配置类中,使用@Bean的方法才会被调用,才会构建方法返回的对象
  • @ControllerAdvice和@RestControllerAdvice,声明该类为全局异常处理类

Spring框架中都用到了哪些设计模式

  • 工厂模式,BeanFactory就是简单工厂的体现,用来创建对象的实例
  • 单例模式,Bean默认为单例模式
  • 代理模式,Spring的AOP功能用到了JDK动态代理与CGLIB动态代理
  • 模板模式,用于来解决代码重复的问题,比如RestTemplate、JmsTemplate、JpaTemplate、TransactionTemplate
  • 观察模式,包含被观察者与观察者两类对象,一个被观察者可以有若干个观察者,一旦被观察者的状态变化,所有观察者都会得到通知。Spring的事件驱动模型就是观察者模式的应用。Spring中的事件监听器的开发,当事件(被观察者)发生时,会自动触发监听器(观察者)的运行。例如Spring中的一种Listener,ApplicationListener

SpringBoot的优势

  • 版本锁定,解决的是maven依赖容易冲突的问题,SpringBoot提供的父工程中集成了常用、且测试过的依赖库版本
  • 起步依赖(简化配置依赖),提供了众多的starter启动器,starter的依赖中集成了需要的依赖以及版本,进行了统一的控制,解决了某一个功能需要整合大量的jar包与依赖的问题
  • 自动配置(简化配置),解决了整合众多框架与技术的配置文件、配置类过多的问题,使用约定大于配置的思想,提供了大量的默认配置
  • 内置Tomcat(简化部署),通过内置Tomcat部署与运行,无需使用外置Tomcat即可直接运行Web应用
  • 总结:SpringBoot被称为搭建Web应用的脚手架,主要作用就是帮助开发者快速构建一个庞大的Spring项目,并尽可能减少xml配置与配置类,做到开箱即用,快速上手,让开发者关注业务逻辑而非配置

说一下SpringMVC的统一异常处理的思想和实现方式

  • 使用SpringMVC后,我们的代码将有SpringMVC来负责调用,所以我们的代码导致的异常,最终都会抛出到框架中,然后由框架指定异常类来进行统一处理
  • 实现统一异常处理的方式:
    • 方式一:创建一个类,作为自定义异常处理器,需要实现HandlerExpcetionResolver接口,并实现接口里面的异常处理方法,然后将这个类加入到IOC容器中
    • 方式二:创建一个类,在类上使用@ControllerAdvice注解或RestControllerAdvice,在类中定义异常处理方法,并在处理方法上添加@ExceptionHandler注解,在该注解上有一个value属性,用于指定这个异常处理方法能处理哪个类型的异常

在SpringMVC中,如果想通过请求转发将数据传递到前台页面,有几种写法?

  • 方式一:直接使用reuqest域对象进行数据传递
request.setAttirbuate("name", value);
  • 方式二:使用Model类进行传值,它的底层会将数据放入request域进行数据的传递
model.addAttribuate("name", value);
  • 方式三:使用ModelMap进行传值,底层会将数据放入request域进行数据的传递
modelmap.put("name", value);
  • 方式四:借用ModelAndView在其中设置数据和视图
mv.addobject("name", value);
mv.setView("success");
return mv;

SpringBoot的启动器starter是什么?它的执行原理?

  • 什么是starter?
    • starter启动器,可以通过启动器集成其他的技术,比如说: webmybatisredis等,可以提供对应技术的开发和运行环境
    • 比如: pom中引入spring-boot-starter-web,就可以进行web开发
  • starter执行原理?
  • SpringBoot在启动时,会扫描jar包中名为的spring.factories配置文件,根据配置文件,加载自动配置类,配置文件的格式为key=value,value配置了很多需要Spring加载的类
  • Spring会去加载这些自动配置类,Spring读取后,就会创建这些类的对象,然后放到Spring容器中,后期就会Spring的容器中获取这些对象

Redis的数据类型有哪些?

Redis一共有5种数据类型:

  • String,字符串类型,最基础的类型,最常用
  • Set,可以存储多个值,无序,存储的数据不可重复
  • List,可以存储多个值,有序,存储数据可以重复,存储顺序就是排序的顺序,可以实现队列和栈
  • Hash Value,键值对,可以对里面的value灵活地修改
  • SortSet(ZSet),可以存储多个值,有序,存储元素不可重复,可以实现实时排序

SpringCloud常用组件

  • Eureka:服务发现与注册,由Netfilx开源
  • Nacos:服务发现与注册,以及配置中心的管理功能,由阿里巴巴开源
  • SpringCloudGateway:微服务网关,微服务统一路由,统一鉴权、跨域、限流等功能
  • Feign:微服务之间远程过程调用,由Netflix开源
  • Ribbon:负载均衡组件,在网关路由和Feign远程过程调用中,底层都会使用到Ribbon实现负载均衡

Eureka与Nacos的区别?

  • 共同点
    • 都支持服务注册与服务拉取
    • 都提供服务提供者心跳方式进行健康检测(Eureka30秒一次,Nacos15秒一次)
  • 区别
  • Nacos支持服务端主动检测提供者的状态,临时实例采用心跳机制,非临时实例(常驻实例)采用主动检测机制
    • 临时实例心跳不正常会被剔除,而非临时实例(常驻实例)则不会被剔除
    • Nacos支持服务列表变更后,进行消息推送,服务列表更新更及时(Eureka是每次发起远程调用时拉取,Nacos则是定时更新)
    • Eureka是短连接操作(每次拉取完,就断开连接),Nacos是长连接操作(netty实现,一直连接,每次拉取完,不会断开)
    • Nacos还可以作为微服务的配置中心,Eureka则不能

Elasticsearch比MySQL的优势在哪里?(为什么要使用Elasticsearch?)

  • MySQL的海量数据时,搜索效率比较低,使用Like关键词,如果%放左边,执行全表扫描,导致性能差,而Elasticsearch采用倒排索引法检测数据,从而效率更高
  • MySQL的搜索功能比较弱,只有like这种模糊搜索,而Elasticsearch拥有大量复杂场景搜索的API(高亮显示,拼音搜索,地理位置检索),更加适合数据搜索场景

请说说Elasticsearch倒排索引原理?

  • 首先,Elasticsearch将文档数据进行索引构建。将文档数据需要分词的字段内容使用分词器进行分词,并记录每个词条和原文档的出现位置和出现频率等信息,构建出文档的索引库

  • 然后,用户搜索时,可以对关键词进行分词,使用分词后词条来匹配索引库,在索引库匹配到记录后,通过文档位置和频率信息,反查具体的文档数据

请说说什么是分词器?ES有哪些常用的分词器?

  • 分词器是Elasticsearch用于对内容进行分词的工具(程序),Elasticsearch内置许多分词器,默认使用Standard标准分词器,而标准分词器对中文支持并不好友(因为它对中文进行单字分词)
  • 所以在开发中进行中文分词时使用第三方的ik分词器,ik分词器内置有ik_smart和ik_max_word算法,ik_smart是最小分词器法,ik_max_word是最细分词法。

MySQL、Redis、MongoDB、Elasticsearch各自的优势?

  • MySQL:是关系型数据库,磁盘。有复杂表关系(一对一,一对多,多对多),并且有完善事务机制(ACID)。例如:用户、订单、商品
  • Redis: 非关系数据库,内存。Redis建议只存储热点(用户查询频率极高)的数据,且数据量相对小的数据。例如:秒杀的库存量、手机验证码、用户token
  • MongoDB: 非关系数据库,磁盘。MongoDB适合相对高频擦写(增删改)的海量数据。例如:评论
  • Elasticsearch: 非关系数据库,磁盘。Elasticsearch适合海量数据的复杂检索。例如:商品搜索
  • 效率: Redis > MongoDB /Elasticsearch:> MySQL

请问Elasticserach中的match和term检索有什么区别?

  • match:全文检索,分词,用在text类型中
    • 先对搜索内容进行分词,得到词条
    • 使用词条匹配倒排索引,得到文档ID
    • 再使用文档ID,查询具体的文档记录,进行聚合(并集或交集)
  • term:精确匹配,不分词,用在keyword、boolean、日期、数值类型中
  • 使用搜索内容,使用搜索全文匹配索引库,得到文档ID
  • 使用文档ID,查询具体的文档记录,进行聚合(并集或交集)

请问Elasticsearch如何实现搜索附近的景点?

  • 在景点表中,保存景点的经度和纬度
  • 前端将用户的经纬度坐标,上传到后端服务中
  • 后端根据用户的经纬度坐标,使用Elasticserach的geo_distance,做升序排序,参数是地理坐标、升序或降序排序
"sort": [
  {
    "_geo_distance": "22.57.113.88",
    "order": "asc",
    "unit": "km"
  }
]

请问在Elasticserach中,如何实现提升指定搜索结果的权重?(类似百度搜索结果中的广告竞价排名)

  • 首先,Elasticsearch默认情况下,使用BM25算法(5.1版本之前,使用TF-IDF算法),计算_score得到算分,按照计算排序。

  • TF-IDF算法:TF(词频) * IDF(逆文档率)

    • TF:词频,词条在文档中出现的频率
    • IDF:逆文档率,计算词条在所有文档中的权重(出现越多文档,权重越低,反之则越高)
  • BM25:是TF-IDF算法的升级版,单个词条的算分有一个上限,不至于过高,让曲线更加平滑

  • 如果希望改变指定结果的权重,可以使用function_score函数,进行提分

  • function_score函数大体有3部分:

  • 1)原始查询条件

  • 2)过滤条件和权重分值,过滤条件就是哪些部分的文档需要提分

  • 3)加权模式,对权重分值进行sum、avg、相乘等规则

在Elasticsearch中,如何实现景点拼音搜索

  • 在Elasticsearch安装拼音分词插件
  • 因为拼音分词器只是把词的每个字进行转拼音,无法实现对一个词进行拼音转换,不太满足项目需求
  • 这时就需要结合ik分词器,进行自定义分词器
  • 自定义分词器,需要配置为先使用ik分词器进行中文分词,然后再将这些词进行拼音分词器,进行转换为拼音
  • 但是在搜索中,为了避免中文转为拼音进行搜索,所以搜索时还是使用ik分词器进行分词,使用search_analayzer来指定,如:
"analyzer": "my_analyzer",
"search_analyzer": "ik_max_word"

在Elasticsearch中,如何实现搜索景点时,关键字高亮

  • 在景点索引库中,添加一个completion字段,该字段的内容是数组,在里面填充需要补充的数据
  • 在用户搜索时,每次输入关键词时,使用suggest搜索,进行suggestion搜索,把结果列表返回给前端
GET /test/_search
{
 "suggest":{
  "title_suggest":{
    "text":"s",
    "completion":{
      "field":"title",
      "skip_duplicates":true,
      "size":10
     }
  }
}

在Elasticserach中,如何与MySQL进行数据同步?

  • 同步方式(不推荐),在对MySQL的数据进行增删改时,同步调用ES更新数据,这种方式会导致整个增删改功能的耗时增加,以及级联失败
  • 异步方式,使用MQ发送异步消息,实现增量同步
    • 在生产者(如后台管理系统),的增删改方法中,调用MQ发送消息,带上主键ID给MQ
    • 在消费者(如前台服务),编写MQ消息监听类,针对增加、更新、删除消息,对ES进行增删改数据(这种方式,如果直接在数据库中,增删改数据,会导致ES和MySQL数据不一致)
  • 使用阿里巴巴的Canal,监听MySQL的 bin log 日志,同步增删改到ES中,这种方式,对业务速度影响最小,能更快的响应用户的请求,用户体验更好,但编程方式稍微有些复杂,而且Cannal只支持MySQL,如果数据换成Oracle数据库,则不能支持

请问Elasticserach的脑裂问题,是如何产生?要如何解决?

  • 什么是脑裂?
    • 一个Elasticserach集群中,出现了多个master主节点的情况,就被称之为脑裂
  • 脑裂是如何产生的?
    • master主节点和slave从节点,因网络延迟、网络阻塞、网络故障等原因,导致slave从节点无法和master主节点通信
    • master节点,需要处理数据,也需要处理任务派发,复杂过重,导致出现假死
    • Elasticserach的JVM内存不足,进程出现无响应
    • 以上情况,都可能会导致master节点临时丢失,导致slave从节点重新选举master节点(重新选主)
  • 如何解决?
    • 修改Elasticserach的节点通信的默认超时时长,默认为3秒,改长一些
    • master主节点职责分离,master主节点不作为数据存储节点,nodes.data=false,减轻主节点的工作量
    • 加大Elasticserach的JVM内存,默认1个G,改大一点,最好为物理机器的内存的一半
    • 修改Elasticserach的选举票数,默认为3票就开始重新选主,改为(备选节点数 / 2)+ 1,ES7默认已经改好,ES6或ES5需要手动修改一下这个值

SpringCloud和SpringCloudAlibaba有什么关系?

  • SpringCloud属于Spring家族的成员

    • 组件有:Eureka(注册中心)、Gateway(网关)、Feign(远程过程调用)、Ribbon(客户端负载均衡)、Hystrix(熔断器)、SpringCloudConfig(配置中心)、Sleuth(链路跟踪)
    • 依赖特点:spring-cloud-starter-netflix 开头
  • SpringCloudAlibaba属于SpringCloud的一套组件

    • 组件有:Nacos(注册中心和配置中心)、Sentinel(熔断器)、Seata(分布式事务)
    • 依赖特点:spring-cloud-starter-alibaba 开头

Sentinel和Hystrix的区别?

  • 隔离方式不同,Sentinel采用信号量(计数器),而Hystrix支持线程池和信号量,默认采用线程池,信号量性能比线程池好
  • 熔断策略不同,Sentinel可以支持超时比例和异常比例,而Hystrix只支持异常比例
  • 限流功能不同,Sentinel有丰富的限流功能(QPS、热点参数、关联模式、链路模式等),Hystrix限流功能非常弱
  • 第三方框架整合方面,Sentinel可以整合SpringCloud和Dubbo,Hystrix只能整合SpringCloud

介绍一下,Sentinel的熔断机制以及使用?

  • Sentinel的熔断机制,主要有线程隔离和熔断降级,启用步骤:
  • yml配置文件中,开启Sentinel的线程隔离和熔断降级功能,然后在Sentinel控制台页面中,加上隔离最大并发线程数或熔断参数配置(统计时间,响应超时时间,请求数,熔断时间等)
  • 接着,给Feign接口定制一个服务降级实现类,在隔离和熔断发生后,给用户返回友好提示信息
# application.yaml配置文件
feign:
  sentinel:
    enabled: true # 开启sentinel支持
//Feign接口的服务降级实现类
public class UserClientFallbackFactory implements FallbackFactory<UserClient> {
    @Override
    public UserClient create(Throwable throwable) {
        return new UserClient() {
            @Override
            public User findById(Long id) {
                User user = new User();
                user.setUsername("查无此人");
                user.setAddress("查无地址");
                return user;
            }
        };
    }
}
  • 线程隔离:在服务消费方加入服务调用占用线程数统计,一旦超过线程数上限,则做服务降级(在服务消费方定制一个降级处理方法,定制失败消息)
  • 熔断降级:在服务消费方加入超时或异常比例统计程序,该程序一旦统计超过比例,一旦比例超过阈值,则做服务降级(在服务消费方定制一个降级处理方法,定制失败消息),熔断有时长,时间到达会尝试请求1次,如果成功,则正常调用,如果失败,继续熔断

请解释一下SpringCloud中Feign接口调用的过程/原理?

  • Feign是声明式Http客户端,Feign的接口写在服务消费者中,当我们依赖注入Feign的接口时,会使用JDK动态代理,生成接口的实现类。当我们调用Feign接口中的方法时,会拦截我们调用的方法,接着就会解析方法上的注解
  • 例如会解析接口上的@FeignClient注解,获取我们配置的服务名,Feign通过Ribbon从注册中心nacos中,获取到该服务的服务列表,通过负载均衡算法,挑选出1个,就可以拿到服务的ip和端口,拼接成url
  • 接着,会解析方法上的例如@GetMapping@RequestParam注解获取到请求方法、资源地址和请求参数
  • 最终通过RestTemplate,发起http请求给服务提供者,服务提供者收到请求后,处理请求,返回响应数据,Feign就会使用Jackson,将响应的json数据转换为java对象返回给我们

大概解释一下熔断器的执行流程?

  • 熔断器是一个统计超时比例、异常比例的程序,它是放在服务消费者中的,熔断器有3个状态:关闭、开启、半开
  • 当服务消费方的请求的超时比例或异常比例,没有达到阈值时,熔断器处于一个关闭状态,请求可以正常通过
  • 而当超过了阈值后,熔断器状态处于开启状态,请求会被降级,降级后,会执行我们配置的Fallback接口,我们返回一个对用户友好的提示信息
  • 然后,熔断器会等待一段时间,例如5秒,就会进入半开状态,会尝试放行1个请求,如果请求成功,那么熔断器切换到关闭状态,放行后续的请求,如果请求失败,那么切换回开启状态,继续对请求进行降级

说一下CAP定理和BASE理论

  • CAP定理,C是值一致性,A指可用性,P指容错性

  • CAP定理,在说P必然存在的,C和A只能选择一个特性。在CAP定理,只存在CP或AP

  • BASE理论,是对CAP一种补充:

    • BA指基本可用,S代表软状态,E代表最终一致性CAP定理说如果选择了一致性,就放弃了可用性,BASE理论说选择了一致性,只是损失了部分可用,意味处于基本可用CAP定理说- 如果选择了可用性,就放弃了一致性,BASE理论说选择了可用行,只是存在临时的不一致状态,这种临时的不一致性称为软状态,这种软状态过后达成最终一致性

请问Seata的AT模式是AP还是CP?大概解释Seata的AT模式的原理

  • AT模式是一种AP模式(强可用,弱一致性)
  • Seata的AT模式的执行济程大概就这样:Seata架构中存在二大组件,TC (TC是事务协调者),TM (事务管理器)RM(资源管理器)
  • 首先,由TM向TC发出开始全局事务的请求,TC在全局事务表中记录数据。接着,由TM通知各人的RM调度各自的分支务,这时分支事务开始执行啦,分支事务先向TC进行注册分支事务,开始执行SOL语句并提交,在SQL执行的前后,AT模式会把更新记录的前后数据保存到undo log日志表中作为数据快照,再上报事务执行结果给TC。最后,TC收集到所有分支事务的执行状态,进行分析,决定是否提交还是回滚,如果提交,则TC向所有RM发出删除undo log日志记录的请求.如果回滚,则TC向所有RM发出读取undo log数据快照做数据恢复的请求
  • 在AT的执行过程中,会有脏读的情况存在,Seata考虑到了,利用全局事务锁表,在每个分支务提交之前,判断是否能获取全局事务锁决定是否提交,这样就控制脏写

什么是乐观锁、悲观锁,他们有什么差别?

  • 悲观锁:

    • 总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁
    • 悲观锁的应用场景:关系数据库的行级锁和表级锁等
  • 乐观锁:总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候,会判断在此期间别人有没有去更新这个数据。

乐观锁的实现方式:

  • 可以使用版本号机制和CAS算法实现,在数据表中加入一个数版本号version字段,表示数据被修改的次数,当数据被修改时,version的值会加一

  • 当线程A要重新更新数据值时,在读取数据的时候也会读取version值,在提交更新时,若刚才读取到的vesion值与当前数据库中的version值相等才更新,否则重新更新操作,直到更新成功

  • 悲观锁与乐观锁的应用差别:

    • 乐观锁适用于写少读多的场景。这样可以省去了锁的开销,加大了系统的整个吞吐量
    • 悲观锁更适合读少写多的场景。因为如果在写多的场景下使用乐观锁,会导致应用会不断的进行重试,这样反倒是降低了性能,所以一般写多的场景下更适合使用悲观锁

MySQL的引擎有几种?

  • InnoDB: MySQL默认存储引擎,支持事务。支持行级锁和表级锁。索引采用聚族索引(索引和数据存储在一个文件,提升查询性能)
  • MyISAM: 不支持事务。仅仅支持表级锁。索引采用非聚簇索(索引和数据分开存储,查询性能差一些)

RabbitMQ和Kafka,各有什么优缺点?

  • RabbitMQ:
    • 优势:
      1. 支持语言非常广
      2. 稳定性很好,采用Erlang语言开发
      3. 吞吐量不算低,万级
      4. RabbitMQ官方提供7种消息发送模式,开发者轻松选择合适的模式进行开发即可
    • 缺点:
      1. 采用Erlang,太小众,研究源码很难
  • Kafka:
    • 优势:
      1. 高吞吐量,百万级
      2. 稳定性好,采用zookeeper进行注册 (Zookeep采用CP模式,高一致模式)
      3. 可以应用在大数据数据处理领域 (KafkaStream)
    • 缺点:
      1. 支持的开发语言比较少
      2. 耦合zk,依赖zookeeper进行注册

什么是事务?

  • 事务是一组原子操作单元,从数据库角度来讲,就是一组SQL语句,要么全部执行,要么全部失败,就是有其中一个指令执行有错误,那么撤销前面执行过的所有SQL指令

  • 事务的特性

    • 原子性:即不可分割性,事务要么全部被执行,要么就全部不被执行
    • 一致性:事务的执行使得数据库从一种正确状态转换成另一种正确状态
    • 隔离性:在事务正确提交之前,不允许把该事务对数据的任何改变提供给任何其他事务
    • 持久性:事务正确提交后,其结果将永久保存在数据库中,即使在事务提交后有了其他故障,事务的处理结果也会得到保存。

事务的四大特性和隔离级别

  • 读未提交(read Uncommited)
    • 在该隔离级别中,所有事务都可以读取到别的事务未提交的数据,会产生脏读的问题,在项目中基本不用,安全性太差
  • 读已提交(read commited)
    • 这是大多数的数据库的默认隔离级别,但不是MySQL的默认隔离级别,这个隔离级别满足了简单的隔离,一个事务只能看到已经提交的事务所有的改变,所以避免了脏读的问题,但由于一个事务可以看到别的事务已经提交的数据,随之而来产生了不可重复读和虚读的问题
  • 可重复读(Repeatable read)
    • 这是MySQL的默认隔离级别,它确保了一个事务并发读取数据时,能读取到一样的数据。不过理论上,也导致了幻读(Phantom Read)。简单的来讲,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再次读取该范围的数据行时,会产生幻读
  • 可串行化(serializable)
  • 事务的最高级别,它通过强制事务排序,使之不可能互相冲突,从而解决幻读问题。简单来讲,它是在每个读的数据行上加上共享锁,在这个级别,可能会导致大量的超时和锁竞争,一般为了提升程序的吞吐量,不会采用该级别

Redis的数据类型和持久化方式

Redis有5种数据类型

  • String
  • Set
  • List
  • Hash
  • SortedSet(ZSet)

Redis的持久化方式

  • 有2种方式,分别是RDB和AOF,RDB的原理是对整个内存数据进行快照备份,文件体积小,而AOF,原理是每条操作指令都会持久化到文件,导致文件体积比较大
  • RDB的2次备份时间间隔最小是1分钟,时间长,容易导入数据丢失。而AOF的默认间隔时间为1秒1次,时间短,数据完整性高
  • 但从数据恢复速度来讲,RDB比AOF快,因为体积小
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容