面试总结 详细

JVM篇
系统讲一下JVM架构
JVM主要分为三部分,第一,有一个类加载系统,这部分会对class文件的进行加载、验证、准备、解析、初始化。
第二部分是一个运行时数据区,里面有堆区域、堆栈区、和方法区、程序计数器、本地方法栈。这里面jvm还实现了一个垃圾回收机制。
第三部分是一个执行引擎,这一部分会去执行运行时数据区的字节码。

1、什么是JVM?
JVM是java程序与计算机沟通的桥梁,这座桥梁不在乎它是java运行环境的组成部分,java程序通过java编译器编译成虚拟机可以理解的字节码,通过虚拟机调用字节码来调度操作系统为我们工作。java虚拟机有自己的内存模型和内存管理机制,制定了自己的垃圾回收机制,以及类加载机制。
2:JRE/JDK/JVM是什么关系

JRE(JavaRuntimeEnvironment,Java运行环境),也就是Java平台。所有的Java 程序都要在JRE下才能运行。普通用户只需要运行已开发好的java程序,安装JRE即可。

JDK(Java Development Kit)是程序开发者用来来编译、调试java程序用的开发工具包。JDK的工具也是Java程序,也需要JRE才能运行。为了保持JDK的独立性和完整性,在JDK的安装过程中,JRE也是 安装的一部分。所以,在JDK的安装目录下有一个名为jre的目录,用于存放JRE文件。

JVM(JavaVirtualMachine,Java虚拟机)是JRE的一部分。它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。JVM有自己完善的硬件架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。Java语言最重要的特点就是跨平台运行。使用JVM就是为了支持与操作系统无关,实现跨平台。

JVM原理
jvm是利用软件方法实现的抽象的计算机,通过识别java编译器编译的字节码文件,用解释器将每一条指令翻译成不同平台的机器码来实现的

2、JVM的内存模型
JVM的内存模型是每个线程都有自己独立的工作空间,所有的内存都有自己共享的内存空间,当线程要对共享空间的数据进行操作时,首先,它会将数据复制到自己的工作空间对这个数据进行操作,然后将操作的结果返回到共享空间中。

堆和栈的区别
(1)堆主要放new的对象,而栈放基本数据类型和对象的引用,引用指向的是堆。
(2)在堆中有jvm有垃圾回收机制
3、栈中数据不会再多个线程中共享,而堆中的数据是被所有线程所共享的

3、JVM的内部结构。
首先,JVM中内部结构分为线程安全以及线程不安全的区域。
线程安全的区域包括,本地虚拟机栈,本地方法栈和程序计数器。本地虚拟机栈描述的是java方法执行的内存模型,每个方法执行的时候会创建一个栈帧,里面存放的是局部变量表,对象引用等信息。本地方法栈和本地虚拟机栈类似,不过它描述的是本地方法执行的内存模型。程序计数器记录程序执行到哪了,下一步将怎么执行。
线程不安全的区域包括堆和方法区,其中堆中存储的对象实例,如果按内存回收的角度来看,堆还分为新生代和老年代。
方法区存放的是类信息,常量,静态变量。

JAVA回收机制:
1、判断什么对象该回收?
java虚拟机有两种方式来判断一个java对象是否该回收。
第一:引用计数法,该方法无法解决对象之间循环引用的问题,会造成内存泄漏。
第二:Java虚拟机会指定一些特定是对象作为GCroots,通过对象的引用关系形成引用链,对于不在这些引用链中的对象,java虚拟机会认定这些对象时不可达的。当对象被标记为不可达时,若这个对象的finalize()方法已经被调用过一次或者没被重写,那么对象将会被清理。
2、用什么方式清理?
1、标记清理
会产生不连续的内存空间,不利于大对象的存放,而且效率很低。
2、整理清理
解决产生不连续内存空间的问题,但是效率依然很低。
3、复制算法
将内存空间分为两半,每次只使用其中的一半,当其中一半满了,将还存活的对象复制到另一半,且清空前一半。
内存利用率低,所以java虚拟机一半会采用8:1:1来划分内存空间。
4、分代收集。
根据老年代对象和新生代对象的特性来采用不同的垃圾回收算法。
3、时候回收?
java虚拟机有两种回收垃圾的策略,一种是针对新生代对象的minorGC,当新生代空间的eden区没有足够内存分配,将发起minorGC,这种回收比较频繁,而且速度也较快。
还有一种是针对老年代对象发起的fullGC,
当老年代内存空间不足,
持久代空间不足,
GC担保失败。
在minorGC发生之间,JVM会去计算这次年轻代空间的总空间是否大于老年代最大的可用连续空间,如果大于,要看虚拟机是否允许担保失败,如果担保失败或者老年代最大可用空间小于历次晋升老年代对象的平均大小,会发生一次fullGC。

新生代如何进入老年代
1、每个对象都会维护一个年龄,每次发生一次minorGC,存活的年龄达到一定程度,就会进入老年代。
2、如果大对象需要的内存超过新生代内存,直接进入老年代。
3、如果survivor空间中相同年龄的大小总和大于survivor空间的一半,直接进入老年代。

对象之间的引用
1、强引用:new一个对象时,不可能被系统垃圾回收机制回收,即时这个对象永远不会被用到。
2、软引用:
3、弱引用:
4、虚引用:

垃圾收集器:
如果就收集方式来分类的话:
1、串行GC收集器,比如serial系列,当GC发生时,会停止整个程序。
2、并行收集器,比如parallel系列,与串行类似,不同的是它使用了多线程,在多CPU的环境下会比较快。
3、并发收集器:比如CMS
它的特点是低停顿,并发。他收集的过程主要包括:
1、初始标记, 这个过程会标记直接与GC ROOTs关联的对象。
2、并发标记,这个过程是并发的,会标记所有的可达对象。
3、重新标记,这个过程需要暂停工作线程, 需要重新标记变化的引用关系。
4、并发清除,清理对象的过程。

