基础
1. String是基础数据类型么?
不是,String是一个类
而java的8大基本数据类型分别是:
逻辑类 boolean
文本类 char
整数类 byte, short, int, long
浮点类 double, float。
2. float f=3.4;是否正确?
不正确。3.4是双精度数,将双精度型(double)赋值给浮点型(float)属于下转型(down-casting,也称为窄化)会造成精度损失,因此需要强制类型转换float f =(float)3.4; 或者写成float f =3.4F
3. int和Integer有什么区别?
Integer是Int的包装类
装箱,封箱
class AutoUnboxingTest {
public static void main(String[] args) {
Integer a = new Integer(3);
Integer b = 3; // 将3自动装箱成Integer类型
int c = 3;
System.out.println(a == b); // false 两个引用没有引用同一对象
System.out.println(a == c); // true a自动拆箱成int类型再和c比较
}
}
面试题2:
public class Test03 {
public static void main(String[] args) {
Integer f1 = 100, f2 = 100, f3 = 150, f4 = 150;
System.out.println(f1 == f2); //结果是true
System.out.println(f3 == f4); //结果是false
}
}
如果整型字面量的值在-128到127之间,那么不会new新的Integer对象,而是直接引用常量池中的Integer对象,所以上面的面试题中f1f2的结果是true,而f3f4的结果是false。
4. &和&&的区别?
虽然二者都要求运算符左右两端的布尔值都是true整个表达式的值才是true。&&之所以称为短路运算是因为,如果&&左边的表达式的值是false,右边的表达式会被直接短路掉,不会进行运算。很多时候我们可能都需要用&&而不是&,例如在验证用户登录时判定用户名不是null而且不是空字符串,应当写为:username != null &&!username.equals(“”),二者的顺序不能交换,更不能用&运算符,因为第一个条件如果不成立,根本不能进行字符串的equals比较,否则会产生NullPointerException异常。注意:逻辑或运算符(|)和短路或运算符(||)的差别也是如此。
5. 解释内存中的栈(stack)、堆(heap)和静态区(static area)的用法。
栈空间操作起来最快但是栈很小,通常大量的对象都是放在堆空间
String str = new String("hello");
上面的语句中变量str放在栈上,用new创建出来的字符串对象放在堆上,而”hello”这个字面量放在静态区。
6. 是否可以继承String类?
不能,String是final类
7. String和StringBuilder、StringBuffer的区别?
Java平台提供了两种类型的字符串:String和StringBuffer/StringBuilder,它们可以储存和操作字符串。其中String是只读字符串,也就意味着String引用的字符串内容是不能被改变的。而StringBuffer/StringBuilder类表示的字符串对象可以直接进行修改。StringBuilder是Java 5中引入的,它和StringBuffer的方法完全相同,区别在于它是在单线程环境下使用的,因为它的所有方面都没有被synchronized修饰,因此它的效率也比StringBuffer要高。
8. 如何将字符串反转?
调用了它的父类 AbstractStringBuilder 的 reverse 方法实现
public static String reverseStringByStringBuilderApi(String str) {
if (str != null && str.length() > 0) {
return new StringBuilder(str).reverse().toString();
}
return str;
}
9. java 中 IO 流分为几种?
这个题要从流的角度去划分:
- 按照流的流向分,可以分为输入流和输出流;
- 按照操作单元划分,可以划分为字节流和字符流;
- 按照流的角色划分为节点流和处理流。
所有流的基类 - InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
- OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。
-
10. Java 容器有哪些
这一块之所以重要是因为,各个接口的特性不同。下面说一下我对这些类的理解。
Set下各种实现类对比
HashSet基于哈希表实现,有以下特点:
1.不允许重复
2.允许值为null,但是只能有一个
3.无序的。
4.没有索引,所以不包含索引操作的方法
LinkedHashSet跟HashSet一样都是基于哈希表实现。只不过linkedHashSet在hashSet的基础上多了一个链表,这个链表就是用来维护容器中每个元素的顺序的。有以下特点:
1.不允许重复
2.允许值为null,但是只能有一个
3.有序的。
4.没有索引,所以不包含索引操作的方法
TreeSet是SortedSet接口的唯一实现类,是基于二叉树实现的。TreeSet可以确保集合元素处于排序状态。TreeSet支持两种排序方式,自然排序 和定制排序,其中自然排序为默认的排序方式。向TreeSet中加入的应该是同一个类的对象。有以下特点:
1.不允许重复
2.不允许null值
3.没有索引,所以不包含索引操作的方法
11. 迭代器 Iterator 是什么?
首先说一下迭代器模式,它是 Java 中常用的设计模式之一。用于顺序访问集合对象的元素,无需知道集合对象的底层实现。
Iterator 是可以遍历集合的对象,为各种容器提供了公共的操作接口,隔离对容器的遍历操作和底层实现,从而解耦。
缺点是增加新的集合类需要对应增加新的迭代器类,迭代器类与集合类成对增加。
12. Java 中的final关键字有哪些用法?
(1)修饰类:表示该类不能被继承;
(2)修饰方法:表示方法不能被重写;
(3)修饰变量:表示变量只能一次赋值以后值不能被修改(常量)。
13. Math.round(11.5) 等于多少?Math.round(-11.5)等于多少?
Math.round(11.5)的返回值是12,Math.round(-11.5)的返回值是-11。四舍五入的原理是在参数上加0.5然后进行下取整。
14. switch 是否能作用在byte 上,是否能作用在long 上,是否能作用在String上?
expr可以是byte、short、char、int、enum、String类型,但是long类型不能
15. 用最有效率的方法计算2乘以8?
2 << 3(左移3位相当于乘以2的3次方,右移3位相当于除以2的3次方)。
16. 数组有没有length()方法?String有没有length()方法?
数组没有length()方法,有length 的属性。String 有length()方法
17. 构造器(constructor)是否可被重写(override)?
构造器不能被继承,因此不能被重写,但可以被重载。
18. 重载和重写的区别
方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载;重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。重载对返回类型没有特殊的要求。
19. ava.util.ArraysArrayList和java.util.ArrayList
调用Arrays.asList()、CollectionUtils.arrayToList()生产的List的add、remove方法时报异常,这是因为返回的是Arrays的内部类ArrayList, 而不是java.util.ArrayList
20. List、Set、Map是否继承自Collection接口?
List、Set 是,Map 不是。Map是键值对映射容器,与List和Set有明显的区别,而Set存储的零散的元素且不允许有重复元素(数学中的集合也是如此),List是线性结构的容器,适用于按数值索引访问元素的情形。
21. Collection和Collections的区别?
Collection是一个接口,它是Set、List等容器的父接口;Collections是个一个工具类,提供了一系列的静态方法来辅助容器操作,这些方法包括对容器的搜索、排序、线程安全化等等。
22. List、Map、Set三个接口存取元素时,各有什么特点?
List以特定索引来存取元素,可以有重复元素。Set不能存放重复元素(用对象的equals()方法来区分元素是否重复)。Map保存键值对(key-value pair)映射,映射关系可以是一对一或多对一
23. String和byte[]之间的转换
public static byte[] strToByteArray(String str) {
if (str == null) {
return null;
}
byte[] byteArray = str.getBytes();
return byteArray;
}
24. 当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递?
是值传递。Java语言的方法调用只支持参数的值传递。当一个对象实例作为一个参数被传递到方法中时,参数的值就是对该对象的引用。对象的属性可以在被调用过程中被改变,但对对象引用的改变是不会影响到调用者的
25. 获得一个类的类对象有哪些方式?
- 方法1:类型.class,例如:String.class
- 方法2:对象.getClass(),例如:”hello”.getClass()
- 方法3:Class.forName(),例如:Class.forName(“java.lang.String”)
26. hashCode简介
public int hashCode():hashCode是根类Obeject中的方法。默认情况下,Object中的hashCode() 返回对象的32位jvm内存地址。也就是说如果对象不重写该方法,则返回相应对象的32为JVM内存地址。
27. hashCode注意点
关于hashCode方法,一致的约定是:
1、重写了euqls方法的对象必须同时重写hashCode()方法。
2、如果两个对象equals相等,那么这两个对象的HashCode一定也相同
3、如果两个对象的HashCode相同,不代表两个对象就相同,只能说明这两个对象在散列存储结构中,存放于同一个位置
28. hashCode作用
从Object角度看,JVM每new一个Object,它都会将这个Object丢到一个Hash表中去,这样的话,下次做Object的比较或者取这个对象的时候(读取过程),它会根据对象的HashCode再从Hash表中取这个对象。这样做的目的是提高取对象的效率。若HashCode相同再去调用equal。
HashCode是用于查找使用的,而equals是用于比较两个对象的是否相等的。
29. 为什么重写
实际开发的过程中在hashmap或者hashset里如果不重写的hashcode和equals方法的话会导致我们存对象的时候,把对象存进去了,取的时候却取不到想要的对象。
重写了hashcode和equals方法可以迅速的在hashmap中找到键的位置;
1、重写hashcode是为了保证相同的对象会有相同的hashcode;
2、重写equals是为了保证在发生冲突的情况下取得到Entry对象(也可以理解是key或是元素);
30. JSP有哪些内置对象?作用分别是什么?
JSP有9个内置对象:
- request:封装客户端的请求,其中包含来自GET或POST请求的参数;
- response:封装服务器对客户端的响应;
- pageContext:通过该对象可以获取其他对象;
- session:封装用户会话的对象;
- application:封装服务器运行环境的对象;
- out:输出服务器响应的输出流对象;
- config:Web应用的配置对象;
- page:JSP页面本身(相当于Java程序中的this);
- exception:封装页面抛出异常的对象。
31. 讲解JSP中的四种作用域。
JSP中的四种作用域包括page、request、session和application,具体来说:
- page代表与一个页面相关的对象和属性。
- request代表与Web客户机发出的一个请求相关的对象和属性。一个请求可能跨越多个页面,涉及多个Web组件;需要在页面显示的临时数据可以置于此作用域。
- session代表与某个用户与服务器建立的一次会话相关的对象和属性。跟某个用户相关的数据应该放在用户自己的session中。
- application代表与整个Web应用程序相关的对象和属性,它实质上是跨越整个Web应用程序,包括多个页面、请求和会话的一个全局作用域。
常见的异常类
- runtime
- classsnot
- indexOutOfBound
- 运行时异常
- ArithmeticException(算术异常)
- ClassCastException (类转换异常)
- IllegalArgumentException (非法参数异常)
- IndexOutOfBoundsException (下标越界异常)
- NullPointerException (空指针异常)
- SecurityException (安全异常)
线程
1. 在 java 程序中怎么保证多线程的运行安全?
线程安全在三个方面体现
1.原子性:提供互斥访问,同一时刻只能有一个线程对数据进行操作,(atomic,synchronized);
2.可见性:一个线程对主内存的修改可以及时地被其他线程看到,(synchronized,volatile);
3.有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序,该观察结果一般杂乱无序,(happens-before原则)。
2. synchronized关键字的作用
synchronized提供了同步锁的概念,被synchronized修饰的代码段可以防止被多个线程同时执行,必须一个线程把synchronized修饰的代码段都执行完毕了,其他的线程才能开始执行这段代码。
因为synchronized保证了在同一时刻,只能有一个线程执行同步代码块,所以执行同步代码块的时候相当于是单线程操作了,那么线程的可见性、原子性、有序性(线程之间的执行顺序)它都能保证了。
3. volatile关键字和synchronized关键字的区别
①volatile只能作用于变量,使用范围较小。synchronized可以用在变量、方法、类、同步代码块等,使用范围比较广。
②volatile只能保证可见性和有序性,不能保证原子性。而可见性、有序性、原子性synchronized都可以包证。
③volatile不会造成线程阻塞。synchronized可能会造成线程阻塞。
4. 线程的状态
新建状态:
新建一个线程的对象。
实现Runnable接口和继承Thread可以得到一个线程类,new一个实例出来,线程就进入了初始状态
就绪状态:
当线程有资格运行,但调度程序还没有把它选定为运行线程时线程所处的状态。
运行状态:
可运行的线程获取了cpu的使用权,执行程序代码
阻塞状态:
所谓阻塞状态是正在运行的线程没有运行结束,暂时让出CPU,这时其他处于就绪状态的线程就可以获得CPU时间,进入运行状态。
① 线程通过调用sleep方法进入睡眠状态;
② 线程调用一个在I/O上被阻塞的操作,即该操作在输入输出操作完成之前不会返回到它的调用者;
③ 线程试图得到一个锁,而该锁正被其他线程持有;
④ 线程在等待某个触发条件;
死亡状态:
有两个原因会导致线程死亡:
① run方法正常退出而自然死亡;
② 一个未捕获的异常终止了run方法而使线程猝死;
③ 线程调用 stop()方法、destory()方法或 run()方法
为了确定线程在当前是否存活着(就是要么是可运行的,要么是被阻塞了),需要使用isAlive方法,如果是可运行或被阻塞,这个方法返回true;如果线程仍旧是new状态且不是可运行的,或者线程死亡了,则返回false。
5. Thread和Runnable区别
在这就是可以避免Java中的单继承的限制,增加程序的健壮性,代码可以被多个线程共享,代码和数据独立。线程池只能放入实现Runnable 类线程,不能直接放入继承Thread的类
6. 什么是线程安全
如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
7. 多线程实现买票
https://blog.csdn.net/weixin_41835916/article/details/80630833
8. 线程和进程的区别是什么?
1、进程是一段正在执行的程序,是资源分配的基本单元,而线程是CPU调度的基本单元。
2、进程间相互独立进程,进程之间不能共享资源,一个进程至少有一个线程,同一进程的各线程共享整个进程的资源(寄存器、堆栈、上下文)。
3、线程的创建和切换开销比进程小。
9. 为什么使用线程池
由于创建和销毁线程都需要很大的开销,运用线程池就可以大大的缓解这些内存开销很大的问题;可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存。
10. Thread类的sleep()方法和对象的wait()方法都可以让线程暂停执行,它们有什么区别?
sleep()方法(休眠)是线程类(Thread)的静态方法,调用此方法会让当前线程暂停执行指定的时间,将执行机会(CPU)让给其他线程,但是对象的锁依然保持,因此休眠时间结束后会自动恢复。wait()是Object类的方法,调用对象的wait()方法导致当前线程放弃对象的锁(线程暂停执行),进入对象的等待池(wait pool),只有调用对象的notify()方法(或notifyAll()方法)时才能唤醒等待池中的线程进入等锁池(lock pool),如果线程重新获得对象的锁就可以进入就绪状态。
11. 线程的sleep()方法和yield()方法有什么区别?
① sleep()方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会;yield()方法只会给相同优先级或更高优先级的线程以运行的机会;
② 线程执行sleep()方法后转入阻塞(blocked)状态,而执行yield()方法后转入就绪(ready)状态;
③ sleep()方法声明抛出InterruptedException,而yield()方法没有声明任何异常;
④ sleep()方法比yield()方法(跟操作系统CPU调度相关)具有更好的可移植性。
12. 请说出与线程同步以及线程调度相关的方法。
- wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁;
- sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理InterruptedException异常;
- notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且与优先级无关;
- notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态;
13. 编写多线程程序有几种实现方式?
①继承Thread类② 实现Runnable接口③@Sync注解
两种方式都要通过重写run()方法来定义线程的行为,推荐使用②,因为Java中的继承是单继承,一个类有一个父类,如果继承了Thread类就无法再继承其他类了,显然使用Runnable接口更为灵活。
14. synchronized关键字的用法?
synchronized关键字可以将对象或者方法标记为同步,以实现对对象和方法的互斥访问,可以用synchronized(对象) { … }定义同步代码块,或者在声明方法时将synchronized作为方法的修饰符。
15. Redis 分布式锁
Redis分布式锁,借助Redisson实现。Redisson提供了可重入锁,公平锁,联锁,红锁、读写锁等功能。它的锁,可通过参数设置超时自动解锁,还自带WhatchDog, 在会在Redisson实例在关闭前,不断的延长锁的有效期,默认检测间隔是30秒,可以通过参数Config.lockWatchDogTimeout进行修改。
常用的可重入锁,实现了java.util.concurrent.locks.Lock接口,通过redisson.getLock()来获取,操作是lock/unlock。它的特点是:如果操作的对象已经被另外的实例锁定后,会在这个对象进行继续加锁,加锁次数会累加
公平锁,实现了java.util.concurrent.locks.Lock接口,通过redisson.getFairLock()来获取,特点是,如果有多个实例使用了公平锁,rdisson会根据调用的先后顺序进行排队处理
读写锁,实现了java.util.concurrent.locks.ReadWriteLock接口,通过redisson.getReadWriteLock()获取,使用的时候,通过.readLock/.writeLock.lock()实现。
联锁RedissonMultiLock和红锁RedissionRedLock,都是将多个锁对象同时加锁,区别是联锁需要所有的都加锁成功,就算成功;而红锁,只需要大部分加锁成功即算成功
算法
1. 二分查找
public static int sort(int[] array, int a, int lo, int hi) {
if (lo <= hi) {
int mid = (lo + hi) / 2;
if (a == array[mid]) {
return mid + 1;
} else if (a > array[mid]) {
return sort(array, a, mid + 1, hi);
} else {
return sort(array, a, lo, mid - 1);
}
}
return -1;
}
2. 冒泡算法
算法思路:
1、比较相邻的元素。如果第一个比第二个大,就交换它们两个;
2、对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;
3、针对所有的元素重复以上的步骤,除了最后一个;
4、重复步骤1~3,直到排序完成。
public class
{
public static void main(String[] args) {
int array[] = {1,2,4,3,9,7,8,6};
for( int i = 0;i < array.length - 1;i++ ){
for( int j = 0;j < array.length - i - 1;j++ ){
if( array[j] > array[j+1] ){
int temp = array[j];
array[j] = array[j+1];
array[j+1] = temp;
}
}
}
for( int i = 0 ; i < array.length ; i++ ){
System.out.print(array[i]+" ");
}
}
}
//运行结果:
//1 2 3 4 6 7 8 9
3. 选择排序
首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
public class XuanZePaiXu {
public static void main(String[] args) {
int minIndex = 0;
int temp = 0;
int array[] = {1,2,4,3,9,7,8,6};
for(int i = 0;i < array.length;i++){
minIndex = i; //先假设最开始的元素为最小的元素
for( int j = i + 1;j < array.length;j++ ){
if( array[j] < array[minIndex] ){ // 寻找最小的数
minIndex = j; // 将最小数的索引保存
}
}
temp = array[minIndex]; //将此轮的最小元素和最开始的元素交换
array[minIndex] = array[i];
array[i] = temp;
}
for( int i = 0;i < array.length;i++ ){
System.out.print(array[i]+" ");
}
}
}
//运行结果:
//1 2 3 4 6 7 8 9
数据库
1. 什么时候不用索引
① 查询中很少使用到的列 不应该创建索引,如果建立了索引然而还会降低mysql的性能和增大了空间需求.
② 很少数据的列也不应该建立索引,比如 一个性别字段 0或者1,在查询中,结果集的数据占了表中数据行的比例比较大,mysql需要扫描的行数很多,增加索引,并不能提高效率
③ 定义为text和image和bit数据类型的列不应该增加索引,
④ 当表的修改(UPDATE,INSERT,DELETE)操作远远大于检索(SELECT)操作时不应该创建索引,这两个操作是互斥的关系
2. 索引优化
① 最左前缀匹配原则
② 对 where,on,group by,order by 中出现的列使用索引
③ 尽量选择区分度高的列作为索引
④ 对较小的数据列使用索引,这样会使索引文件更小
⑤ 对于like查询,”%”不要放在前面。
3. 性能优化
1、根据服务层面
配置mysql性能优化参数;
2、从系统层面增强mysql的性能:优化数据表结构
① 将字段较多的表分解成多个表
对于字段较多的表,如果有些字段的使用频率很低,可以将这些字段分离出来形成新表。因为当一个表的数据量很大时,会由于存在使用频率低的字段而使查询速度变慢。
② 增加中间表
对于经常需要联合查询的表,可以建立中间表以提高查询效率。通过建立中间表,把需要经常联合查询的数据插入中间表,然后将原来的联合查询改为对中间表的查询,以此来提高查询效率。
3、从数据库层面增强性能
优化SQL语句,合理使用字段索引。
4、从代码层面增强性能:使用缓存和NoSQL数据库方式存储,如MongoDB/Memcached/Redis来缓解高并发下数据库查询的压力。
5、减少数据库操作次数,尽量使用数据库访问驱动的批处理方法。
6、不常使用的数据迁移备份,避免每次都在海量数据中去检索。
7、提升数据库服务器硬件配置,或者搭建数据库集群。
8、编程手段防止SQL注入:使用JDBC PreparedStatement按位插入或查询;正则表达式过滤(非法字符串过滤);
框架
Spring IOC
IOC就是控制反转,是指创建对象的控制权的转移,以前创建对象的主动权和时机是由自己把控的,而现在这种权力转移到Spring容器中,并由容器根据配置文件去创建实例和管理各个实例之间的依赖关系,对象与对象之间松散耦合,也利于功能的复用。DI依赖注入,和控制反转是同一个概念的不同角度的描述,即 应用程序在运行时依赖IoC容器来动态注入对象需要的外部资源。
最直观的表达就是,IOC让对象的创建不用去new了,可以由spring自动生产,使用java的反射机制,根据配置文件在运行时动态的去创建对象以及管理对象,并调用对象的方法的。
Spring的IOC有三种注入方式 :构造器注入、setter方法注入、根据注解注入。
Spring Bean的生命周期
首先说一下Servlet的生命周期:实例化,初始init,接收请求service,销毁destroy;
Spring上下文中的Bean生命周期也类似,如下:
(1)实例化Bean:
对于BeanFactory容器,当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用createBean进行实例化。对于ApplicationContext容器,当容器启动结束后,通过获取BeanDefinition对象中的信息,实例化所有的bean。
(2)设置对象属性(依赖注入):
实例化后的对象被封装在BeanWrapper对象中,紧接着,Spring根据BeanDefinition中的信息 以及 通过BeanWrapper提供的设置属性的接口完成依赖注入。
(3)处理Aware接口:
接着,Spring会检测该对象是否实现了xxxAware接口,并将相关的xxxAware实例注入给Bean:
①如果这个Bean已经实现了BeanNameAware接口,会调用它实现的setBeanName(String beanId)方法,此处传递的就是Spring配置文件中Bean的id值;
②如果这个Bean已经实现了BeanFactoryAware接口,会调用它实现的setBeanFactory()方法,传递的是Spring工厂自身。
③如果这个Bean已经实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring上下文;
(4)BeanPostProcessor:
如果想对Bean进行一些自定义的处理,那么可以让Bean实现了BeanPostProcessor接口,那将会调用postProcessBeforeInitialization(Object obj, String s)方法。
(5)InitializingBean 与 init-method:
如果Bean在Spring配置文件中配置了 init-method 属性,则会自动调用其配置的初始化方法。
(6)如果这个Bean实现了BeanPostProcessor接口,将会调用postProcessAfterInitialization(Object obj, String s)方法;由于这个方法是在Bean初始化结束时调用的,所以可以被应用于内存或缓存技术;
以上几个步骤完成后,Bean就已经被正确创建了,之后就可以使用这个Bean了。
(7)DisposableBean:
当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean这个接口,会调用其实现的destroy()方法;
(8)destroy-method:
最后,如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法。
Spring 事务
声明式事务最大的优点就是不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明或通过@Transactional注解的方式,便可以将事务规则应用到业务逻辑中。
Spring Boot 的核心注解是哪个?它主要由哪几个注解组成的?
启动类上面的注解是@SpringBootApplication,它也是 Spring Boot 的核心注解,主要组合包含了以下 3 个注解:
@SpringBootConfiguration:组合了 @Configuration 注解,实现配置文件的功能。
@EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项,如关闭数据源自动配置功能: @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })。
@ComponentScan:Spring组件扫描。
Spring Boot 的装载顺序
你如何理解 Spring Boot 配置加载顺序?
在 Spring Boot 里面,可以使用以下几种方式来加载配置。
1)properties文件;
2)YAML文件;
3)系统环境变量;
4)命令行参数;
Spring Boot 启动流程
数据库
数据库优化
乐观锁、悲观锁
悲观锁
即总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次读写数据的时候,都加上锁。这样别人想拿到这个数据就会阻塞,直到拿到锁。例如数据库的:表锁,行锁,读锁,写锁等。
Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。
乐观锁
总是假设最好的情况,每次去拿数据的时候,都认为别人不会修改,但是在做更新的时候,会先判断是在此期间别人是不是有修改。主要用CAS算法来实现
CAS
设计模式
1. 简述一下你了解的设计模式。
所谓设计模式,就是一套被反复使用的代码设计经验的总结(情境中一个问题经过证实的一个解决方案)。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性
几个常用的设计模式:
-
工厂模式:工厂类可以根据条件生成不同的子类实例,这些子类有一个公共的抽象父类并且实现了相同的方法,但是这些方法针对不同的数据进行了不同的操作(多态方法)。当得到子类的实例后,开发人员可以调用基类中的方法而不必考虑到底返回的是哪一个子类的实例。
-
代理模式:给一个对象提供一个代理对象,并由代理对象控制原对象的引用。
-
适配器模式:把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起使用的类能够一起工作。
-
单例模式:一个类只有一个实例,即一个类只有一个对象实例。
2. 单例模式
单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
单例模式的特点:
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
代码:
public class DL {
private volatile static DL singleton;
private DL (){}
public static DL getInstance() {
if (singleton == null) {
synchronized (DL.class) {
if (singleton == null) {
singleton = new DL();
}
}
}
return singleton;
}
}
3. 为什么使用单例模式呢
当我们需要确保某个类只要一个对象,或创建一个类需要消耗的资源过多,如访问IO和数据库操作等,这时就需要考虑使用单例模式了。
比如:当我们使用多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行
数据库连接实例主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为用单例模式来维护,就可以大大降低这种损耗。
4. 工厂模式
桌子接口:Desk
/**
* 桌子接口
*/
public interface Desk {
String getType();
}
木质桌子:WoodenDesk
/**
* 木质桌子
*/
public class WoodenDesk implements Desk{
private String type = "木质桌";
@Override
public String getType() {
return type;
}
}
塑料桌子:PlasticDesk
/**
* 塑料桌
*/
public class PlasticDesk implements Desk {
private String type = "塑料桌";
@Override
public String getType() {
return type;
}
}
桌子工厂接口:DeskFactory
/**
* 桌子工厂接口
*/
public interface DeskFactory {
Desk createDesk();
}
木质桌子工厂:WoodenDeskFactory
/**
* 木质桌子工厂
*/
public class WoodenDeskFactory implements DeskFactory{
@Override
public Desk createDesk(){
return new WoodenDesk();
}
}
塑料桌子工厂:
/**
* 塑料桌子工厂
*/
public class PlasticDeskFactory implements DeskFactory {
@Override
public Desk createDesk() {
return new PlasticDesk();
}
}
测试类:Clienter
public class Clienter {
public static void main(String[] args) {
DeskFactory factory = new WoodenDeskFactory();
Desk desk = factory.createDesk();
System.out.println(desk.getType());
}
}
linux
1. 常用命令
ps -ef|grep xxx 查看进程
lsof -i:端口号/netstat -tunlp |grep 端口号 查看占用某端口的进程
2. Vi的模式
一.命令模式
1.光标移动
a、字符级
左(h) 下(j) 上(k) 右(l)
b、单词级
w word移动到下个单词首字母
b before上个单词首字母
e end下个单词结尾
c、行级
0 行首
$ 行尾
d、段落级{ 上 } 下(没必要记忆)
e、屏幕级 H屏首 L屏尾(没必要记忆)
f、文档级
G 文档尾部
nG 文档第n行
gg 文档第一行
crtl+f <–> pagedown向下翻页
crtl+b <–> pageup向上翻页
n+enter 向下移动n行
2.内容删除
dd //删除当前行
ndd //自当前行向下删除n行
x //删除当前字符
cw //删除光标所在字母后面的字符
3.内容复制
yy //复制光标当前行
nyy //自当前行复制n行
p //对(删除)复制的内容进行粘贴
4.相关快捷操作
u //撤销
. //重复上次操作
二.编辑模式 (编辑模式下可以输入任意内容)
a 光标向后移动一位
i 当前位置
o 另起新行
s 删除光标所在字符
r 替换光标所在字符
三.尾行模式 (用于保存内容、查找替换、设置行号等功能性操作)
:q //quit退出vi编辑器
:w //write保存修改的内容
:wq //保存并退出
:q! //强制退出,当对文本内容作了修改而不想要保存时
:w! //强制保存,当没有文本的写权限时
:set number 或 :set nu //显示行号
:set nonumber 或 :set nonu //取消显示行号
:/内容/ 或 /内容 //查找指定内容 //n将光标移动到下一个目标 //N上一个
:n //跳转到第n行
😒/targetContent/newContent //替换当前行第一个targetContent为newContent
😒/targetContent/newContent/g //整行相应内容替换
:%s/targetContent/newContent <==> :1,$s/word1/word2/g //整个文本相应内容替换
:n1,n2s/word1/word2/gc //:100,200s/word1/word2/g 把100行到200行之间的word1替换为word2,并提示是否替换 c->confirm