Java问题总结
在网上看到一篇专门汇总面试题的文章,于是一时兴起,就把其内容整理了一番,因为原文问题和答案不完整,且混在一起,于是有了这篇Java问题总结
一、Java基础
-
为什么java可以一次编译,到处运行?
- 答:一处编译到处运行,主要依赖于不同平台上统一的JVM运行程序;我们知道不同的平台拥有者不同的架构,不同的执行标准等,使得不管什么情况下,要使得一套代码在不同的操作系统上运行遇上了阻碍。然而Sun公司的工程师们给不同的平台都开发了一个模拟软件,这个软件是用来运行Java程序的,这就是JVM,JVM并不跨平台,但是使得在JVM上运行的Java程序变得通用;即JVM接收的程序是跨平台的,本身不跨平台,把接收的Java程序经过处理,调用宿主机的硬件完成运算;
-
StringBuffer和StringBuilder区别?
- 答:StringBuffer和StringBuilder都是可变字符串,即可改变原有字符串的内容,但是区别在于:StringBuffer是线程安全的,其中的方法都添加了Synchronized关键字,所以其执行的效率相对较低;而StringBuilder相对于StringBuffer少了Synchronized关键字,所以是线程不安全的,故其执行的效率相对较高;
-
String为什么是不可变的?
- 答:因为封装String的本质是一个使用private final修饰的字符数组,所以正常情况下,这个字符数组是不可变的,但是我们可以使用反射的方式对元字符串进行修改,代码如下,不过一般情况下不会这么使用;
public static void testReflection() throws Exception { String s = "Hello World"; System.out.println("s = " + s); Field valueField = String.class.getDeclaredField("value"); valueField.setAccessible(true); char[] value = (char[]) valueField.get(s); value[5] = '_'; System.out.println("s = " + s); }
- 答:因为封装String的本质是一个使用private final修饰的字符数组,所以正常情况下,这个字符数组是不可变的,但是我们可以使用反射的方式对元字符串进行修改,代码如下,不过一般情况下不会这么使用;
-
StringBuilder线程不安全体现在哪?
- 答:在多线程环境下,StringBuilder对象常常会使得多个线程同时操作,造成一个结果覆盖另一个结果的情况,从而造成最后结果错误!
-
ArrayList和LinkedList区别?
- 答:①数据结构的区别:ArrayList是基于数组的数据结构,而LinkedList是基于链表的数据结构;②性能的区别:查询和更新数据方面:ArrayList优于LinkedList,因为ArrayList可以根据对象的地址和索引直接计算出要查找或者要更新的对象的地址,而LinkedList需要遍历所有的节点,相比之下肯定速度上有影响;新增和删除数据方面,这两个数据结构不一定,因为对于ArrayList而言会新增可能触发扩容的问题和利用System.arraycopy方法进行数据的复制移位,对于LinkedList需要遍历查找节点,找到后进行节点的衔接或者移除;
-
ArrayList有初始容量吗?容量不够怎么办 ,扩容为多大?
- ArrayList空参构造过程:
- 初始情况:elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; size = 0;
- 当向数组中添加第一个元素时,通过add(E e)方法中调用的ensureCapacityInternal(size + 1)方法,即ensureCapacityInternal(1);
- 在ensureCapacityInternal(int minCapacity)方法中,可得的minCapacity=DEFAULT_CAPACITY=10,然后再调用ensureExplicitCapacity(minCapacity)方法,即ensureExplicitCapacity(10);
- 在ensureExplicitCapacity(minCapacity)方法中调用grow(minCapacity)方法,即grow(10),此处为真正具体的数组扩容的算法,在此方法中,通过elementData = Arrays.copyOf(elementData, 10)具体实现了elementData数组初始容量为10的构造。
- 在JDK1.7和1.8中,如果通过无参构造的话,初始数组容量为0,当真正对数组进行添加时,才真正分配容量。每次按照1.5倍(位运算)的比率通过copeOf的方式扩容。在JDK1.6中,如果通过无参构造的话,初始数组容量为10.每次通过copeOf的方式扩容后容量为原来的1.5倍加1;
- ArrayList空参构造过程:
-
如果Arraylist当前容量是10,且有9个数据,那么是添加第10个数据时扩容还是第11个?扩容怎么实现的,为什么采用复制数组的方式而不是往后直接添加数据?
- 在现在常用的JDK8中,扩容是在添加第11个元素的时候才会触发的;扩容步骤:①调用ArrayList的add方法;②add方法中第一行就调用了容量检查的方法ensureCapacityInternal,参数是要添加一个元素,这个数组的最小容量大小(即:已添加的元素个数+1);③这个方法内部先调用方法calculateCapacity确定最小容量,其实就是如果存储元素的数组是空的,就获取默认容量大小和传递进来的最小容量取其大者为确定的容量最小值,否则返回传递进来的最小容量值;④然后把结果进一步传递到ensureExplicitCapacity中来确定是否扩容,机制:如果传递进来的容量最小值大于目前数组的容量,就会调用grow方法进行扩容,并传递参数最小容量,否则不会调用grow方法;⑤扩容的机制:
int newCapacity = oldCapacity + (oldCapacity >> 1);
,即每一次让梁都是扩展到之前容量的1.5倍;当然下面还进行了一些校验,这里就不说了;
- 在现在常用的JDK8中,扩容是在添加第11个元素的时候才会触发的;扩容步骤:①调用ArrayList的add方法;②add方法中第一行就调用了容量检查的方法ensureCapacityInternal,参数是要添加一个元素,这个数组的最小容量大小(即:已添加的元素个数+1);③这个方法内部先调用方法calculateCapacity确定最小容量,其实就是如果存储元素的数组是空的,就获取默认容量大小和传递进来的最小容量取其大者为确定的容量最小值,否则返回传递进来的最小容量值;④然后把结果进一步传递到ensureExplicitCapacity中来确定是否扩容,机制:如果传递进来的容量最小值大于目前数组的容量,就会调用grow方法进行扩容,并传递参数最小容量,否则不会调用grow方法;⑤扩容的机制:
- 详述TreeMap,TreeSet,HashSet,HashMap,HashTable,ConcurrentHashMap的实现原理;
- HashMap:数组+链表/红黑树,数组长度少于MIN_TREEIFY_CAPACITY是不优先转红黑树的,而是优先进行扩容;
- 说下hashmap put的过程;
- key的hashcode高16位与低16位异或运算得到新的hash,为了让数据(特别是在当前容量不大的情况下)散列更均匀,然后把异或计算出的新hash与此时的hashmap容量-1做&运算,得到插入下标。为什么要做&运算呢?因为二进制运算速度快,还可以取模。之后如果下标位数组无数据则直接插入,如果有数据则链表往下逐个进行hash比较,如果产生hash碰撞再进行==或者equals比较key是否一样,一样则覆盖原数据,否则添加到链表后面。
- 说说你对hash的理解。HashSet是怎么去重的?
- 发生hash冲突怎么办?
- JDK1.7和JDK1.8的HashMap有什么区别?
- 抽象类接口区别有哪些?接口中可以定义静态公共方法吗?接口种可以定义方法的实现吗?
- 为什么重写equals方法最好也得重写hashcode?
- 序列化作用有哪些?
- 定时器的实现方式有哪些?
- Integer缓存是什么?有什么作用?
- Comparable接口和Comparator接口实现比较的区别和用法,Arrays静态类如何实现排序的?
- http和https区别(https用了什么证书,怎样在网络传输中实现,我当时回答了osi七层,TCP udp ,三次握手,四次断开)
- 在Java中>>、>>>的区别
- >>代表算术移位也就是用符号位填充最高位。
- >>代表逻辑移位即用0填充最高位。
- 对于正整数两者基本相似
- 对于负数,算术移位后扔是负数但是逻辑移位后不保证是负数
- Integer.toBinaryString的作用
可以把正型输出为补码。
原码:是一种计算机中对数字的二进制定点表示方法。原码表示法在数值前面增加了一位符号位(即最高位为符号位):正数该位为0,负数该位为1(0有两种表示:+0和-0),其余位表示数值的大小。
反码:正数的反码与其原码相同;负数的反码是对其原码逐位取反,但符号位除外。
反码通常用来作为原码求补码或者补码求源码的过渡。
补码:计算机中用补码表示。
对于正数,补码和原码的表示相同
对于负数,原码符号位不变, 数值部分按位取反,末位加1(即“取反加1”)此规则同样用于补码求原码。
题目:变量a是一个64位有符号的整数,初始值用16进制表示为:0x7FFFFFFFFFFFFFFF;
变量b是一个64位有符号的整数,初始值用16进制表示为:0x8000000000000000。
则a+b的结果用10进制表示为多少?
理解:其中计算机中的数值用补码表示,所以a和b都是补码
0x7FFF,FFFF,FFFF,FFFF + 0x8000,0000,0000,0000 = 0xFFFF,FFFF,FFFF,FFFF
然后将 0xFFFF,FFFF,FFFF,FFFF转为原码(初符号位以外取反加1)等于0x1000,0000,0000,0001即为-1
详细介绍请参考:http://www.cnblogs.com/wxdlut/p/5881865.htmlpublic class App { public static void main( String[] args ) { long a = 0x7FFFFFFFFFFFFFFFL; System.out.println(Long.toBinaryString(a)); long b = 0x8000000000000000L; System.out.println(Long.toBinaryString(b)); System.out.println(a+b); long c = a + b; System.out.println(Long.toBinaryString(c)); } }
- Switch能否用string做参数?
- equals与==的区别
- Java的四种引用,强弱软虚,用到的场景。
- Hashcode的作用
- ArrayList、LinkedList、Vector的区别
- Collection包结构,与Collections的区别
- try catch finally,try里有return,finally还执行么?
- Excption与Error包结构。OOM你遇到过哪些情况,SOF你遇到过哪些情况
- Java面向对象的三个特征与含义
- Override和Overload的含义去区别
- Interface与abstract类的区别
- Static class 与non static class的区别
- Java多态的实现原理
二、Java进阶
- 注解实现原理是什么?
- 能不能调用一个类的私有方法?为什么?
- JDK中有哪些设计模式的运用?你的项目中用了哪些设计模式?TreeMap中Compartor用了什么设计模式?
- 设计模式的六大原则是什么?如何理解?
- 原则1:单一职责原则:不要存在多于一个导致类变更的原因。通俗的说,即一个类只负责一项职责。
- 原则2:里氏替换原则:子类可以扩展父类的功能,但不能改变父类原有的功能。
- 原则3:依赖倒置原则:高层模块不应该依赖低层模块,二者都应该依赖其抽象(接口);抽象不应该依赖细节;细节应该依赖抽象。
- 原则4:接口隔离原则:客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。接口设计的过大或过小都不好。
- 原则5:迪米特法则:一个对象应该对其他对象保持最少的了解。迪米特法则又叫最少知道原则
- 原则6:开闭原则:个软件实体如类、模块和函数应该对扩展开放,对修改关闭。
- 总结:单一职责原则告诉我们实现类要职责单一;里氏替换原则告诉我们不要破坏继承体系;依赖倒置原则告诉我们要面向接口编程;接口隔离原则告诉我们在设计接口的时候要精简单一;迪米特法则告诉我们要降低耦合。而开闭原则是总纲,他告诉我们要对扩展开放,对修改关闭。
- 用过哪些设计模式?怎样实现线程安全单例模式?
- 有哪些单例模式的实现方式?
- 线程安不安全怎么理解?启动多个线程,如何知道他们都运行完毕了?
- BIO,NIO,AIO区别是什?Netty用的多吗?
- 简述Java内存模型?新生代老年代有哪些垃圾回收算法?新生代老年代属于堆还是栈?还知道哪些GC算法?创建大对象也是在新生代分配吗?
- 为什么Java内存模型中新生代和老年代采用GC算法不同?
- 简述新生代老年代gc过程;
- 详述JVM内存分代机制,并比较各个带使用的回收算法和各个带的特点;说说分代回收的优点有哪些?
- 说说新生代的算法中minor gc是什么?minor gc触发条件是什么?老年代用了什么算法?说说标记整理算法;full gc是否真正回收了废弃对象。有哪些gc策略。你觉得你的项目中如果需要jvm调优你会注重哪个分带的调优,或者说更注重哪种gc调优,为什么,具体怎么做
- TreeMap中元素怎么排序,如果没实现Comparator接口会怎么样?
- 红黑树,二叉树?红黑树是基于什么数据结构实现的?红黑树可能会再转为链表吗(长度低于6会转回链表)
- 简述Socket长连接短连接;当连接出现异常你是怎么处理的?
- 如果CPU有8个核心,那么程序启动多少个线程相对合适?
- synchronized作用,原理及JDK6之后的优化,与lock有哪些区别;你还知道哪些锁?
- 锁升级:偏向锁->轻量级锁->重量级锁
- Java中的同步机制,synchronized关键字,锁(重入锁)机制,其他解决同步的方volatile关键字ThreadLocal类的实现原理要懂。
- 什么是自旋锁?
- 线程池,线程池等待队列,线程池拒绝策略,死锁产生原因?
- 实现线程的方式?
- 锁的等级:方法锁、对象锁、类锁
- 写出生产者消费者模式
- ThreadLocal的设计理念与作用
- ThreadPool用法与优势
- Concurrent包里的其他东西:ArrayBlockingQueue、CountDownLatch等等
- 线程对象的方法run和start区别在哪?
- 简述线程的状态切换的状态变化过程;
- 简述:线程池,线程池等待队列,线程池拒绝策略
- 死锁产生原因有哪些?
- 如果线程池执行shutdown或shutdownNow,线程池中线程会中断吗,会出现什么异常,catch处理中该写些什么?
- 线程池有哪些参数,各代表什么意思?
- 线程池中提供哪些队列种类?
- 假如线程池核心池5,总池大小10,有界队列长度10,现在有13个线程要用线程池运行,说下核心池和队列中的线程情况;
- JDK中提供了哪几种线程池的实现?
- 用过哪些concurrent包下的类?
- 简述什么是原子类,cas操作可能会存在什么问题?
- 项目中有用到多线程,线程池吗,怎么用的?
- jvm调优有哪些切入点?
- 简述volatile关键字背后的实现原理;
- 简述Java反射机制和内省机制;
- 如何合理地估算线程池大小?
- 参考链接:http://ifeve.com/how-to-calculate-threadpool-size/
- 考虑cpu的个数,期望cpu利用率, io密集型任务, cpu密集型任务,当前系统是不是有其他的任务。
- 在《Java Concurrentcy in Practise》给出的经验公式如下:
- N_threads = 线程个数
- N_cpu = CPU的数量
- U_cpu = 目标CPU的使用率, (0<=Ucpu<=1)
- W/C = 等待时间和计算时间的比率
- 为保持处理器达到期望的使用率, 最优的池的大小等于:
- N_threads = N_cpu * U_cpu * (1 + W/C)
- Java对象的内存布局以及对象所需内存大小计算
- Java中的锁~队列同步器
- 有没有遇到中文乱码问题,如何解决的
- 你知道的开源协议有哪些
- json和xml区别
- 用过哪些调试java内存工具(https://www.nowcoder.com/discuss/3043)
- foreach与正常for循环效率对比
- 谈谈你对Java浅克隆与深克隆的理解
- JVM
- 内存模型以及分区,需要详细到每个区放什么。
- 堆里面的分区:Eden,survival from to,老年代,各自的特点。
- 对象创建方法,对象的内存分配,对象的访问定位。
- GC的两种判定方法:引用计数与引用链。
- GC的三种收集方法:标记清除、标记整理、复制算法的原理与特点,分别用在什么地方,如果让你优化收集方法,有什么思路?
- GC收集器有哪些?CMS收集器与G1收集器的特点。
- Minor GC与Full GC分别在什么时候发生?
- 几种常用的内存调试工具:jmap、jstack、jconsole。
- 类加载的五个过程:加载、验证、准备、解析、初始化。
- 先用javac Test.java进行编译。然后用java -XX:+TraceClassLoading Test。
- 然后就能在控制台打印出一大串的log。
- 在eclipse中查看类加载过程步骤: 项目–>properties–>run/debug setting–>选择目标类Test.java–>edit–>arguments–>VM arguments–> 输入:-XX:+TraceClassLoading。
- 类加载过程:
- 类从被加载到虚拟机到虚拟机内存中开始,到卸载出内存为止,它的生命周期包括: 加载(Loading),验证(Verification),准备(Preparation),解析(Resolution), 初始化(Initialization),使用(Using)和卸载(Unloading)7个阶段。 其中验证,准备,解析这3个部分统称为链接(Linking)。 类加载的过程:加载,验证,准备,解析,初始化这5个过程
- 在加载阶段,虚拟机完成以下3件事情:
- 通过一个类的全限定名来获取定义此类的二进制字节流
- 将这个字节流所代表的静态结构转化为方法区的运行时数据结构
- 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据访问的入口。
- 验证阶段:验证目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机规范,并且不会危害虚拟机自身安全。如果验证到输入的字节流不符合Class文件的约束,会抛出java.lang.VerifyError异常或其子类异常。验证阶段大致分为4个阶段完成:
- 文件格式验证:魔术,主次版本号,常量池类型等等
- 元数据验证:对字节码描述的信息进行语义分析,确保符号规范;比如:是否有父类,是否继承final修饰的类,是否实现了接口中的方法等等
- 字节码验证:这个验证最复杂的部分,目的是通过数据流和控制流分析,确保程序语义合法,符合逻辑。在第二阶段元数据中数据类型做完验证后,这个阶段对类的方法体进行校验分析。
保证操作数栈的数据类型与指令代码序列可以配合工作,不会出现操作数栈放置一个int类型,使用时按long类型加载本地变量表。 - 符合引用验证:发生在虚拟机将符号引用转化为直接引用的时候。比如符号引用中通过字符串描述的全限定名是否能查找到相应的类,符号引用中的类,字段,方法的访问性是否可以被当前类方法。
符号引用验证的目的是确保解析动作能正常执行
对于虚拟机的类加载机制来说,验证阶段是一个非常重要的,但不是一定必要的阶段。(因为对程序运行期没有影响)。
- 准备阶段: 准备阶段是正式为类的变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。注意:这时候进行内存分配的仅包括类变量(被static修饰的变量),而不包括实例变量,这里所说的初始值“通常情况”下是数值类型的零值。
相对“通常情况”会有一些特殊情况:如果类字段的字段属性存在ConstantValue属性,那在准备阶段变量会被初始化为ConstantValue属性所指定的值。比如:public static final int value = 123; 编译时javac将会为value生成ConstantValue属性,在准备阶段虚拟机就会根据ConstantValue的设置将value赋值为123 - 解析阶段: 解析阶段是虚拟机将常量池的符合引用替换为直接引用的过程。 符合引用:符合引用字面量形式明确定义在Java虚拟机规范的Class文件格式中。符合引用于虚拟机实现的内存布局无关。直接引用:直接引用是可以直接指向目标的指针,相对偏移量或是一个能间接定位到目标的句柄。直接引用和虚拟机实习的内存布局相关。如果有了直接引用,目标必定已经存在内存当中。 解析动作主要针对类或接口,字段,类方法, 接口方法的解析。
- 初始化:类的初始化是类加载过程的最后一步,前面的类加载过程中,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的Java程序代码(或者说是字节码)。初始化阶段是执行类构造器方法的过程。 构造器执行时由编译器自动收集类中所有类变量的赋值动作和静态语句块中的语句合并产生的,编译器收集顺序是由语句在原文件中出现的顺序决定,静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块中可以赋值,但是不能访问。
- 双亲委派模型:Bootstrap ClassLoader、Extension ClassLoader、ApplicationClassLoader。
- 分派:静态分派与动态分派。
- 面向对象和面向过程的区别
- 什么是泛型、为什么要使用以及泛型擦除
- Error、Exception区别
- 终止线程有几种方式?终止线程标记变量为什么是valotile类型?
- 什么叫守护线程,用什么方法实现守护线程
- Java线程池技术及原理,如何实现一个线程池
- 常见的序列化协议有哪些
- 内存溢出和内存泄漏的区别
- ava内存模型及各个区域的OOM,如何重现OOM,出现OOM如何解决
- 用什么工具可以查出内存泄漏
- Java类加载器及如何加载类,判断两个对象是否相等。(双亲委派)
- 比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载它们才有意义,否则,即使两个类来源同一个Class文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等。
- 双亲委派模型
- 启动类加载器(Bootstrap ClassLoader)C++编写
- 扩展类加载器 (Extension ClassLoader)一般对应jre\lib\ext
- 应用程序类加载器(Application ClassLoader)加载classpath指定的类,是最长使用的一种加载器
- 看过哪些JDK源码
- 详述类加载机制
- 什么是幂等性
- 有哪些JVM调优经验
-
Java多线程技术图谱
Java多线程技术图谱
三、JavaWeb基础
- Servlet和Jsp区别有哪些?如果没有Servlet,Jsp怎么跟后端交互?
- JSP和Servlet的区别;
- JSP的动态include和静态include;
- MVC概念;
- 简述Servlet生命周期;
- Tomcat在初始化中做了什么事?
- Tomcat调优怎么做?切入点在哪里?
- SQL优化有哪些切入点?
- SQL左连接右连接区别?
- 数据库优化哟哪些措施?
- 数据库索引在什么时候使用?什么样的字段适合建立索引?谈谈索引的底层数据结构;
- 索引什么情况下会失效?联合索引abc只用了a字段,索引是否会生效?
- SQL交并差集是用来干什么的?
- 简述MySQL事务隔离级别;
- JDBC连接步骤,Statement和PrepareStatement区别;
- 区别:
- Statement、PreparedStatement和CallableStatement都是接口(interface)。
- Statement继承自Wrapper、PreparedStatement继承自Statement、CallableStatement继承自PreparedStatement。
- Statement每次执行sql语句,数据库都要执行sql语句的编译 , 最好用于仅执行一次查询并返回结果的情形,效率高于PreparedStatement。
- 各自的特点1
- Statement接口提供了执行语句和获取结果的基本方法;
- PreparedStatement接口添加了处理 IN 参数的方法;
- CallableStatement接口添加了处理 OUT 参数的方法。
- 各自的特点2
- Statement:普通的不带参的查询SQL;支持批量更新,批量删除;
- PreparedStatement:可变参数的SQL,编译一次,执行多次,效率高; 安全性好,有效防止Sql注入等问题; 支持批量更新,批量删除;
- CallableStatement:继承自PreparedStatement,支持带参数的SQL操作; 支持调用存储过程,提供了对输出和输入/输出参数(INOUT)的支持;
- PreparedStatement是预编译的,使用PreparedStatement有几个好处
- 在执行可变参数的一条SQL时,PreparedStatement比Statement的效率高,因为DBMS预编译一条SQL当然会比多次编译一条SQL的效率要高。
- 安全性好,有效防止Sql注入等问题。
- 对于多次重复执行的语句,使用PreparedStament效率会更高一点,并且在这种情况下也比较适合使用batch;
- 代码的可读性和可维护性。
- 区别:
- 项目中数据库事务控制你们是怎么做的?
- 什么是存储过程,在什么时候使用?
- MySQL有哪些存储引擎?各自特点
- 乐观锁与悲观锁。使用场景有何差异?怎么实现乐观锁?
- 乐观锁与悲观锁:二者都是正如其名,乐观锁对待竞争资源保持乐观的态度,认为资源不是竞争激烈的资源,每次操作之前都要进行比对;而悲观锁则认为竞争资源竞争异常激烈,只要操作,立马加锁,直到资源的操作完毕才释放锁;
- 乐观锁则适用于读多写少,并发冲突少的场景。悲观锁比较适合强一致性的场景,但效率比较低,特别是读的并发低。
- 在MySQL中,乐观锁的实现一般需要借助于操作表的一个字段,可以是额外添加的,如
version
或timestamp
,也可以是现有的字段,但是一般选取第一种方案,我们经常采用的是timestamp的方案;每次在更新数据之前先要逻辑判断这个字段是否和之前查到的一样,一样则进行相应的更新操作,否则进行睡眠指定的时间,一般是毫秒级别重复进行如上操作;
- MySQL 索引原理。为什么索引一部分放到内存,一部分放到硬盘上? MySQL数据库索引原理
- MySQL有哪些存储引擎?各自特点 ?MySQL默认的存储引擎是什么?
- MySQL5.5以后默认使用InnoDB存储引擎,其中InnoDB和BDB提供事务安全表
四、JavaWeb常用框架
- 简述Spring IoC,AOP作用和原理;
- 简述SpringMVC执行流程;SpringMVC有什么优点?为什么要用它?SpringMVC和Struts2区别有哪些?
- Spring Bean是单例吗?单例bean是怎么实现的?Spring的Bean还有哪些其他模式?
- 简述SpringMVC获取请求参数的方式有哪些?原理是什么?
- SpringMVC控制器中定义全局HashMap,它是否是线程安全的,为什么?
- 若想要在某些自己指定的某些方法前后打印日志怎么实现?
- 过滤器和拦截器的区别?
- Spring的BeanFactory和FactoryBean有什么区别?
- 两个线程间隔输出字符怎么实现?
- Java线程阻塞调用wait函数和sleep区别和联系,还有函数yield,notify等的作用。
- Spring中有几种注入Bean的方式?
- Struts中请求处理过程;
- SpringMVC与Struts2区别;
- Hibernate/Ibatis两者的区别;
- Hibernate一级和二级缓存;
- Hibernate实现集群部署;
- Hibernate如何实现声明式事务;
- 简述Hibernate常见优化策略;
- 优化Hibernate所鼓励的7大措施:
- 尽量使用many-to-one,避免使用单项one-to-many
- 灵活使用单向one-to-many
- 不用一对一,使用多对一代替一对一
- 配置对象缓存,不使用集合缓存
- 一对多使用Bag 多对一使用Set
- 继承使用显示多态 HQL:from object polymorphism="exlicit" 避免查处所有对象
- 消除大表,使用二级缓存
- Spring如何实现AOP和IOC
- Spring bean注入方式
- Spring的事务管理(推荐看Spring的源码)
- Spring事务的传播特性
- springmvc原理
- springmvc用过哪些注解
- Restful有几种请求
- Restful好处
- Tomcat,Apache,JBoss的区别
- memcached和redis的区别
五、Java高级框架
- 你是怎么理解Nginx的?
- 为什么要用redis;redis有支持哪些数据结构?
- zookeeper
- 消息中间件
- 分布式
- 集群
- 微服务
- 相比于如memcached,mongodb有什么优势
- 如何理解分布式锁
- 分布式CAP了解吗?
- 开发过分布式框架?怎么实现分布式事务?
- 是否了解过高性能队列Disruptor?
参考:高性能队列——Disruptor
六、解决问题
- 线上系统每天会收到20万级的数据,一个月会有百万条,并且还会递增,你如何设计数据库?(分库分表,建立索引) 分库分表按什么规则分?如果查询字段不是分库分表的规则字段,怎么办?(分库分表多维度查询) Oracle分页的SQL关键字是什么?(rownum)
- 高并发情况下怎么降低服务器压力(暂不从增加服务器和硬件提升层面考虑),方案解决问题:
- 假如servlet处理一个请求需要0.4秒,那么处理100请求大概需要多久,为什么。同时处理1000个请求导致服务器压力过大崩溃怎么解决?
- 线上系统造成服务器cpu占用率过高问题(网上有解决方案)
- 秒杀系统少卖超卖问题怎么处理(网上有解决方案)
- 有个业务场景,通过第三方工具收集到大概百万条手机号,存储到文本中,文本中每行一个号码。我们的数据库中会存每个号码对应的会员等级信息(也有可能这个号码在数据库中不存在,那么他就是新用户)。按会员等级给号码发送不同内容的信息,新号码发送邀请信息。设计一个方案,怎么做效率最高(数据库数据量很大)
- 浏览器如何实现免登陆之类的功能,cookie和session的联系和区别以及其作用;
- 写算法。找到最大子数组的start,和end下标
- 5升桶3升桶搞4升水
面试及学习建议
-
基础->spring/springmvc/mybatis/数据库、缓存->消息中间件(activemq、kafka...)->(后面的仅代表个人看法,未知领域...)springboot、微服务、docker/分布式、zookeeper、rpc/大数据相关)
-
说说红黑树,它是基于什么数据结构实现的(红黑树了解不深,只是大致说了下思想-旋转、平衡、大小比较。(我感觉很难,有空得静下心来好好研究))。红黑树可能会再转为链表吗(答:长度低于8会转回链表 问:你确定是8而不是6 答:好像是6... (hashmap的看点真的很多,回去看了源码真的是6,而且扩容时红黑树变化很复杂,暂时看不懂))。说下hashmap put的过程(答:key的hashcode高16位与低16位异或运算得到新的hash,为了让数据(特别是在当前容量不大的情况下)散列更均匀,然后把异或计算出的新hash与此时的hashmap容量-1做&运算,得到插入下标。问:为什么要做&运算,还有什么方式。答:二进制运算速度快,还可以取模。之后如果下标位数组无数据则直接插入,如果有数据则链表往下逐个进行hash比较,如果产生hash碰撞再进行==或者equals比较key是否一样,一样则覆盖原数据,否则添加到链表后面。问:发生hash冲突怎么办 答:刚刚说了(重复)问:你确定是添加到链表后面吗 答:确定(我怀疑他jdk8的hashmap没看全(jdk7是新元素插入至链表头部的)还问了产生hash冲突后为什么还会比较原来key的hashcode,表示没听懂,看了源码也没发现有再次比较的过程,而且之后也重复问rehash后的元素具体去向问题(1.7和1.8元素rehash后元素去向是不同的),还有扩容问题,感觉他对jdk8的hashmap和1.7的有点混淆(还好我没怎么看jdk7,不过如果jdk7也看明白了,再说明与1.8的区别之后估计会大大加分吧,有点遗憾))。继续,当hashmap中数据量超过当前容量*扩容因子(默认0.75)则扩容为原来的2倍,问:还有什么要求 答:好像没了 问:当前插入的位置上没有元素就不扩容吧 答:哦哦。(其实不然,他说的是jdk7的情况,jdk8没有这个要求。当时要是知道7和8的区别,指出这点,那就是亮点了,再次遗憾))。为什么是2次幂扩容。(答:我觉得有3点因素。2进制运算快;hash与当前容量-1做&运算很快且很巧妙地获得元素下标;扩容后能巧妙地重新分配元素位置)说下扩容的rehash,扩容后的部分节点数据会重新定位,具体规则是hash&原容量,得到无非两个结果:0和1,如果是0则该元素所在下标位置不动,如果是1则将该元素放置原位置扩容后的对应位置(假如原先容量为16,元素位置在数组下标14的位置,则扩容后容量为32,该元素移动到数组下标30的位置(即原索引+原容量位置)。当时没这点解释的不够准确,有瑕疵)。为什么看hashmap源码,你觉得看了后对你有什么好处(答:比较喜欢探究(其实是近期面试才认真看的...)。知道了计算机位运算速度会比其它数学运算快;学习了它的思想对我思考问题方式有提升(能吹多少尽量吹多少);扩容是一个费性能的事,如果知道集合中大致会存多少元素最好给它一个初始容量),如果你知道里面会存100个左右数据,你会给它多大初始容量 (答:128) 它是线程安全的吗(答:不是,jdk1.8多线程情况下可能会造成数据丢失,1.8之前更可能造成死循环),线程安全的map有什么(答:hashtable,concurrentHashMap,前者已经被后者替代了,效率更高),说说concurrentHashMap,默认容量多少(答:16。只是了解,太复杂了,没细看,只知道jdk1.8之前采用分段锁方式处理,1.8之后采用cas乐观锁的方式来操作)。其它集合类了解吗(hashSet,treeSet,treeMap,都解释的相对明白,篇幅过长,不细说了),它们是否允许插入空值(treeSet,treeMap不可以)使用treeMap有什么需要注意的 (其中的元素要实现comparator接口)
链接:https://www.nowcoder.com/discuss/36782
来源:牛客网
一面(顺序可能有适当修改):
1、一个数组,有正有负,把正的移到右边,负的移到左边。
2、判断一个链表是否有环(我回答快慢指针,因此引出下一个问题)
3、假设一个节点为100的环形单链表,你这方法要走多少步判断出有环,99个节点呢?
4、tcp三次握手的过程?
5、进程与线程的区别?
6、说说你了解的http状态码,http请求方法?
7、简单说下银行家算法?
8、项目等相关。
9、实现一个生产者和消费者?
然后让我在房间等会,过了几分钟,二面面试官进来。
二面:
1、dubbo怎么实现异步的?(由于项目使用了)
2、dubbo底层怎么通信的?
3、进程和线程的区别?linux有进程和线程的区分吗?
4、中断和异常是什么?有什么区别?
5、tcp三次握手和四次挥手,两次握手会怎么样?
6、说说arp协议,nat协议,局域网是怎么通信的?
7、ip地址和mac地址区别?
8、虚拟内存和虚拟地址是什么?
9、死锁的必要条件?举一个死锁的例子,怎么避免死锁,怎么解决死锁?
10、hashmap、concurrenthashmap、hashtable?
11、项目,具体就不说了。
12、说下dns的过程?
13、最小堆的定义?最小堆的插入与删除的全过程?
14、红黑树的定义?为什么是平衡树?怎么维护平衡?
15、B+树的定义?为什么要用b+树,而不用平衡二叉树?
16、聚集索引和非聚集索引的区别?
17、synchronized的原理?
18、synchronized和lock的区别?
19、如果美团给了你offer,你实习公司又转正了,你还来吗?
然后带我出去,说等会不在这间房间,然后路上问我实习的部门leader是谁,卧槽,他认识我leader。很是尴尬。等了几分钟,三面面试官带我去另一个地方面试。
三面(最难,问的很深入):
1、排序算法的稳定性是指什么?
2、说说常见排序算法的稳定性?选择排序为什么不稳定?归并为什么是稳定的?
3、讲一下堆排序的全过程(这里面试官对每一个参数都会问清楚)?
4、你知道的设计模式(我回答单例,工厂,观察者,装饰者,因此引出更多问题)?
5、你会的单例(我回答懒汉,恶汉,静态内部类,枚举,双重检查)?
6、说说双重检查,volatile关键字的作用,因此也问了内存可见性和重排序?
7、说说原子类实现原理?(cas)?
8、说说cas需要哪些参数,操作系统对应的命令是什么?为什么能保证原子性?
9、volatile和cas有什么关联?什么时候可以互相替代?(一脸闷逼)
10、观察者模式有什么用?怎么实现的?
11、java类库那个是用装饰者模式(io)?
12、装饰着模式和代理模式有什么区别?(一脸懵逼)
13、给你一个64G的内存,你会怎么设置堆大小?
14、说说cms收集器的过程?
15、平时线上项目出现问题怎么排查?
16、hash冲突有哪些解决方法?简单说下再哈希法?怎么保证多个hash函数不会出现死循环?(很懵逼)
17、类加载机制?怎么破坏单例(这里应该想让我用类加载机制来破坏)?
DBMS对数据库的保护通过4个方面来实现:
数据库的恢复
数据库的并发控制
数据库的完整性控制
数据库安全性控制
DBMS中实现事务持久性的子系统是恢复管理子系统。
大多数 JVM 将内存区域划分为
- Method Area(Non-Heap)(方法区) ,
- Heap(堆) ,
- Program Counter Register(程序计数器) ,
- VM Stack(虚拟机栈,也有翻译成JAVA 方法栈的),
- Native Method Stack ( 本地方法栈 )
其中Method Area 和 Heap 是线程共享的 ,VM Stack,Native Method Stack 和Program Counter Register 是非线程共享的。为什么分为 线程共享和非线程共享的呢?请继续往下看。
首先我们熟悉一下一个一般性的 Java 程序的工作过程。一个 Java 源程序文件,会被编译为字节码文件(以 class 为扩展名),每个java程序都需要运行在自己的JVM上,然后告知 JVM 程序的运行入口,再被 JVM 通过字节码解释器加载运行。那么程序开始运行后,都是如何涉及到各内存区域的呢?
概括地说来,JVM初始运行的时候都会分配好 Method Area(方法区) 和Heap(堆) ,而JVM 每遇到一个线程,就为其分配一个 Program Counter Register(程序计数器) , VM Stack(虚拟机栈)和Native Method Stack (本地方法栈), 当线程终止时,三者(虚拟机栈,本地方法栈和程序计数器)所占用的内存空间也会被释放掉。这也是为什么我把内存区域分为线程共享和非线程共享的原因,非线程共享的那三个区域的生命周期与所属线程相同,而线程共享的区域与JAVA程序运行的生命周期相同,所以这也是系统垃圾回收的场所只发生在线程共享的区域(实际上对大部分虚拟机来说知发生在Heap上)的原因。
事务属性的种类: 传播行为、隔离级别、只读和事务超时
a) 传播行为定义了被调用方法的事务边界。
传播行为 | 意义 |
---|---|
PROPERGATION_MANDATORY | 表示方法必须运行在一个事务中,如果当前事务不存在,就抛出异常 |
PROPAGATION_NESTED | 表示如果当前事务存在,则方法应该运行在一个嵌套事务中。否则,它看起来和 PROPAGATION_REQUIRED 看起来没什么俩样 |
PROPAGATION_NEVER | 表示方法不能运行在一个事务中,否则抛出异常 |
PROPAGATION_NOT_SUPPORTED | 表示方法不能运行在一个事务中,如果当前存在一个事务,则该方法将被挂起 |
PROPAGATION_REQUIRED | 表示当前方法必须运行在一个事务中,如果当前存在一个事务,那么该方法运行在这个事务中,否则,将创建一个新的事务 |
PROPAGATION_REQUIRES_NEW | 表示当前方法必须运行在自己的事务中,如果当前存在一个事务,那么这个事务将在该方法运行期间被挂起 |
PROPAGATION_SUPPORTS | 表示当前方法不需要运行在一个是事务中,但如果有一个事务已经存在,该方法也可以运行在这个事务中 |
b) 隔离级别
在操作数据时可能带来 3 个副作用,分别是脏读、不可重复读、幻读。为了避免这 3 中副作用的发生,在标准的 SQL 语句中定义了 4 种隔离级别,分别是未提交读、已提交读、可重复读、可序列化。而在 spring 事务中提供了 5 种隔离级别来对应在 SQL 中定义的 4 种隔离级别,如下:
隔离级别 | 意义 |
---|---|
ISOLATION_DEFAULT | 使用后端数据库默认的隔离级别 |
ISOLATION_READ_UNCOMMITTED | 允许读取未提交的数据(对应未提交读),可能导致脏读、不可重复读、幻读 |
ISOLATION_READ_COMMITTED | 允许在一个事务中读取另一个已经提交的事务中的数据(对应已提交读)。可以避免脏读,但是无法避免不可重复读和幻读 |
ISOLATION_REPEATABLE_READ | 一个事务不可能更新由另一个事务修改但尚未提交(回滚)的数据(对应可重复读)。可以避免脏读和不可重复读,但无法避免幻读 |
ISOLATION_SERIALIZABLE | 这种隔离级别是所有的事务都在一个执行队列中,依次顺序执行,而不是并行(对应可序列化)。可以避免脏读、不可重复读、幻读。但是这种隔离级别效率很低,因此,除非必须,否则不建议使用。 |
c) 只读
- 如果在一个事务中所有关于数据库的操作都是只读的,也就是说,这些操作只读取数据库中的数据,而并不更新数据,那么应将事务设为只读模式( READ_ONLY_MARKER ) , 这样更有利于数据库进行优化 。因为只读的优化措施是事务启动后由数据库实施的,因此,只有将那些具有可能启动新事务的传播行为 (PROPAGATION_NESTED 、 PROPAGATION_REQUIRED 、 PROPAGATION_REQUIRED_NEW) 的方法的事务标记成只读才有意义。
- 如果使用 Hibernate 作为持久化机制,那么将事务标记为只读后,会将 Hibernate 的 flush 模式设置为 FULSH_NEVER, 以告诉 Hibernate 避免和数据库之间进行不必要的同步,并将所有更新延迟到事务结束。
d) 事务超时
- 如果一个事务长时间运行,这时为了尽量避免浪费系统资源,应为这个事务设置一个有效时间,使其等待数秒后自动回滚。与设置“只读”属性一样,事务有效属性也需要给那些具有可能启动新事物的传播行为的方法的事务标记成只读才有意义。
Servlet 与 CGI 的比较
- 和CGI程序一样,Servlet可以响应用户的指令(提交一个FORM等等),也可以象CGI程序一样,收集用户表单的信息并给予动态反馈(简单的注册信息录入和检查错误)。然而,Servlet的机制并不仅仅是这样简单的与用户表单进行交互。传统技术中,动态的网页建立和显示都是通过CGI来实现的,但是,有了Servlet,您可以大胆的放弃所有CGI(perl?php?甚至asp!),利用Servlet代替CGI,进行程序编写。
- 对比一:性能
- 当用户浏览器发出一个Http/CGI的请求,或者说 调用一个CGI程序的时候,服务器端就要新启用一个进程 (而且是每次都要调用),调用CGI程序越多(特别是访问量高的时候),就要消耗系统越多的处理时间,只剩下越来越少的系统资源,对于用户来说,只能是漫长的等待服务器端的返回页面了,这对于电子商务激烈发展的今天来说,不能不说是一种技术上的遗憾。
- Servlet充分发挥了服务器端的资源并高效的利用。每次调用Servlet时并不是新启用一个进程 ,而是在一个Web服务器的进程敏感词享和分离线程,而线程最大的好处在于可以共享一个数据源,使系统资源被有效利用。
- 对比二:平台环境的敏感性
- 传统的CGI程序,不具备平台无关性特征,系统环境发生变化,CGI程序就要瘫痪,
- Servlet具备Java的平台无关性,在系统开发过程中保持了系统的可扩展性、高效性。
- 对比三:架构
- 传统技术中,一般大都为二层的系统架构,即Web服务器+数据库服务器,导致网站访问量大的时候,无法克服CGI程序与数据库建立连接时速度慢的瓶颈,从而死机、数据库死锁现象频繁发生。
- Servlet有连接池的概念,它可以利用多线程的优点,在系统缓存中事先建立好若干与数据库的连接,到时候若想和数据库打交道可以随时跟系统"要"一个连接即可,反应速度可想而知。
- 对比一:性能
Servlet的生命周期
- 加载:容器通过类加载器使用Servlet类对应的文件来加载Servlet
- 创建:通过调用Servlet的构造函数来创建一个Servlet实例
- 初始化:通过调用Servlet的init()方法来完成初始化工作,这个方法是在Servlet已经被创建,但在向客户端提供服务之前调用。
- 处理客户请求:Servlet创建后就可以处理请求,当有新的客户端请求时,Web容器都会创建一个新的线程来处理该请求。接着调用Servlet的Service()方法来响应客户端请求(Service方法会根据请求的method属性来调用doGet()和doPost())
- 卸载:容器在卸载Servlet之前需要调用destroy()方法,让Servlet释放其占用的资源。
Struts1和Struts2的区别和对比:
-
Action 类:
- Struts1要求Action类继承一个抽象基类。Struts1的一个普遍问题是使用抽象类编程而不是接口,而struts2的Action是接口。
- Struts 2 Action类可以实现一个Action接口,也可实现其他接口,使可选和定制的服务成为可能。Struts2提供一个ActionSupport基类去 实现 常用的接口。Action接口不是必须的,任何有execute标识的POJO对象都可以用作Struts2的Action对象。
-
线程模式:
- Struts1 Action是单例模式并且必须是线程安全的,因为仅有Action的一个实例来处理所有的请求。单例策略限制了Struts1 Action能作的事,并且要在开发时特别小心。Action资源必须是线程安全的或同步的。
- Struts2 Action对象为每一个请求产生一个实例,因此没有线程安全问题。(实际上,servlet容器给每个请求产生许多可丢弃的对象,并且不会导致性能和垃圾回收问题)
-
Servlet 依赖:
- Struts1 Action 依赖于Servlet API ,因为当一个Action被调用时HttpServletRequest 和 HttpServletResponse 被传递给execute方法。
- Struts 2 Action不依赖于容器,允许Action脱离容器单独被测试。如果需要,Struts2 Action仍然可以访问初始的request和response。但是,其他的元素减少或者消除了直接访问HttpServetRequest 和 HttpServletResponse的必要性。
-
可测性:
- 测试Struts1 Action的一个主要问题是execute方法暴露了servlet API(这使得测试要依赖于容器)。一个第三方扩展--Struts TestCase--提供了一套Struts1的模拟对象(来进行测试)。
- Struts 2 Action可以通过初始化、设置属性、调用方法来测试,“依赖注入”支持也使测试更容易。
-
捕获输入:
- Struts1 使用ActionForm对象捕获输入。所有的ActionForm必须继承一个基类。因为其他JavaBean不能用作ActionForm,开发者经常创建多余的类捕获输入。动态Bean(DynaBeans)可以作为创建传统ActionForm的选择,但是,开发者可能是在重新描述(创建)已经存 在的JavaBean(仍然会导致有冗余的javabean)。
- Struts 2直接使用Action属性作为输入属性,消除了对第二个输入对象的需求。输入属性可能是有自己(子)属性的rich对象类型。Action属性能够通过 web页面上的taglibs访问。Struts2也支持ActionForm模式。rich对象类型,包括业务对象,能够用作输入/输出对象。这种 ModelDriven 特性简化了taglib对POJO输入对象的引用。
-
表达式语言:
- Struts1 整合了JSTL,因此使用JSTL EL。这种EL有基本对象图遍历,但是对集合和索引属性的支持很弱。
- Struts2可以使用JSTL,但是也支持一个更强大和灵活的表达式语言--"Object Graph Notation Language" (OGNL).
AWT & Swing
-
AWT :是通过调用操作系统的native方法实现的,所以在Windows系统上的AWT窗口就是Windows的风格,而在Unix系统上的则是XWindow风格。 AWT 中的图形函数与 操作系统 所提供的图形函数之间有着一一对应的关系,我们把它称为peers。 也就是说,当我们利用 AWT 来构件图形用户界面的时候,我们实际上是在利用 操作系统 所提供的图形库。由于不同 操作系统 的图形库所提供的功能是不一样的,在一个平台上存在的功能在另外一个平台上则可能不存在。为了实现Java语言所宣称的"一次编译,到处运行"的概念,AWT 不得不通过牺牲功能来实现其平台无关性,也就是说,AWT 所提供的图形功能是各种通用型操作系统所提供的图形功能的交集。由于AWT 是依靠本地方法来实现其功能的,我们通常把AWT控件称为重量级控件。
-
Swing :是所谓的Lightweight组件,不是通过native方法来实现的,所以Swing的窗口风格更多样化。但是,Swing里面也有heaveyweight组件。比如JWindow,Dialog,JFrame;Swing由纯Java写成,可移植性好,外观在不同平台上相同。所以Swing部件称为轻量级组件( Swing是由纯JAVA CODE所写的,因此SWING解决了JAVA因窗口类而无法跨平台的问题,使窗口功能也具有跨平台与延展性的特性,而且SWING不需占有太多系统资源,因此称为轻量级组件!!!)
Struts工作原理
- MVC即Model-View-Controller的缩写,是一种常用的设计模式。MVC减弱了业务逻辑接口和数据接口之间的耦合,以及让视图层更富于变化。
- Struts是MVC的一种实现,它将Servlet和JSP标记(属于J2EE规范)用作实现的一部分。Struts继承了MVC的各项特性,并根据J2EE的特点,做了相应的变化与扩展。
- 控制器:有一个XML文件struts-config.xml,与之相关联的是Controller,在Struts中,承担MVC中Controller角 色的是一个Servlet,叫ActionServlet。ActionServlet是一个通用的控制组件。这个控制组件提供了处理所有发送到 Struts的HTTP请求的入口点。它截取和分发这些请求到相应的动作类(这些动作类都是Action类的子类)。另外控制组件也负责用相应的请求参数填充Action From(通常称之为FromBean),并传给动作类(通常称之为ActionBean)。动作类实现核心商业逻辑,它可以访问java bean 或调用EJB。最后动作类把控制权传给后续的JSP文件,后者生成视图。所有这些控制逻辑利用struts-config.xml文件来配置。
- 视图:主要由JSP生成页面完成视图,Struts提供丰富的JSP 标签库: Html,Bean,Logic,Template等,这有利于分开表现逻辑和程序逻辑。
- 模型:模型以一个或多个java bean的形式存在。这些bean分为三类:Action Form、Action、JavaBean or EJB。Action Form通常称之为FormBean,封装了来自于Client的用户请求信息,如表单信息。Action通常称之为ActionBean,获取从 ActionSevlet传来的FormBean,取出FormBean中的相关信息,并做出相关的处理,一般是调用Java Bean或EJB等。
- 流程:在Struts中,用户的请求一般以.do作为请求服务名,所有的.do请求均被指向 ActionSevlet,ActionSevlet根据Struts-config.xml中的配置信息,将用户请求封装成一个指定名称的 FormBean,并将此FormBean传至指定名称的ActionBean,由ActionBean完成相应的业务操作,如文件操作,数据库操作等。 每一个*.do均有对应的FormBean名称和ActionBean名称,这些在Struts-config.xml中配置。
-
核心:Struts的核心是ActionSevlet,ActionSevlet的核心是Struts-config.xml。
image.png
JSP文件的动静包含
-
动态 INCLUDE 用 jsp:include 动作实现 <jsp:include page="included.jsp" flush="true" /> 它总是会检查所含文件中的变化 , 适合用于包含动态页面 , 并且可以带参数。各个文件分别先编译,然后组合成一个文件。
-
静态 INCLUDE 用 include 伪码实现 , 定不会检查所含文件的变化 , 适用于包含静态页面 <%@ include file="included.htm" %> 。先将文件的代码被原封不动地加入到了主页面从而合成一个文件,然后再进行翻译,此时不允许有相同的变量。
-
以下是对 include 两种用法的区别 , 主要有两个方面的不同 ;
- 执行时间上 :
<%@ include file="relativeURI"%> 是在翻译阶段执行
<jsp:include page="relativeURI" flush="true" /> 在请求处理阶段执行 . - 引入内容的不同 :
<%@ include file="relativeURI"%>
引入静态文本 (html,jsp), 在 JSP 页面被转化成 servlet 之前和它融和到一起 .
<jsp:include page="relativeURI" flush="true" /> 引入执行页面或 servlet 所生成的应答文本 .
- 执行时间上 :
常见JVM配置参数释义
常见配置汇总
- 堆设置
- -Xms:初始堆大小
- -Xmx:最大堆大小
- -XX:NewSize=n:设置年轻代大小
- -XX:NewRatio=n:设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4
- -XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5
- -XX:MaxPermSize=n:设置持久代大小
- 收集器设置
- -XX:+UseSerialGC:设置串行收集器
- -XX:+UseParallelGC:设置并行收集器
- -XX:+UseParalledlOldGC:设置并行年老代收集器
- -XX:+UseConcMarkSweepGC:设置并发收集器
- 垃圾回收统计信息
- -XX:+PrintGC
- -XX:+PrintGCDetails
- -XX:+PrintGCTimeStamps
- -Xloggc:filename
- 并行收集器设置
- -XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数。并行收集线程数。
- -XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间
- -XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)
- 并发收集器设置
- -XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况。
- -XX:ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。
-Xms -Xmx分别设置堆的最小值和最大值,如果要设置成堆的大小可变,那么可以将最大值和最小值设置成不一样,如果要将堆大小固定,那么只需将最大值和最小值设置成一样的就行。
jvm中分为堆和方法区
https://www.nowcoder.com/questionTerminal/970cdaaa4a114cbf9fef82213a7dabca
堆又进一步分为新生代和老年代
方法区为永久代
堆中区分的新生代和老年代是为了垃圾回收,新生代中的对象存活期一般不长,而老年代中的对象存活期较长,所以当垃圾回收器回收内存时,新生代中垃圾回收效果较好,会回收大量的内存,而老年代中回收效果较差,内存回收不会太多。
基于以上特性,新生代中一般采用复制算法,因为存活下来的对象是少数,所需要复制的对象少,而老年代对象存活多,不适合采用复制算法,一般是标记整理和标记清除算法。
因为复制算法需要留出一块单独的内存空间来以备垃圾回收时复制对象使用,所以将新生代分为eden区和两个survivor区,每次使用eden和一个survivor区,另一个survivor作为备用的对象复制内存区。
综上:
-Xmn设置了新生代的大小为5120m,而-XXSurvivorRatio=3,所有将新生代共分成5分,eden占三份,survivor占两份,每份1/5
算法问题:# 二叉树中任意两个节点的最近公共祖先
思路:从根节点开始遍历,如果node1和node2中的任一个和root匹配,那么root就是最低公共祖先。 如果都不匹配,则分别递归左、右子树,如果有一个 节点出现在左子树,并且另一个节点出现在右子树,则root就是最低公共祖先. 如果两个节点都出现在左子树,则说明最低公共祖先在左子树中,否则在右子树。
代码实现
public class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
//发现目标节点则通过返回值标记该子树发现了某个目标结点
if(root == null || root == p || root == q) return root;
//查看左子树中是否有目标结点,没有为null
TreeNode left = lowestCommonAncestor(root.left, p, q);
//查看右子树是否有目标节点,没有为null
TreeNode right = lowestCommonAncestor(root.right, p, q);
//都不为空,说明做右子树都有目标结点,则公共祖先就是本身
if(left!=null&&right!=null) return root;
//如果发现了目标节点,则继续向上标记为该目标节点
return left == null ? right : left;
}
}