怎么选择垃圾收集器?
这要视具体需求而定,垃圾收集器一般会分为新生代和老年代,对于新生代,选择复制算法的收集器,对于老年代,一般选用标记清理算法的收集器。对于单CPU环境,可以选用串行收集的收集器,对于多CPU可以采用并行收集器,如果想改善系统停顿时间,可以采用CMS收集器。

你能让系统执行垃圾回收吗?
不能,我们只可以使用System.GC()建议JVM执行垃圾回收,但是不能保证这个建议会有效。

Out of Memory异常
当堆中的内空间不足,且无法扩展时,会发生Out of Memory,比如,无限地创建对象,
stack overflowerror 异常
若线程请求的栈的深度大于虚拟机所允许的深度,会抛出stack overflowerror 异常,若虚拟机可以动态扩展,扩展时无法申请到足够的内存空间,会抛出OOM异常

java程序是初始化顺序
父类静态变量、父类静态代码块,子类静态变量,子类静态代码块,父类实例变量,父类实例代码块,父类构造器,子类实例变量, 子类实例代码块,子类构造器

类加载顺序
1、加载 :将类加载并生产class对象的过程
2、校验:对字节码的格式进行检查是否符合规范
3、准备:为类变量分配空间,并赋默认值
4、解析:将常量池的符号引用替换成直接引用
5、初始化:执行类构造器方法的过程

双亲委任机制
一个类加载的请求发送过来,当前类加载器不会直接去加载这个请求,而是委托它的父类加载器,没一层类加载器都是如此,因此所有的加载请求都会到达启动类加载器。只有当父类加载器反馈自己无法加载时,子类加载器才会尝试自己去加载。
这样的好处在于可以保证系统安全性,比如java中的object类,它存放在rt.jar中,无论哪个类加载器加载这个类都是委托给启动类加载器,这样的话Object在各种类加载环境中都是同一个类,如果没有这个机制,那么系统会存在很多不同的Object类。

对象初始化顺序:
1、首先java虚拟机会检查对象的类是否被加载
2、为对象在堆中开辟内存空间
3、对实例变量赋默认值
4、将对象进行初始化

多线程篇:
1、为什么要使用多线程?
使用多线程可以充分的利用CPU资源,提高CPU的使用率,采用多线程的方式可以同时完成几件事而不互相干扰,比如一些IO操作,如果只有单线程,那么会花费很多时间去等待,多线程就可以把这些任务放到后台去处理。
缺点:
1.如果有大量的线程,会影响性能,因为在操作系统在处理多线程的时候是通过不断地切换任务来执行的。
2、更多的线程也会占据更多的内存空间
3、由于多个线程之间可能存在共享数据,所以要考虑线程同步的问题,在考虑线程同步时又得考虑死锁情况发生。

Thread.start()与Thread.run()有什么区别?
start()方法是一个本地方法,他能在内部调用run方法,从而启动线程,而run方法的话只是一个普通的实例方法,调用它只是执行线程的run方法而已。

线程和进程的区别
1、对于操作系统来讲,线程是最小的调度单位,进程是最小的资源分配单位这就以为着进程有自己独立的地址空间,在同一个进程中的线程共享同一个地址空间。
2、一个进程最少有一个线程,而线程依赖于进程而存在。
3、由于上面说的特性,CPU对于线程的创建和切换对于资源的花费要比进程少的多,所以线程更加轻量级,而且由于线程之间共享数据,所以线程之间的通信也更加方便。

线程和进程之间的通信方式
进程通信主要有:
1 管道:只能发生在父子进程中
2 有名管道:允许无关系的进程通信
3 信号量;控制多个进程对共享资源的访问
4 消息队列
5 信号:通知进程某个事件发生
6 共享内存:与信号量搭配使用,实现进程间的同步和通信
7 套接字:用于不同设备之间的进程通信
线程:
1、锁机制
2、信号量机制
3、信号机制

如何创建一个线程
在Java中可以通过四种方式创建线程
1、通过继承Thread类来创建线程
2、通过实现Runnable接口来创建线程
3、通过Callable接口和future创建线程
Callable接口创建线程是实现它的call方法,这个方法有返回值,而且可以抛出异常,这个返回值可以被future接收,通过这个特性能够实现异步计算的功能。
4、实现线程池来创建线程
线程池中有四个主要的参数
maxnumpoolsize :表示一个

如何保证线程安全,
1、不共享线程间的变量,比如servlet可以通过这个方式来获取
2、设置属性变量为不可变变量
3、利用锁机制

线程中sychronized和lock的区别

sychronized的底层实现

线程之间如何传递数据

怎么停止一个线程
可以通过interrupt 方法和

怎么保证多个线程的顺序执行
可以使用join方法,这个方法在线程中使用表示把指定的线程加入到当前线程中,只有当调用join方法的线程执行完毕,才会执行当前线程。

wait和sleep方法的区别
wait方法是Object方法,而sleep方法是Thread的一个方法,
wait方法只有在同步快中才能调用,它会使当前线程释放锁。
sleep方法是让当前线程放弃cpu资源进入等待状态,它可以在任何地方调用,不会时当前线程释放锁。

生产者和消费者问题

写一个死锁

什么是原子操作,java中的原子操作是什么
原子操作指的是不可分割的操作,一个原子操作中间是不会被其他线程打断的,所以不需要同步一个原子操作。
i++不是一个原子操作,它包含读取,修改,写入,在多线程状态下是不安全的。可以使用volitile使他们成为原子操作。

volatile关键字
首先,volatile的作用是保证变量的可见性,在java内存模型中,每个线程有自己的工作空间,当对一个共享变量操作时,线程会从主内存中将数据复制到自己的工作内存,修改后在某个时间刷新到主存空间。在这个过程中,它保证读取数据只能从主内存读取,修改直接修改到主内存空间,这就保证了这个变量对多线程的可见性。但是它只能保证单次读或者写是原子的。所以说,它不能保证多次操作的原子性,这就意味着它不是线程安全的。

