前言
- 大家好,这里是IT学习日记,相信大家对今年IT的行情应该也有所了解了,从大厂到小厂,各种裁员消息。公司裁员我们无法决定,我们能做的就是不断提升自己,提前准备。
- 本系列文章主要分享了之前博主真实面试中遇到的一些问题,希望能够帮助准备就业或者跳槽的朋友。
后端基础知识篇
一: Java中的队列和它们的区别
1、定义
队列是一个先进先出的数据结构,Queue是JDK1.5引入的。
2、用途
将大量的请求转换成服务器能够处理的队列请求,可以保证有序性,如秒杀系统
3、分类
第一类: 没有实现阻塞接口,但是实现了Queue和AbstractQueue接口
(1)、LinkedList:: 实现了Deque接口,双向有序队列
(2)、PriorityQueue: 有限队列,维护一个有序列表,可以自然排序也可以传递Comparator构造函数实现自定义排序
(3)、ConcurrentLinkedQueue: 基于链表、线程安全的队列,并发访问不需要同步,它是从尾部添加元素从头部删除元素,对公共的集合访问效率做得很不错,添加删除O(1),查询O(n)。
第二类: 实现了阻塞接口,concurrent包中引入了BlockQueue接口和五个阻塞队列,他们不是操作就立即向队列中添加或者删除元素,而是线程执行阻塞操作,直至队列有空间可以添加或者有元素可以删除。
(1)、ArrayBlockingQueue: 数组型有界队列
(2)LinkedBlockingQueue: 链接节点,支持可选的有界队列
(3)PriorityBlockingQueue: 一个优先级堆,支持的无界队列
(4)DelayQueue: 一个优先级堆,支持基于时间的无界队列
(5)SynchronousQueue: 可以用来在线程间安全的交换单一元素
三: 关于阻塞队列的操作
1、add、remove、element: 操作一个已满或者为空的队列时,如果操作的集合为空或者已满时,remove或者add则会抛出异常。
2、Offer、poll、peek: 在无法完成操作时,只会返回true或者null,不会抛出异常
3、Take操作: 队列为空的时候阻塞。put操作:队列满时阻塞
二: 简单描述下JVM内存模型以及它们的作用
注: 该图是jdk1.7时对应的JVM内存模型
1、程序计数器
用来标识下一条需要执行指令的位置,它是线程私有的。
2、虚拟机栈
线程私有,声明周期和线程相同,它描述了Java方法执行的内存模型。每个方法在执行的同时会创建一个栈帧(Stack Frame),用于存储局部变量表,动态链接等信息。从调用到调用完成对应的是一个栈帧从虚拟机栈入栈到出栈的过程。
局部变量表存放了编译器可知的基本类型、独享引用。Long、Double占用了2个局部变量控件slot,故为非原子性,它在编译时便完成了内存的分配,运行时不会进行修改。
当申请的栈深度大于虚拟机允许的宽度时会抛出:StackoverFlowError,如虚拟机自动拓展、但申请不到足够的内存时,则抛出OutofMemeryError
3、本地方法栈
线程私有、为本地方法(native)服务,如:hotspot是将它和虚拟机栈合并在一起。可能抛出的错误有:StackoverFlowError和OutofMemeryError。
4、堆
线程共享、JVM中占用内存最大的一块区域,VM启动时创建,主要目的是: 存放对象实例。当实例没有申请到足够的空间时,堆大小也无法拓展时,会抛出OutofMemeryError错误,它也是GC主要进行收集的地方。(jdk1.8后,常量池也是存放在堆中,因为永久代废除了)
5、方法区
线程共享用于存储被虚拟机加载的类信息、变量、静态变量既即时编译的代码,JVM规范是将它和堆进行分开。永久代和元空间其实都是方法区的一种实现。
三: 常见的GC算法和它们的差异
知识小贴士:
Hotspot将堆划分为新生代(Young Generation)和年老代(Tetured Generation),新生代又分为Eden去和Survivor区,它们的比例是:8:2。所有新建的对象存放在新生代,其中survivor区又划分为from区和to区,比例是1:1。
1、标记-清除(Mark Sweap)
主要的步骤: 标记回收对象,回收被标记的对象
标记方式有两种: 1、引用计数算法(Reference Counting), 2、可达性分析法(Rechability Analysis)
缺点:
回收后会出现大量非连续内存,且需要扫描两次内存,效率低
2、复制算法(coping)
思路: 将内存划分为等大的两块区域,,一次只用一块,用完将存活的对象复制到另一块,然后清空自己。
特点:
解决了标记-清楚算法效率和内存碎片问题,但是需要浪费一块内存空间,利用率不高,主要是用于回收新生代(因为新生代的对象基本是”朝生暮死”,存活的时间很短)
3、标记-整理(Mark - Compact)算法
思路:
从根节点开始对所有可达对象进行一次标记、之后,不是简单的清除未标记的对象,而是将所有存活的对象压缩到内存的一端,之后,清理边界外的所有空间。
特点:
避免了内存碎片的产生、同时又不需要两块相同的内存空间,相对来说性价比较高。多适用于老年代的区域回收,因为老年代的对象存活率大。
4、分代收集算法
分代收集算法是目前虚拟机使用的回收算法。它解决了标记清除算法不使用于老年代的问题。在不同年代中使用不同的收集方式,新生代存活率低,可以使用复制算法。老年代对象存活率高,没有额外的控件对它进行分配担保,可以使用标记清除或者标记整理算法。
四: 反射的优缺点
优点:
体现了灵活性、降低了类之间的耦合性、体现了多态的作用。
缺点:
性能存在一定问题。反射本身算是一种解释操作,告诉JVM我们想要做什么并让它满足我们的要求,它会比直接执行操作更慢一些。
五: 多继承的弊端和解决方案
弊端: 如果有多个父类,有相同的功能时,子类调用,会产生不确定性,所以JAVA中类的只有单继承。
解决: 通过”实现”解决,因为接口中的功能都是未实现的,需要子类明确。接口的出现避免了单继承的局限性,所以,一般是父类中定义的事物的基本功能,接口定义的是事物的拓展功能。
六: 抽象类和接口的区别
一: 相同点
都不能直接实例对象,都可以包含抽象方法
二: 不同点
1、接口中除了静态方法和默认方法外只能存在抽象方法,但是抽象类中既可以存在抽象方法也可以存在非抽像方法。
2、接口可以多实现,但是类只能单继承
3、接口中只能定义常量,抽象类中可以定义常量和变量
4、接口中没有构造函数,抽象类中有构造函数
七: 继承、封装、多态的作用
继承: 子类自动拥有父类所有可以继承的属性和方法
封装: 隐藏对象的属性和方法的细节实现,提供一些公共访问的方式
多态: 配合继承与方法的重写提高代码的重用性和拓展性,如无方法重写,则多态同样无意义。
八: ClassNotFoundException知道?遇到的场景是什么?如何解决?
场景:
1、调用Classs.forName(“类的全限路径”)加载类时
2、ClassLoader.findSystemClass方法调用时
3、ClassLoader.loadClass方法时
解决:
检查类名是否正确或者是否真的存在需要加载的类
九: NoClassDefError错误的常见场景
场景:
1、类依赖的class或者jar包不存在
2、类文件存在,但是存在于不同的域中
3、大小写问题,javac编译时是无视大小写的,可能编译出的class文件和想要的不一样
十: hashcode和equals方法的特点
1、重写equals方法则必须同时重写hashCode方法
2、如果两个对象的equals方法相等,则两个对象的hashCode也一定相等
3、如果两个对象的hashCode相等,那么两个对象的equals方法不一定相等,只能说明两个对象在散列存储结构中,存放在相同的一个位置
十一: hashcode的作用
用于快速定位对象在散列表的位置。在JVM中new一个对象时,会将这个对象丢到Hash表中,下次再进行对象的比较或者取该丢向时,根据该对象的hashCode从hash表中获取,目的,提高获取对象的效率。
若HashCode相同则再去调用equals方法,所以hashCode是用于查找的,而equals方法是用于判断两个对象是否相等。
十二: 为什么需要重写hashcode方法
HashMap或者HashSet中如果不重写会导致存对象进去了,但是取对象的时候却取不到正确的。重写hashCode时为了保证相同的对象有相同的hashCode。
十二: java.util包和java.awt包的区别
Java,util包中存放的是常用的工具包,如集合。
Java.awt包存放的是: 包含用于创建用户界面和绘制图形图像的所有类。
十三: 项目中常用的包有哪些
Java.lang: 提供利用 Java 编程语言进行程序设计的基础类。
java.util: 包含集合框架、遗留的 collection 类、事件模型、日期和时间设施、国际化和各种实用工具类
java.io.*: Java的核心库java.io提供了全面的IO接口。包括:文件读写、标准设备输出等。
java.net.*: 包含与网络相关的一些类库。
java.sql.*: 包含了与数据库相关的一些类库。
此外还有如果java.util.concurrent等与线程安全相关的包。
十四: fail-fast和fail-safe的区别
并发修改: 当一个线程或者多个线程在遍历集合时,另外的线程对该集合进行了内容变动(添加、删除、修改)。
Fail-fast-快速失败: java.util下的集合都是这种模式
在遍历集合的时候,如果进行了并发修改,会抛出concurrentModificationException异常,因为它所有的操作都是在原来的集合中进行的,在遍历的时候判断是否存在下一个元素时会进行:expectModcount和modCount的判断,如果不相等,则直接抛出异常。
Fail-Safe-安全失败: java.util.concurrent下的集合都是这种模式
属于这种机制的集合,任何对集合的操作(添加、修改、删除)都会在当前集合的复制出来的集合上操作,不会直接在当前的集合上进行,所以不会抛出异常,但是会存在以下的问题:
1、需要复制集合,产生无效对象,开销较大
2、无法保证读取的数据是目前原始数据结构中的最新数据
十五: Bean工厂和Appcation Context的区别
Bean工厂:
是Spring的原始接口,Bean工厂实现的容器特点是:每次获取对象的时候才会去创建对象,而不是在容器启动的时候去加载所有的对象。这是因为早期机器的容量和资源都是比较稀缺的,如果在启动时一次加载完所有的对象,资源可能全被占用,程序无法正常运行。
Bean工厂的优缺点:
优点:
1、项目启动速度快,且资源占用少
缺点:
因为对象是在获取的时候才进行创建,所以无法在启动时检查出对象配置是否存在问题,需要在获取的时候才知道.
ApplicationContext:
它是继承了Bean工厂,同时拓展了许多功能,每次容器启动的时候会创建所有的对象。
优点:
可以及时发现对象配置的问题,因为在容器启动的时候会创建所有的对象。
缺点:
项目启动速度较慢,且资源占用较多。
十六: 垂直拆分和水平拆分
垂直拆分:
把一个数据库中不同的业务单元的数据分配到不同的数据库中,如:用户信息存存储在库1,订单信息存储在库2。
水平拆分:
根据一定的规则,将同一个业务单元的数据分配存储在不同的数据库中,防止单表的数据量太大,导致查询速率降低。
十七: 保证线程安全的方式
1、添加锁,如synchronized,Reentractlock
2、使用ThreadLocal线程副本,每个线程独享一份数据,互不干扰
3、使用阻塞队列,线性执行任务
4、使用JDK提供的原子类如java.concurrent.atomic包下的类
十八: 重写equals方法需要注意的事项
注: java规范中要求重写equals方法需要具有以下的特性:
1、自反性: 针对非空的x,使用x.equals(x)应该返回true
2、对称性: 针对x,y,如果x.equals(y)为true,那么y.equals(x)也应该为true
3、传递性: 如有x,y,z,存在x.equals(y)和y.equals(z)都为True,那么x.equals(z)也应该为true
4、一致性: 如果比较对象未发生改变,则反复调用equals方法应该返回同样的结果
5、对于任意的非空x,x.equals(null)应该返回false
十九: JAVA中哪些属性不能被序列化
1、被static修饰的属性,它是属于类级别的,序列化针对的是对象。序列化保存的是对象的状态,静态变量是以类的状态,因此序列化并不保存静态变量。这里的不能序列化的意思,是序列化信息中不包含这个静态成员域
2、被transient修饰的属性
二十: JRE和JDK的区别
JRE:
核心的内容是JVM及相关的核心类库及支持文件。
JDK:
除了包含JRE外,还包含了JAVA工具(编译器、调试器等)和java的基础类库,开发者可以进行: 开发 -> 编译 -> 执行java应用程序。
小结
不积跬步,无以至千里;不积小流,无以成江海。今天播种努力的种子,总会有一天发芽!