产生死锁的必要条件
1、互斥:一个资源每次只能被一个进程使用
2、循环等待:若干个线程之间形成头尾相接的循环等待关系
3、请求与保持:一个进程因请求资源而阻塞,对已获得的资源保持不放
4、不可剥夺:进程已获得资源,在未使用完之间,不能强行剥夺。

如何避免死锁
1、可以设置加锁的顺序,也就是线程按照一定顺序加锁,但是这种方式你必须知道所有可能会用到的锁
2、设置加锁的时限,当一个线程在一定时间内没有成功获取到锁,就会进行回退,并且释放之间获取到的锁
但是这种方式在大量线程的情况下还是会出现死锁
3、死锁检测:当一个线程获取锁之后,会在相应的数据结构中记录下来,如果另一个线程请求锁,也会在相应结构中记录下来,当一个线程请求失败,会遍历这个数据结构检查是否有死锁发生。

线程的状态:
线程的状态分为新建,就绪,运行,结束,等待,无限制等待,阻塞这些状态,新建指的创建一个线程对象,就绪指的是调用start方法,但是此时该线程还未分得CPU的时间片,等待CPU调度的状态,运行指的是线程在执行任务的状态,对于单CPU来说,在一个时间点只有一个线程处于这个阶段,等待指的是调用sleep方法或者join方法,此时线程会放弃自己占用的CPU资源,然后等待时间到之后会重新去竞争CPU资源。阻塞状态指的是线程在等待获取一个锁的状态,此时线程由于无法获取锁资源,也不会运行。

什么是线程池?

ThreadLocal
ThreadLocal是用来维护线程中的变量不被其他线程干扰而出现的一个结构,内部包含一个ThreadLocalMap类,该Map存储的key为ThreadLocal对象自身,value为我们要存储的对象,这样一来,每个线程对应一个threadlocalmap,与其他线程完全隔离,以此来保证线程执行过程中不受其他线程的影响。

也就是说threadlocal提供了解决多线程并发问题的新的思路,我们知道如果一个线程有共享变量的话,那么

你在多线程环境下遇到过什么问题?

一次完整的http请求会发生什么?
首先浏览器会通过DNS解析获取url对应的ip地址,
然后通过三次握手的机制建立起tcp连接,
连接建立完后,浏览器会向WEB服务器发送请求,
服务器响应该请求,然后处理该请求,处理之后将结果返回给浏览器一个html文件,
浏览器解析该html代码,并请求html的资源,然后对页面进行渲染,呈现给用户。

数据库篇:
什么是数据库?
数据库时按照数据结构组织,存储和管理数据的仓库,通过数据库我们能实现数据共享,减少数据冗余度,还能保证数据的一致性和可维护性。

关系型数据库和非关系型数据库的差别
首先、关系型数据库通过构建关系模型来存储数据,它的有点在于支持事务,可以通过sql语句在一个表或者多个表中做复杂数据查询,而非关系型数据库的话是基于键值对的方式来存储数据,数据之间不具有耦合性,非常适合水平扩展,而且由于不需要进过sql层解析,所以性能非常高 。

数据库引擎有哪些。
数据库的引擎有MyIsam,INnoDB,memory和merge,其中masam不支持事务,外键和行级锁,当我们使用这个引擎来操作数据时,会锁住全表,所以相对来说效率比较低,但是他会保存数据的行数,所以在读多与写的场景下,他会比较适合
Innodb支持事务、外键和行级锁,而且它会把数据和索引存放在缓存中,所以效率较高,但是不会保存数据的行数,所以适合于写多于读的场景,memory是基于内存的数据库引擎,它读写非常快,merge只是简单理解成一组mysam表的集合。

数据库索引
什么是索引?
索引是能够提高数据库查询数据的数据结构,就像是书本上的目录,帮助我们快速定位到想要的内容。

索引有哪些数据结构?
有hash索引和B+树索引,hash索引是memory引擎支持的,由于它是以散列表的形式存放的,所以它查找速度很快,但是不支持范围查找和排序
B+树索引的话,所有的索引数据在叶子节点上,而且增加了顺序访问指针,每个节点都有指向相邻叶子节点的指针,这样提高了区间查找的效率

数据库有哪些索引?
有唯一索引
唯一索引的特性在于能够保证该列的数据都是唯一的
普通索引
普通索引只是
主键索引
主键索引的话,特点是该列的数据非但唯一,而且不可为空,一张表只能维护一个主键索引。
联合索引
联合索引是通过将多个字段建立起来的索引,适合与条件查询中有多个字段查询的情况。
全文索引

唯一索引和主键索引的区别
1、一张表中有一个主键索引,可以有多个唯一索引
2、主键索引是不可为空的
3、主键索引可以是多个字段的组合

聚集索引和非聚集索引

索引的注意事项:
索引能加快读的速度,但是对于更新操作来说,由于要维护它的数据结构,所以效率会低,索引一般适合读多余写的情况,
组合索引是为了更多的提高mysql效率的方式,但是一定要注意遵循“最左前缀原则”,将频率最高的筛选条件的列放在最左边。

数据库的三大范式:
1、列不可再分
2、满足与第一范式的前提下,非主属性要完全依赖主属性,不能部分依赖
3、满足第二范式的前提下,消除传递依赖。

数据库连接池
就是为数据库连接建立一个缓冲池,使用完后再放进去,我们可以设置连接池的最大连接数来防止系统无限与数据库连接,更为重要的是我们可以监视数据库连接的状况。在项目里,Mybatis封装了一个数据库连接池,只需要在XML文件中设置它的相关参数即可。

存储过程:
指的是一组为了完成特定功能的sql语句集,存储在数据库中,经过第一次编译后再次调用不需要再编译,用户通过指定存储过程的名字并给出参数来执行它。可以理解成在mysql中写业务逻辑。

内连接、外连接、交叉连接
内连接可以理解成两张表取交集
左外连接是将左边的表作为主表,主表满足条件的行都会显示,右边的表如果没有匹配用null代替
右外与上面的相反
交叉连接 左边每一行都要对应右边所有行,是一个笛卡尔乘积。

事务的特性
事务的特性包括原子性,一致性,隔离性,永久性
隔离级别
脏读、不可重复读、幻读
脏读读到了还未提交的数据。
不可重复读指的是在一个事务中,两次读到不同的数据,因为这个数据在中途可能被别的事务修改过。
幻读指的是在一个事务中读到别的事务插入或者删除的数据。

数据库优化
可以从四个方面去考虑优化
1、可以提升是计算机的性能
2、可以优化sql语句,比如避免使用*号。
3、可以通过增加合适的索引来进行优化。
4、可以将数据库进行分库分表,读写分离。

批处理

JDBC操作过程
1、先加载mysql的驱动
2、创建连接
3、创建statement语句
4、执行sql语句
5、关闭连接

sql执行顺序
select
from
join
where
order by
having
des
limit

约束

用java怎么实现每天有一亿条记录的DB存储?
首先计算平均每秒有几条操作,这个情况是每秒1157,那么如果只是插入的话,可以在插入数据库之前加个缓存,每多少条插入一次?就跟批处理一样。在加上分库,不过这一块不了解。如果还要读取的话,那么这么大数据量还要考虑索引的问题

如何有效合并两个文件:一个是1亿条的用户基本信息,另一个是用户每天看电影连续剧等的记录,5000万条。内存只有1G
显然内存不能同时存下所有的数据,所以考虑分而治之的思想。
假设1K Byte可以保存一个用户的基本信息和看电影记录。我们可以将基本信息和看电影记录都按照hash(user_name)%100的余数各分成100个小文件。利用1G内存,我们可以每次只处理一对小文件,然后将结果输出到一个文件中即可。
在处理一对小文件时,可以利用key为用户名的hash_map将基本信息和看电影记录合并在一起。

索引结构

delete drop druncat
delete是删除行
drop是删除表中所有数据
druncate删除表中所有数据还会删除表结构
其中drop最快

explain 可以用来查看索引的信息,可以在优化索引的时候用上

设计模式
六大原则
单一职责 表示的是一个类负责一件事
开闭原则 对拓展开放,对修改关闭
接口隔离原则 接口设计尽可能小
依赖倒置原则 抽象不该依赖细节,细节要依赖抽象
迪米特法则 类之间依赖应该尽可能小,降低类之间的偶尔性
里氏替换原则 所有父类的引用都能引用到子类的对象。

设计结构分类
1、创建型:比如单例,工厂模式
2、结构型:比如代理模式
3、行为型:观察者模式
单例有三个准则
1、一个类只能创建一个实例
2、该实例必须由这个类进行初始化
3、其他的所有类都能获取到这个对象。

工厂模式:将创建对象实例的方法交给工工厂
1、简单工厂:所有的产品都交给一个工厂类进行初始化
2、工厂模式:对工厂进行抽象,每个工厂类只负责创建一种产品
3、抽象工厂:对产品进行抽象,每个工厂负责创建一类产品。

代理模式:通过代理对象来执行目标对象的方法,并且在代理对象中增强目标对象的方法。
有三种代理方式
1、静态代理:这种代理必须要和目标类实现同一个接口(因为代理对象要实现目标对象所有的方法)
2、JDK动态代理:利用java反射,通过获取到目标类的加载器,接口来创建一个代理对象,它必须要求目标对象实现了某个接口
3、cglib动态代理:不需要目标类实现接口,而是对目标类生成一个子类,并且覆盖其中的方法实现增强

观察者模式:发生在一对多关系的时候可以使用,被观察者能够添加它的观察者,通过调用他的观察者的update方法同时数据发生变化。

java基础
1、异常处理
一个程序在实际运作时发生了不被期望的事件,这就叫异常。发生了异常,能够按照代码预设的异常处理逻辑来处理,就是异常处理机制
不可检查异常:
在编译期不可检查的异常,比如说error和运行时异常,error主要是指的是虚拟机错误,运行期异常包括数组索引越界,强制类型转换错误,这些异常程序员来可以不去处理,编译也能通过,但是这些异常一般是代码写的有问题,应该修改代码。
在编译期可以检查的异常,强制程序员去处理,比如说try catch,throws,否则编译期不会通过 比如sql异常,IO异常,类无法加载等。

反射
反射指的是在系统运行过程中获取一个类的所有属性和方法,并且通过class对象创建一个对象实例,调用它的任意一个方法和实例。根据这个特性的话可以访问一个全部是private修饰的类。

泛型
指的是参数类型化,通过泛型可以使程序设计更加灵活,而且可以在编译器预防错误的类型对象放置在容器中,java程序在编译器会有一个泛型擦除机制,使得java编译器生成的字节码文件不包含有泛型的信息。

接口和抽象类的区别
首先接口更多的是定义了一种规则,因为接口的抽象方法必须全部被实现,抽象类的话更多的是一种模板设计,因为他即可有抽象的方法,也可以有非抽象的方法。
接口没有普通属性,只有静态属性,而且只能用public final static修饰
接口不中没有构造器,但是抽象类中有构造器
接口不能包含初始化块,而抽象方法可以包含初始化块。
一个类只能继承一个类,可以实现多个接口,这样弥补了java不能多继承的不足

面向过程和面向对象的区别:
面向过程就是分析出解决问题所需要的步骤,然后用函数将这些步骤一步一步实现,使用的时候一个一个依次调用。
面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。

怎么理解java是面向对象?
就是说在java的世界里,所有的实体都是对象,面向对象关注的不是具体的实现步骤,而是关注的对象之间的相互联系和相互作用。java通过将多个具有相同属性和行为特征的对象抽象成类,通过调用类的实例和也就是对象的属性和方法来完成我们需要实现的目的。
面向对象的三大特性:
1、封装
2、继承
3、多态

序列化
1、实现serializable接口,这个接口只是个标识接口,然后用objectOutputstream和ObjectInputstream对象对实现serializable接口的对象进行实例化,静态的变量和transient修饰的对象是不能被序列化的。而且默认的序列化机制不仅会序列化对象本身,还会对该对象引用的其他对象也进行序列化

java和C++的区别
首先,在内存管理方面,JVM提供了垃圾回收机制,它会自动在特定的时间去释放内存中不可用的对象,而对于C++来说,需要程序员主动地去释放内存空间,如果操作不当的话,容易出错。
第二、对于基础数据类型来说,java对于每个基础类型的数据都有其特定的长度,与平台无关,而在C++的基础数据类型长度与平台有关,这样对于程序的移植性不够好。
第三、C++可以通过指针直接访问内存,而java把指针封装在JVM内部中,有利于防止操作不当引起程序的不安全。

集合篇:
java集合主要有两个接口,一个是collection接口,一个是map接口,在collection接口下又有两个子接口,分别是list和set,list接口下有三个主要的实现类,vector,arraylist和linkedlist,如果按照线程安全的角度来说,vector是线程安全的,arraylist和linkedlist都是线程不安全的,如果按照数据结构的角度来看,vector和arraylist底层都是数组结构,linkedlist底层是链表结构,根据这个数据结构来看,arraylist和vector对于查找的速度很快,而在中间插入或者删除数据的操作很慢,而arraylist适合于在中间插入和删除数据。vector和arraylist初始容量都为10,但是每次vector是扩容100%,而arraylist是扩容50%。arraylist还有一个特性在于它实现了queue,所以它有一些关于队列的方法。

set接口下有两个实现类,treeset和hashset,mapset底层封装的是一个hashmap,他只使用hashmap的键部分,用一个object对象来填充值部分。所以hashset的键允许为null,而且不能重复。treeset底层封装的是一个treemap,所以它能够对元素进行排序,它底层数据结构是一个红黑树。

map接口下主要有两个实现类,一个是hashmap,一个是treemap,hashmap是一个数组链表结构,元素是以键值对的形式存放的。通过对元素hash之后与数组长度取余找到对应的数组索引,然后将元素插入到对应的数组中去,如果发生hash冲突,hashmap采用的是开链法来解决,通过在相同数组索引位置的地方以链表的形式存放数据。它有下面这些特性:键不可重复,键可以为null,初始大小为16,且一定是2的n次方,每次扩容都是100%,负载因子默认为0.75,线程不安全。
底层实现:
put方法:首先hashmap会对插入元素的键进行一个null值判断,如果为null会将它插入到table【0】的位置,如果不会null,hashmap会对键的hashcode重新计算一个hash,加入高位运算,然后与数据长度进行取余,hashmap采用的是将hash值与数组长度-1进行与操作,这样效率更高,而且这里也是为什么设计数组长度为2的n次方的原因。找到索引位置之后,如果这个索引已经放了元素,元素将会以链表的形式插入到链表的头部。如果没有元素,直接插入即可。插入元素之后,hashmap会去判断数组是否需要扩容,扩容的判断阈值在于负载因子的选择,如果判断需要扩容,那么hashmap会创建一个2倍于元素组的新数组,然后对原数组的元素重新计算索引位置,并将所有元素放入到新数组。

get方法:hashmap通过给定的key参数,对key进行hash算法找到对应的索引位置,然后遍历链表,找到对应的值。

fast-fail机制:
hashmap有一个modecount属性,这个属性是volatile修饰的,而且在每次对hashmap进行增删操作时都会+1,那么当我们用迭代器对map进行遍历的过程中,每次都会去判断这个值是否被修改,如果被修改会抛出异常。

synchronizedMap是collections的一个静态方法,能够将任意的实现了map接口的map作为参数传进去,解决线程同步的问题,它的原理和hashtable一样,都是在调用map的所有方法时,对整个map进行同步。

concurrenthashmap采用的是一个分段锁的结构,采用两次hash的方式,第一次hash会将键映射到对应的segment中,而第二次hash则是映射到segment的不同桶中。它对每个segment都加上不同的锁,所以可以运行多个线程同时对不同的segment进行操作,这样的话性能要更高。

hashset和hashmap的区别
首先他们实现的接口不一样,hashset实现的是set接口,hashmap实现的是map接口。
hashmap存储的是键值对,hashset存储的仅仅是对象。

HashMap和Hashtable的区别
hashmap是线程不安全的,hashtable是线程安全的,hashmap可以接受键为null,而hashtable不允许键为null,hashmap有fail-fast机制,而hashtable由于是线程安全的,所以不存在这个机制。单线程的环境下,hashmap的性能要优于hashtable。

网络篇:
1、三次握手四次挥手
第一次握手,客户端将标志位syn置1,随机生成一个序列J,将该数据包发送给服务端,客户端进入syn_sent状态,等待服务器确认
第二次握手,服务端将SYN和ACK都置1,ack=J+1,随机生成一个序列K,并将该包发送到客户端,进入syn_rcvd状态
第三次握手,客户端收到确认后,回去检查ack请求,检查成功后将回复ack=K+1;
为什么要三次握手

前两次服务端并不确定客户端能否收到自己的消息,依然不可靠。

三次握手中accept函数处于第几次?
accept函数是用来给这次连接分配资源的,只有建立连接,才会调用accept函数

第一次挥手,客户端发送一个FIN,请求关闭连接,进入FIN_wait。
第二次挥手,服务端收到fin后,回复一个ACK给客户端,此时服务端会进去close_wait状态,它在等待自己所有的数据都发送完。
第三次挥手,服务器发送一个fin,请求关闭连接,进入Last_ack状态。
第四次挥手,客户端收到fin后,进入time_wait状态,接着发送一个ack给服务器,服务器进入到closed状态。
为什么要四次挥手。
因为TCP连接是全双工的,所有每个方向都必须要单独关闭连接,这个原则在于当一方发送fin,只是表明自己的数据发完了,但是还是会接收另一方的数据,所以需要通信双发都需要主动发送一次fin请求,回复一次fin请求。

为什么客户端的time-wait状态要等2MSL时间?
为了保证客户端发送的最后一个ack报文能让服务器收到,同时也能使本次连接所有的报文都消失,不影响下次连接。

七层模型:
从上到下依次是应用层,表示层,会话层,运输层、网络层、数据链路层、物理层
1、应用层是应用程序与网络直接的接口,直接向用户提供服务。FTP/HTTP/DNS
2、表示层主要是负责编码,数据格式转换,加密解密等
3、会话层定义了如何开始、控制、结束一个会话。
4、运输层定义了一些传输数据的协议和端口号,比如,tcp协议,udp协议
5、网络层将数据封装为包,负责数据的路由和交换,主要有ip协议
6、数据链路层,将数据封装成帧,在不可靠的物理介质上提供可靠传输
7、物理层,传输的是bit流。

TCP/UDP的区别
TCP是一种面向连接,可靠的连接协议
UDP是一种面向无连接,不可靠的协议。
面向连接具体体现在TCP的三次握手机制,UDP不需要建立这种连接过程。
所以TCP传输速率较慢,而UDP的传输速率较快。
TCP面向流传输,tcp先发送数据到缓冲区,接收端只要缓冲区上有数据就可以读取,可以一次读取多个数据包,而UDP是面向数据包传输,一次只能读取一个数据包,数据包之间相互独立。

TCP拥塞控制
1、慢启动:
就是一开始将TCP拥塞窗口初始一个数据包的大小,每收到一个ack确认的话,拥塞窗口就会以指数即的大小增加。
2、拥塞避免
为数据包的大小设定一个阈值,超过这个阈值就会进入到拥塞避免状态,在这个状态,拥塞窗口只会每次加一个数据包的大小,如果在这个状态下TCP重传了一个报文段,那么TCP就会调整阈值的大小,并且将拥塞窗口的大小重新设为一个数据包的大小,重新进入慢启动状态。

TCP可靠性的四大手段
1、顺序编号
2、确认机制
3、超时重传
4、校验信息

什么是http协议?
超文本传输协议,对客户端和服务端之间数据传输的格式规范。
无状态协议。通过cookies和session会话保存。
请求报文组成:
1、请求行 :方法、url、版本
2、请求首部字段
3、请求内容实体
响应报文
1、状态行:包含HTTP版本,状态码,状态码原因
2、响应首部字段
3、响应内容实体
请求方式:
get post put head delete options
get和post的区别
1、get重点从服务器上获取资源,post重点在向服务器发送数据。
2、get传输数据通过url请求,所以传输量小,不安全。
3、post可以传输大量数据,所以上传文件只能有post。

502 bad gateway 顾名思义 网关错误 后端服务器tomcat没有起来,应用服务的问题;

504 gateway time-out 顾名思义 网关超时 一般计算机中的超时就是配置错了,此处一般指nginx做反向代理服务器时,所连接的服务器tomcat无响应导致的。

spring
spring有两大特性,第一是IOC容器,第二是AOP,IOC的意思是对象的控制权发生了变化,原来是由程序控制对象的生命周期,而现在对象的生命周期都是由IOC容器控制,在你需要使用到对象时,IOC容器会通过依赖注入的方式为你的对象属性赋值。
AOP是面向接口编程,意思就是将那些与业务逻辑无关的但是又是大多数业务逻辑通用的代码抽取出来,提高了代码的复用性。

ICO实现的原理;
springIOC实现原理主要分两步,首先是IOC容器的初始化,spring中有一个beanfactor接口,这个接口定义了一些操作bean的规则,具体怎么创建交给它的实现类,比如说常用的classpathXMLapplicationContext,它能够加载XML文件,并且将XML抽象成一个resource对象,通过对这个resource对象进行解析,找到它的bean节点,将所有的bean封装成一个beandifinition对象,这个对象保存了一个bean的所有信息,包括他的属性,依赖等。spring内部有一个map的数据结构,用于存储bean的信息,其中它的键是bean的名称,值是beandifinition。
接着,我们可以通过getbean方法将我们的依赖注入到我们的属性当中去,spring
采用的反射的方式,首先,它会通过反射的方式创建一个对象,然后通过bean的名称找到对应的beandifinition,然后对beandifinition解析,将里面的属性赋值到对象中去。

spring依赖注入的方式
1、通过构造器
2、通过set方法
3、通过注解的方式

AOP实现原理:
AOP实现原理是通过动态代理的方式实现的,spring有两种动态代理的方法,一种是JDK动态代理,一种是cglib动态代理,spring的默认策略是判断目标类是否实现了一个接口,如果实现了一个接口,那么会采用jdk动态代理,如果没有实现接口,那么就会采用cglib动态代理。这么做的原理在于这两种代理方式的实现机制不一样。

springMVC
springMVC是在spring的原理上搭建的专门面向WEB应用开发的一个框架,它包括了三个部分,控制、模型、视图
控制部分用来接收用户请求改变模型调整视图的显示。
视图部分用来负责应用的展示,
模型部分封装应用对象和业务逻辑

优点:
MVC设计模式的优点主要体现在可维护性,可重用性,可移植性。
比如model层和view层以及controller层是分离的,那么我如果想更改我的数据库,只需要修改model层部分就行了,只要返回同样格式的数据。
还有就是一个modle层可以对应多个view层,那么只需要一个model就能展示不同的界面 。

缺点:
对应简单的应用程序,MVC设计模式反而显得系统结构复杂,冗余
MVC虽然实现了各层的分离,但是层与层之间的关联性还是太强了,可重用性不够高

改进
对于web开发来说,可以将controller层中的视图处理部分的业务进行解耦出来,将视图部分交给前端,在后台的话只需要返回统一的json格式的数据。

它把servlet封装起来,并且提供了很多组件,使得WEB应用开发成本降低。
它的dispatchservlet的基类就是httpservlet,所以它对于http请求处理非常方便。
它的注解包括 @controller @autowire @requestMapping @requestParam
servlet生命周期主要是包括 init 、service、destroy。在第一个请求发送时,web容器会创建一个servlet,并对servlet进行初始化,servlet
SpringMVC处理流程,首先一个用户请求会传入到dispatchServlet,dispatchServlet调用handlermappering会通过传入的URL映射到相应的handle,contral方法会把处理好的结果生成一个ModelandView对象返回给dispatchServlet,dispatchServlet将结果交给视图解析器解析成视图,最后进行视图渲染将结果返回给用户。

mybatis
mybatis是一个面向持久层开发的一个框架
1、对JDBC操作数据库的过程进行了封装。
2、使用数据库连接池管理连接,避免了频繁创建关闭连接,浪费资源。
3、在mybatis中可以通过配置.xml文件来配置数据库连接池、通过xml管理sql语句,让java代码和sql语句分离,代码更容易维护
4、可以自动将结果集封装成对象,通过statement的resultType定义输出的类型。
原理:mybatis通过配置文件创建一个sqlsessionFactory,这个sqlsessionFactor根据xml配置文件或者java中的注解获取到sqlSession,sqlsession包含了执行sql语句的所有方法,可以通过sqlsession直接运行映射的sql语句。完成事务的提交工作。
工作流程:具体表现在一个Mapper接口,通过这个接口的全类名定位到一个配置文件,接口的方法名定位到一个statement,再执行相应的sql语句。这个过程是通过动态代理来实现的
jdbc原理:1、通过反射加载数据库驱动、2、通过url、password、username创建连接、3创建一个statement 4、执行sql语句。

1、#{}是预编译 KaTeX parse error: Expected 'EOF', got '#' at position 20: …符串替换 mybatis在处理#̲{}时,会将sql中的#{}替…{}时被把${}替换成变量的值
2、模糊查询like怎么写
在java代码中添加sql通配符
3、如何传递多个参数
使用@param注解
4、Mybatis有哪些标签
select delete update insert resultMap

redis
redis是一个基于内存的高性能键值数据库,数据都是放在内存中读写性能出色,并且支持原子性操作,
数据类型:支持String list set sortedlist hash
nginx
Niginx服务器:可以用来做反向代理服务器
客户端发送的http请求会先到达代理服务器,代理服务器再按照一定的负载均衡策略发到相应的代理服务器上
负载均衡:根据服务器的负载能力不同,分配给他们不同的工作量,让他们最大程度地发挥作用
负载均衡策略 轮询 加权轮询 urlhash iphash fair

tomcat

servlet

数据结构篇
1、外排
当排序的文件无法直接一次装入内存时,需要用到。
就是先将大文件分成段比如20G 分为 10G和10G,然后将两段先排好序,然后将内存分为三段,两段做为输入。一段作为输出,通过归并排序的方式将结果输出
2、

leetcode 刷题
二分法查找次数,100个数最多查找多少次?
1、两数之和
遍历一次,用hashset判断是否存在
2、两数相加
用一个while来判断,记得有一个进位的标志
3、无重复的最长子串
用一个hashset来判断是否包含,有两个指针,一个指针代表头,一个指针代表尾。
想象如果不是程序,你该怎么找最长子串
4、最长的回文子串
用db来判断
DP【I】【J】 表示从第I个元素到第J个元素是否是回文子串

5、翻转整数
一直除10,直到为0。
6、字符串转为整数
注意字符串前面为空,还有+,-标记,如果都满足了,那么就可以遍历求结果了。注意-32次方末尾是-8, 32次方末尾是7.

7、回文整数。
可以把数转为数组,用两个指针遍历,直到右边小于左边。

8、将字符串转为整数
首先去空格
用一个指针
然后判断第一个字符是否为数或者为+号-号还有是否超出
然后遍历后面的数输出

8、最长的字符串前缀
可以先对字符串排序
然后遍历后面的字符串,如果不等跳出,
如果相等就遍历下一个字符

9、三数相加
先排序,然后每次固定一个数,
对后面的数用两个指针一前一后遍历
注意去重

10、四数相加,和上面的思路一样但是每次固定两个数

11、三数相加最接近

也是固定一个数,然后后面的数用两个指针来遍历

12、移除链表中的倒数第n个节点

找到倒数第n个节点前的指针,然后用它指向节点后面的节点,快慢指针

13、判断括号是否匹配
用stack来做,

14、将两个有序的列表排序
直接用while来对两个链表进行遍历,新建一个链表来装

15、对n个有序链表进行再排序
可以通过一个优先队列来做

16、将链表进行成对翻转
要用三个指针,pre cur next

17将链表进行n对翻转
思路是用n+1个指针

18、数组去重
一直遍历过去
用两个索引,一个遍历,一个来保存不重复的数的位置

19

n个数取topk

判断一个链表是否有环

两个有序数组合并为一个有序数组

项目:
1、说说最近的项目
2、如何实现session共享的,用redis如何实现?
3、项目中最重要的地方。
4、实习期间的项目聊了很久,开发的细节,用到的框架,遇到的难题,如何做code review
5、jedis怎么操作redis
6、高并发怎么解决

做项目最大困难

主要是刚开始做的时候技术薄弱,经验不足,比如对于一些框架的理解,对于一些新技术的实现刚开始都处于一个一知半解的状态,从书本到实际项目的开发有一个很漫长的过渡过程,可能需要不断地去反思,这个我就会多多去看看别人的博客,看看别人怎么处理这些问题,从中学习到一些对自己有用的地方。

找回密码怎么做的?
分两步来做,首先通过用户名找到问题,将问题和问题的答案提交,通过UUID生成一个token保存在cache中,并且设置一个持续时间,通过这个token去修改密码

为什么分类的时候用子类和父类
如何通过父类去搜索它的所有子类?
用一个递归来做的,每层递归都会将该节点加入到一个set容器中,并且遍历它的所有子节点。递归地结束就是所有的子节点都加入到set容器中。
上传文件怎么做的?
我这里用了springMVC上传,首先获取文件的上传路径,用的是tomcat服务器的路径,在该路径下创建一个新文件,通过file.tansferto将文件发送到tomcat上,再将文件上传到ftb服务器上。
怎么连接的FTP服务器?
配置FTP相关参数
将文件用fileinputstream包装,用storefile来存储服务器。
怎么通过关键字和种类ID搜索商品?
首先判断他的类别是否为空,如果为空就不操作,如果不为空就返回所有的子类别,再判断keyword,将结果进行排序输出,
有个bug:就是我得先新建个容器来存储类别,但是我类别ID为空直接就进入到keyword判断了,没有对这个容器处理,在执行sql的时候就说语法错误。
后来才知道原来新建容器的操作也相当于赋值了,只是里面的元素不存在,所以在sql中用in就报错了。

购物车模块怎么设计的?
首先封装了两个VO对象,一个用来显示购物车里面的商品列表,以及总价和一个用来显示购物车某个商品的详细信息。由于我的购物车只能实现登录状态的增删改查,所以每次要获取session然后进行一个权限判断,然后就是简单的增删改查,通过商品表和购物车表为对象的属性赋值,难点在于每次对购物车操作都要进行一次库存判断。(就是从购物车表中找到某个ID下的所有的商品,然后对每个商品进行库存判断,最后计算已勾选商品的总价)。
全选怎么实现的?
一个sql update cart set checked =1 where uerid=userid and prodactid=productid;

订单模块怎么设计的?

全局异常处理怎么设计的?
在原有的设计中,异常日志是存储在tomcat服务器下,所以一旦异常发生,异常信息就会打到前台,这样会暴露很多项目细节,不利于安全
在这个背景下,我就通过springMVC实现了一个全局异常处理。主要是通过实现handleExcetionresolver接口,在这个接口的resolveExcetion方法中创建了一个json格式的modelandView对象,对这个对象进行格式包装。然后返回这个对象。

日志的作用
用的SLF4J和logback
能够显示程序当前的运行状态,更有利于出问题后的排查。

springMVC拦截器进行权限验证?
主要是在对业务代码分析的时候觉得有很多重复的用户登录权限验证代码,太过于冗余,而且对于以后的功能添加也麻烦,所以想能够把这部分重复的逻辑抽离出来,就利用了springMVC的拦截器对这部分进行处理
具体过程,在XML配置文件中注册一个拦截器,创建一个拦截器实现HandlerInterceptor接口,重写它的prehandle方法,主要是从cookies中拿到token,通过token从redis从拿到对象,对对象进行权限判定。在这里的话考虑到用户在进行登录操作时是拿不到token的,无法登录,所以在拦截器中对这种特殊的路径做了处理,主要是通过获取请求方法的方法名和类名进行判断是否是需要拦截的方法。具体是通过handle来做的。

springMVC拦截器原理:
springMVC拦截器接口有三个方法,prehandle posthandle aftercompletion、其中prehandle是在到达controller方法前生效的,它返回一个bool型,若为true,请求继续往下进行,若为false,请求直接结束。posthandle是controller 方法返回一个modleandview对象后生效,我们可以在这里对modle对象操作,aftercompletion主要用于对资源的清理工作。

如何实现session共享
因为我搭了一个分布式集群环境,通过Niginx作为一个前端的反向代理服务器,tomcat作为应用服务器,在这样的分布式环境下,用户每次可能进入的是不同的服务器,返回不同的sessionID,就无法通过sessionID来存储用户的登录状态,所以在这个背景下,我用redis+jkson+cookies来实现了同域名下单点登录的功能,主要是通过redis来将用户信息以键值对的形式封装到redis中,键是sessionID,值为反序列化化之后的对象,然后通过response返回一个sessionID存在cookies中,这样的话所有服务器就能共享这个sessionID。实现redis的方式是搭建了一个redis连接池,封装redis内部的get、set等方法。在之后又考虑到session如果永久保存的话,又会出现安全性问题,所以通过redis的expire 设置token的有效期。
(可以说为什么不进行session重置可以想一下,然后说在每次发送请求前判断用户是否为空)

1、session和cookies的区别?
session主要存放在浏览器上,session数据放在服务器上
cookies保存的数据大小有限
sessions存在服务器会增加服务器的压力

所以session一般适合存放登录信息
其他信息如果需要保留的话可以放在cookie中。
2、为什么用redis?
redis是一个基于内存的高性能键值数据库,数据都是放在内存中读写性能出色,并且支持原子性操作,
数据类型:支持String list set sortedlist hash

Niginx服务器:可以用来做反向代理服务器
客户端发送的http请求会先到达代理服务器,代理服务器再按照一定的负载均衡策略发到相应的代理服务器上
负载均衡:根据服务器的负载能力不同,分配给他们不同的工作量,让他们最大程度地发挥作用
负载均衡策略 轮询 加权轮询 urlhash iphash fair

分布式锁怎么实现的?
通过springSchedule +redis
具体怎么实现的 ,通过Schedule注解,设置执行时间,我在这里主要设置每整分钟进行一次订单查询,对满足一个小时还未付款的订单进行删除。但是在分布式环境下,每个服务器每分钟都会进行一次这个操作,对于服务器压力以及安全问题都会有影响,所以考虑用redis分布式锁进行一个同步处理。原理就是服务器进行关单任务之前首先要去redis里面拿到锁的键,通过redis的setnext可以保证如果redis里面存在这把锁,别的服务器就无法拿到锁,也就无法进行关单操作。同时也对这个锁设置了持续时间,这样能保证不会发生死锁。(如果还没来及设置时间服务器挂了怎么办 ? 在获取锁的时间先判定是否超时)。

死锁的必要条件:
互斥
不可剥夺
循环等待
请求保持资源

做项目做大的困难

项目有什么需要改进的?
没有考虑安全性的问题。
在业务逻辑层的分层,我这个业务逻辑层耦合性太高,只适合单人开发,而一个大型网站的功能多变,业务逻辑也要复杂很多,需要团队合作,我后来看web架构的书时看别人都是用的分布式架构,按照功能模块划分的,对其他模块开放接口,这样更有利于团队开发。不过这样又牵扯了远程通信的问题,好像都是用rpc来做的,我也算以后自己尝试打一个RPC框架。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值