java高级面试题总结
目录1、java基础2、数据库3、java多线程4、jvm、java内存模型5、java框架(spring boot,sprint cloud)6、中间件7、算法8、网络1、java基础###hashmap原理?扩容##arraylist原理?扩容##jdk1.8新特性?2、数据库##mysql索引优化https://www.cnblogs.com/qlqwjy/p/8592043....
1、java基础
1.1、hashmap原理?扩容
HashMap基于hashing原理,我们通过put()和get()方法储存和获取对象。当我们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算hashcode,找到bucket位置来储存键值对对象。当获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。HashMap使用链表来解决碰撞问题,当发生碰撞了,对象将会储存在链表的下一个节点中。 HashMap在每个链表节点中储存键值对对象。
当两个不同的键对象的hashcode相同时会发生什么? 它们会储存在同一个bucket位置的链表中。键对象的equals()方法用来找到键值对。
hashmap遍历
public class MapDemo2 {
public static void main(String[] args) {
Map<Integer,String> map = new HashMap<Integer, String>();
map.put(1, "abc");
map.put(2, "bcd");
map.put(3, "cde");
//1. 调用map集合方法entrySet()将集合中的映射关系对象,存储到Set集合
Set<Map.Entry <Integer,String> > set = map.entrySet();
//2. 迭代Set集合
Iterator<Map.Entry <Integer,String> > it = set.iterator();
while(it.hasNext()){
// 3. 获取出的Set集合的元素,是映射关系对象
// it.next 获取的是什么对象,也是Map.Entry对象
Map.Entry<Integer, String> entry = it.next();
//4. 通过映射关系对象方法 getKet, getValue获取键值对
Integer key = entry.getKey();
String value = entry.getValue();
System.out.println(key+"...."+value);
}
System.out.println("=========================");
for(Map.Entry<Integer, String> entry : map.entrySet()){
System.out.println(entry.getKey()+"..."+entry.getValue());
}
}
}
https://blog.csdn.net/kun_998/article/details/89480637
1.2、arraylist原理?扩容
ArrayList的扩容主要发生在向ArrayList集合中添加元素的时候。由add()方法的分析可知添加前必须确保集合的容量能够放下添加的元素。主要经历了以下几个阶段:
第一,在add()方法中调用ensureCapacityInternal(size + 1)方法来确定集合确保添加元素成功的最小集合容量minCapacity的值。参数为size+1,代表的含义是如果集合添加元素成功后,集合中的实际元素个数。换句话说,集合为了确保添加元素成功,那么集合的最小容量minCapacity应该是size+1。在ensureCapacityInternal方法中,首先判断elementData是否为默认的空数组,如果是,minCapacity为minCapacity与集合默认容量大小中的较大值。
第二,调用ensureExplicitCapacity(minCapacity)方法来确定集合为了确保添加元素成功是否需要对现有的元素数组进行扩容。首先将结构性修改计数器加一;然后判断minCapacity与当前元素数组的长度的大小,如果minCapacity比当前元素数组的长度的大小大的时候需要扩容,进入第三阶段。
第三,如果需要对现有的元素数组进行扩容,则调用grow(minCapacity)方法,参数minCapacity表示集合为了确保添加元素成功的最小容量。在扩容的时候,首先将原元素数组的长度增大1.5倍(oldCapacity + (oldCapacity >> 1)),然后对扩容后的容量与minCapacity进行比较:① 新容量小于minCapacity,则将新容量设为minCapacity;②新容量大于minCapacity,则指定新容量。最后将旧数组拷贝到扩容后的新数组中。
arrays.copyof()方法,返回一个新数组。
https://blog.csdn.net/aizhuyanwei/article/details/78493495
https://blog.csdn.net/weixin_36378917/article/details/81812210
1.3、jdk1.8新特性?
https://blog.csdn.net/qq_29411737/article/details/80835658
1.4、在java8中,java.time包下主要包含下面几个主要的类:
Instant:时间戳
Duration:持续时间,时间差
LocalDate:只包含日期,比如:2019-10-20
LocalTime:只包含时间,比如:23:12:10
LocalDateTime:包含日期和时间,比如:2019-10-20 23:14:21
Period:时间段
ZoneOffset:时区偏移量,比如:+8:00
ZonedDateTime:带时区的时间
Clock:时钟,比如获取目前美国纽约的时间
1.5、completablefuture
https://www.jianshu.com/p/6bac52527ca4
1.6、Integer用==进行值比较,什么时候相等,什么时候不等?
在值域为 [-128,127]之间,用==符号来比较Integer的值,是相等的。
在Integer类装入内存中时,会执行其内部类中静态代码块进行其初始化工作,做的主要工作就是把 [-128,127]之间的数包装成Integer类并把其对应的引用存入到cache数组中,这样在方法区中开辟空间存放这些静态Integer变量,同时静态cache数组也存放在这里,供线程享用,这也称静态缓存。我们知道在Java的对象是引用的,所以当用Integer 声明初始化变量时,会先判断所赋值的大小是否在-128到127之间,若在,则利用静态缓存中的空间并且返回对应cache数组中对应引用,存放到运行栈中,而不再重新开辟内存。
1.7 ==和equals()的区别?
1, = = 是一个java的运算符,比较的是等号两边的地址值是否相等。equals()是一个object类的方法,具体要看类的实现。
2,基本数据类型在JVM的分布实在虚拟机栈的中的局部变量表里,使用= =和equals()比较基本数据类型是一样的效果。
3,string类重写了equals()方法,比较的是string的值是不是相等,具体实现是通过遍历char数组每个元素对比。
int i = 11;
int j = 11;
i == j //true
i.equels(j) //基本数据类型没有equals方法
--------------------------------------------------
Integer i = 11;
Integer j = 11;
i == j // true
i.equels(j) // true
------------------------------------------------
String a = new String ("1");
String b = new String ("1");
a == b //false
a.equelse(b) // true
-----------------------------------------
String a ="1";
String b = "1";
a == b // true
a.equelse(b) // true
1.8、java IO流中有哪些?转换流有哪些?
字节流:OutputStream InputStream 字节缓冲流:BufferedOutputStream BufferedInputStream
字符流:Writer Reader 字符缓冲流:BufferedWriter BufferedReader
转换流:InputStreamReader:字节流转换为字符流
OutputStreamWriter:字符流转换为字节流
1.9、java NIO
java NIO是在jdk1.4加入的,为了解决原来javaIO的阻塞问题,可以理解为New IO 护着NoBlockIO。
Java NIO 由以下几个核心部分组成:
Channels
Buffers
Selectors
新的输入/输出 (NIO) 库是在 JDK 1.4 中引入的。NIO 弥补了原来的 I/O 的不足,它在标准 Java 代码中提供了高速的、面向块的 I/O。通过定义包含数据的类,以及通过以块的形式处理这些数据,NIO 不用使用本机代码就可以利用低级优化,这是原来的 I/O 包所无法做到的。
https://ifeve.com/overview/
1.10 hashmap和hashset的关系和区别
关系:
hashset是由hashmap实现的
区别:
HashMap | HashSet |
---|---|
HashMap实现了Map接口 | HashSet实现了Set接口 |
HashMap储存键值对 | HashSet仅仅存储对象 |
使用put()方法将元素放入map中 | 使用add()方法将元素放入set中 |
HashMap中使用键对象来计算hashcode值 | HashSet使用成员对象来计算hashcode值,对于两个对象来说hashcode可能相同,所以equals()方法用来判断对象的相等性,如果两个对象不同的话,那么返回false |
HashMap比较快,因为是使用唯一的键来获取对象 | HashSet较HashMap来说比较慢 |
1.11 传统单例模式双重检查锁存在的问题
https://www.cnblogs.com/a154627/p/10046147.html
2、数据库
2.1、mysql索引优化
https://www.cnblogs.com/qlqwjy/p/8592043.html
2.2、创建索引的依据?
1、表的主键、外键必须有索引;
2、数据量超过300的表应该有索引;
3、经常与其他表进行连接的表,在连接字段上应该建立索引;
4、经常出现在Where子句中的字段,特别是大表的字段,应该建立索引;
5、索引应该建在选择性高的字段上;
6、索引应该建在小字段上,对于大的文本字段甚至超长字段,不要建索引;
7、复合索引的建立需要进行仔细分析;尽量考虑用单字段索引代替:
A、正确选择复合索引中的主列字段,一般是选择性较好的字段;
B、复合索引的几个字段是否经常同时以AND方式出现在Where子句中?单字段查询是否极少甚至没有?
如果是,则可以建立复合索引;否则考虑单字段索引;
C、如果复合索引中包含的字段经常单独出现在Where子句中,则分解为多个单字段索引;
D、如果复合索引所包含的字段超过3个,那么仔细考虑其必要性,考虑减少复合的字段;
E、如果既有单字段索引,又有这几个字段上的复合索引,一般可以删除复合索引;
8、频繁进行数据操作的表,不要建立太多的索引;
9、删除无用的索引,避免对执行计划造成负面影响;
2.3、mysql执行计划,explain各项参数代表什么意思?
id:选择标识符
select_type:表示查询的类型。
table:输出结果集的表
partitions:匹配的分区
type:表示表的连接类型
possible_keys:表示查询时,可能使用的索引
key:表示实际使用的索引
key_len:索引字段的长度
ref:列与索引的比较
rows:扫描出的行数(估算的行数)
filtered:按表条件过滤的行百分比
Extra:执行情况的描述和说明
注意:type标识mysql的访问类型,常见的有ALL、index、range、 ref、eq_ref、const、system、NULL(从左到右,性能从差到好)
ALL:Full Table Scan, MySQL将遍历全表以找到匹配的行
index: Full Index Scan,index与ALL区别为index类型只遍历索引树
range:只检索给定范围的行,使用一个索引来选择行
ref: 表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值
eq_ref: 类似ref,区别就在使用的索引是唯一索引,对于每个索引键值,表中只有一条记录匹配,简单来说,就是多表连接中使用primary key或者 unique key作为关联条件
const、system: 当MySQL对查询某部分进行优化,并转换为一个常量时,使用这些类型访问。如将主键置于where列表中,MySQL就能将该查询转换为一个常量,system是const类型的特例,当查询的表只有一行的情况下,使用system
NULL: MySQL在优化过程中分解语句,执行时甚至不用访问表或索引,例如从一个索引列里选取最小值可以通过单独索引查找完成。
2.4、什么时候不会用到索引?
索引列上不能使用表达式或函数
用or分割开的条件,如果or左右两个条件中有一个列没有索引,则不会使用索引。
复合索引,如果索引列不是复合索引的第一部分,则不使用索引(即不符合最左前缀)
如果like是以‘%’开始的,则该列上的索引不会被使用。
如果MySQL估计使用索引比全表扫描更慢,则不适用索引,
如果列为字符串,则where条件中必须将字符常量值加引号,否则即使该列上存在索引,也不会被使用
2.5、mysql存储引擎
https://www.cnblogs.com/xiaohaillong/p/6079551.html
2.6、如何修改大表的表结构(增加一列)
在主服务器上建立新表,表结构为修改后的表结构,将旧表数据同步到新表。
在旧表中建立触发器,使旧表更新的数据同步到新表中。
数据同步完成后,在旧表中建立排它锁,重命名新表为旧表,删除旧表。
可使用工具实现上面的操作:pt-online-schema-change
2.7、数据库隔离级别
Mysql查看当前隔离级别:show variables like ‘%iso%’
隔离级别 隔离级别的值 导致的问题
Read-Uncommitted 0 导致脏读
Read-Committed 1 避免脏读,允许不可重复读和幻读
Repeatable-Read 2 避免脏读,不可重复读,允许幻读
Serializable 3 串行 化读,事务只能一个一个执行,避免了脏读、不可重复读、幻读。执行效率慢,使用时慎重
脏读:一事务对数据进行了增删改,但未提交,另一事务可以读取到未提交的数据。如果第一个事务这时候回滚了,那么第二个事务就读到了脏数据。
不可重复读:一个事务中发生了两次读操作,第一次读操作和第二次操作之间,另外一个事务对数据进行了修改,这时候两次读取的数据是不一致的。
幻读:第一个事务对一定范围的数据进行批量修改,第二个事务在这个范围增加一条数据,这时候第一个事务就会丢失对新增数据的修改。
总结:
隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。
大多数的数据库默认隔离级别为 Read Commited,比如 SqlServer、Oracle
少数数据库默认隔离级别为:Repeatable Read 比如: MySQL InnoDB
2.7、为什么要使用索引
索引大大减少存储引擎需要扫描的数据量
索引可以帮助我们进项排序避免使用临时表
索引可以把随机io变为顺序io
2.8、联合索引什么时候回用到,什么时候不会用到?
最左匹配原则:b+树的数据项是复合的数据结构,比如(name,age,sex)的时候,b+数是按照从左到右的顺序来建立搜索树的,比如当(张三,20,F)这样的数据来检索的时候,b+树会优先比较name来确定下一步的所搜方向,如果name相同再依次比较age和sex,最后得到检索的数据;但当(20,F)这样的没有name的数据来的时候,b+树就不知道下一步该查哪个节点,因为建立搜索树的时候name就是第一个比较因子,必须要先根据name来搜索才能知道下一步去哪里查询。比如当(张三,F)这样的数据来检索时,b+树可以用name来指定搜索方向,但下一个字段age的缺失,所以只能把名字等于张三的数据都找到,然后再匹配性别是F的数据了, 这个是非常重要的性质,即索引的最左匹配特性。
2.9、mysql的limit用法?
SELECT * FROM table LIMIT 5,10; // 检索记录行 6-15
SELECT * FROM table LIMIT 10; // 检索前10条记录
2.10、mysql的select 、where、orderby、having的 执行顺序?having的用法?
逻辑查询处理阶段简介
- FROM:对FROM子句中的前两个表执行笛卡尔积(Cartesian product)(交叉联接),生成虚拟表VT1
- ON:对VT1应用ON筛选器。只有那些使<join_condition>为真的行才被插入VT2。
- OUTER(JOIN):如 果指定了OUTER JOIN(相对于CROSS JOIN 或(INNER JOIN),保留表(preserved table:左外部联接把左表标记为保留表,右外 部 联接把右表标记为保留表,完全外部联接把两个表都标记为保留表)中未找到匹配的行将作为外部行添加到 VT2,生成VT3.如果FROM子句包含 两个 以上的表,则对上一个联接生成的结果表和下一个表重复执行步骤1到步骤3,直到处理完所有的表为止。
- WHERE:对VT3应用WHERE筛选器。只有使<where_condition>为true的行才被插入VT4.
- GROUP BY:按GROUP BY子句中的列列表对VT4中的行分组,生成VT5.
- CUBE|ROLLUP:把超组(Suppergroups)插入VT5,生成VT6.
- HAVING:对VT6应用HAVING筛选器。只有使<having_condition>为true的组才会被插入VT7.
- SELECT:处理SELECT列表,产生VT8.
- DISTINCT:将重复的行从VT8中移除,产生VT9.
- ORDER BY:将VT9中的行按ORDER BY 子句中的列列表排序,生成游标(VC10).
- TOP:从VC10的开始处选择指定数量或比例的行,生成表VT11,并返回调用者。
2.9、mysql的存储引擎的区别?
主要比较InnoDB和MYISAM
InnoDB支持事务,MyISAM不支持,对于InnoDB每一条SQL语言都默认封装成事务,自动提交,这样会影响速度,所以最好把多条SQL语言放在begin和commit之间,组成一个事务;
InnoDB是聚集索引,数据文件是和索引绑在一起的,必须要有主键,通过主键索引效率很高。但是辅助索引需要两次查询,先查询到主键,然后再通过主键查询到数据。因此,主键不应该过大,因为主键太大,其他索引也都会很大。MyISAM是非聚集索引,数据文件是分离的,索引保存的是数据文件的指针。主键索引和辅助索引是独立的。
InnoDB不保存表的具体行数,执行select count(*) from table时需要全表扫描。而MyISAM用一个变量保存了整个表的行数,执行上述语句时只需要读出该变量即可,速度很快;
Innodb不支持全文索引,而MyISAM支持全文索引,查询效率上MyISAM要高;
2.10 mysql表中删除重复的数据,保留一条
delete
FROM
order_pay_channel WHERE
order_id in (select a.order_id from (select count(1) as c ,order_id from order_pay_channel group by order_id having c > 1) a)
and
id not in (select b.max from (select count(1) as c ,max(id) max from order_pay_channel group by order_id having c > 1) b);
3、java多线程
3.1、java并发包java.util.concurrent及其子包都包括什么?
提供了比synchronized更加高级的各种同步结构,包括CountDownLatch、CyclicBarrier、Semaphore等,可以实现更加丰富的多线程操作。比如利用Semaphore作为资源控制器,限制同时进行工作的线程数量。
各种线程安全的容器,比如最常见的ConcurrentHashMap、有序的ConcurrentSkipListMap,或者通过类似快照机制,实现线程安全的动态数组CopyOnWriteArrayList等。
各种并发队列实现,如各种BlockingQueue实现,比较典型的ArrayBlockingQueue、SynchorousQueue或针对特定场景的PriorityBlockingQueue等。
强大的Executor框架,可以创建各种不同类型的线程池,调度任务运行等。绝大部分情况下,不再需要自己从头实现线程池和任务调度器。
https://www.jianshu.com/p/850e3282a80a
3.2、synconsized和volatile关键字区别?
volatile本质是在告诉jvm当前变量在寄存器中的值是不确定的,需要从主存中读取,synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住.
volatile仅能使用在变量级别,synchronized则可以使用在变量,方法.
volatile仅能实现变量的修改可见性,但不具备原子特性,而synchronized则可以保证变量的修改可见性和原子性.
volatile不会造成线程的阻塞,而synchronized可能会造成线程的阻塞.
volatile标记的变量不会被编译器优化,而synchronized标记的变量可以被编译器优化.
3.3、实现线程池的方式?
阿里的 Java开发手册,上面有线程池的一个建议:
线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,
这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
参考
3.4、公平锁与非公平锁
https://www.jianshu.com/p/eaea337c5e5b
3.5、为什么不适用Excutors来创建线程池
https://blog.csdn.net/weixin_41888813/article/details/90769126
3.6、ReentraneLock & AQS
ReentranceLock可重如锁
https://www.cnblogs.com/binbang/p/6425248.html
https://www.jianshu.com/p/da9d051dcc3d
3.7、手写线程死锁
public class TestDeadLock {
private static Object lock1 = new Object();
private static Object lock2 = new Object();
public static void main(String[] args) {
deaLock();
}
private static void deaLock(){
Thread thread1 = new Thread(new Runnable() {
public void run() {
synchronized (lock1){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock2){
System.out.println("Thread1执行print方法");
}
}
}
});
thread1.setName("thread1");
thread1.start();
Thread thread2 = new Thread(new Runnable() {
public void run() {
synchronized (lock2){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock1){
System.out.println("Thread2执行print方法");
}
}
}
});
thread2.setName("thread2");
thread2.start();
}
}
3.8、AQS原理
全称 AbstractQueuedSynchronizer ,抽象队列同步器。这个类在 java.util.concurrent.locks 包下。
AQS 就是一个抽象类,主要用来构建锁和同步器。
AQS 核心思想:如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制 AQS 是基于 CLH 锁 (Craig, Landin, and Hagersten locks) 实现的。CLH 锁是对自旋锁的一种改进,是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系),暂时获取不到锁的线程将被加入到该队列中。AQS 将每条请求共享资源的线程封装成一个 CLH 队列锁的一个结点(Node)来实现锁的分配。在 CLH 队列锁中,一个节点表示一个线程,它保存着线程的引用(thread)、 当前节点在队列中的状态(waitStatus)、前驱节点(prev)、后继节点(next)。
AQS 使用 private volatile修饰的 int 成员变量 state 表示同步状态,通过CLH队列来完成获取资源线程的排队工作。state的值通过protected final修饰的getState()、setState()和compareAndSetState()进行修改,无法在子类中重写。
以可重入的互斥锁 ReentrantLock为例,当线程调用 lock() 方法时,会尝试通过 tryAcquire() 方法独占该锁,并让 state 的值加 1,假设线程 A 获取锁成功了,释放锁之前,A 线程自己是可以重复获取此锁的(state 会累加)。这就是可重入性的体现:一个线程可以多次获取同一个锁而不会被阻塞。但是,这也意味着,一个线程必须释放与获取的次数相同的锁,才能让 state 的值回到 0。
再以倒计时器 CountDownLatch 以例,任务分为 N 个子线程去执行,state 也初始化为 N(注意 N 要与线程个数一致)。这 N 个子线程开始执行任务,每执行完一个子线程,就调用一次 countDown() 方法。该方法会尝试使用 CAS(Compare and Swap) 操作,让 state 的值减少 1。当所有的子线程都执行完毕后(即 state 的值变为 0),CountDownLatch 会调用 unpark() 方法,唤醒主线程。这时,主线程就可以从 await() 方法(CountDownLatch 中的await() 方法而非 AQS 中的)返回,继续执行后续的操作。
3.9、synchronized锁升级
synchronized锁升级:偏向锁 → 轻量级锁 → 重量级锁
初次执行到synchronized代码块的时候,锁对象变成偏向锁(通过CAS修改对象头里的锁标志位),字面意思是“偏向于第一个获得它的线程”的锁。执行完同步代码块后,线程并不会主动释放偏向锁。当第二次到达同步代码块时,线程会判断此时持有锁的线程是否就是自己(持有锁的线程ID也在对象头里),如果是则正常往下执行。由于之前没有释放锁,这里也就不需要重新加锁。如果自始至终使用锁的线程只有一个,很明显偏向锁几乎没有额外开销,性能极高。
一旦有第二个线程加入锁竞争,偏向锁就升级为轻量级锁(自旋锁)。这里要明确一下什么是锁竞争:如果多个线程轮流获取一个锁,但是每次获取锁的时候都很顺利,没有发生阻塞,那么就不存在锁竞争。只有当某线程尝试获取锁的时候,发现该锁已经被占用,只能等待其释放,这才发生了锁竞争。
在轻量级锁状态下继续锁竞争,没有抢到锁的线程将自旋,即不停地循环判断锁是否能够被成功获取。获取锁的操作,其实就是通过CAS修改对象头里的锁标志位。先比较当前锁标志位是否为“释放”,如果是则将其设置为“锁定”,比较并设置是原子性发生的。这就算抢到锁了,然后线程将当前锁的持有者信息修改为自己。
长时间的自旋操作是非常消耗资源的,一个线程持有锁,其他线程就只能在原地空耗CPU,执行不了任何有效的任务,这种现象叫做忙等(busy-waiting)。如果多个线程用一个锁,但是没有发生锁竞争,或者发生了很轻微的锁竞争,那么synchronized就用轻量级锁,允许短时间的忙等现象。这是一种折衷的想法,短时间的忙等,换取线程在用户态和内核态之间切换的开销。
显然,此忙等是有限度的(有个计数器记录自旋次数,默认允许循环10次,可以通过虚拟机参数更改)。如果锁竞争情况严重,某个达到最大自旋次数的线程,会将轻量级锁升级为重量级锁(依然是CAS修改锁标志位,但不修改持有锁的线程ID)。当后续线程尝试获取锁时,发现被占用的锁是重量级锁,则直接将自己挂起(而不是忙等),等待将来被唤醒。在JDK1.6之前,synchronized直接加重量级锁,很明显现在得到了很好的优化。
一个锁只能按照 偏向锁、轻量级锁、重量级锁的顺序逐渐升级(也有叫锁膨胀的),不允许降级。
4、jvm、java内存模型
4.1、jvm内存模型?
https://www.nowcoder.com/discuss/151138?type=1
4.2、jvm调优具体调的那些参数?
|Xms| 初始堆大小,也可称最小堆大小 |
|-Xmx | 最大堆大小 |
|-Xmn|堆中新生代的大小 |
|-Xss | 每个线程的堆栈大小|
|-XX:PermSize| 设置持久代(perm gen)初始值 |
|-XX:MaxPermSize | 设置持久代最大值 |
5、java框架(spring boot,sprint cloud)
5.1、mybatis一级缓存,二级缓存
1)一级缓存 Mybatis的一级缓存是指SQLSession,一级缓存的作用域是SQlSession, Mabits默认开启一级缓存。 在同一个SqlSession中,执行相同的SQL查询时;第一次会去查询数据库,并写在缓存中,第二次会直接从缓存中取。 当执行SQL时候两次查询中间发生了增删改的操作,则SQLSession的缓存会被清空。 每次查询会先去缓存中找,如果找不到,再去数据库查询,然后把结果写到缓存中。 Mybatis的内部缓存使用一个HashMap,key为hashcode+statementId+sql语句。Value为查询出来的结果集映射成的java对象。 SqlSession执行insert、update、delete等操作commit后会清空该SQLSession缓存。
2)二级缓存 二级缓存是mapper级别的,Mybatis默认是没有开启二级缓存的。 第一次调用mapper下的SQL去查询用户的信息,查询到的信息会存放代该mapper对应的二级缓存区域。 第二次调用namespace下的mapper映射文件中,相同的sql去查询用户信息,会去对应的二级缓存内取结果。 如果调用相同namespace下的mapepr映射文件中增删改sql,并执行了commit操作,此时会清空该缓存
5.2、mybatis ${}和#{}的区别?
#{}的执行结果是给参数添加‘’,
美元符{}的执行结果是直接将参数替换,存在sql注入的风险。
能用#{}的地方就不要用${}
5.3、mybatis中like模糊查询中#和$的使用
如果在like语句中想使用#{},通过字符串拼接或者concat拼接的方式实现
1、表达式: name like"%“#{name}”%"
2、name like concat(concat(‘%’,#{username}),‘%’)
concat拼接可以这么写name like CONCAT(‘%’,‘${name}’,‘%’)
https://blog.csdn.net/u010398771/article/details/70768280
5.3、sprint cloud 的常用组件?
Spring Cloud Eureka(服务治理)
Spring Cloud Ribbon(客户端负载均衡)
Spring Cloud Hystrix(服务容错保护)
Spring Cloud Feign(声明式服务调用)
Spring Cloud Zuul(API网关服务)
Spring Cloud Config(分布式配置中心)
springcloud相关面试题
6、中间件
6.1、Redis面试
https://blog.csdn.net/Butterfly_resting/article/details/89668661
6.2、redis实现分布式锁原理,使用redis实现分布式锁有什么问题?
https://www.cnblogs.com/gxyandwmm/p/9588383.html
6.3、zookeeper如何实现公平锁
获取锁:首先在zookeeper当中创建一个持久节点ParentLock。当第一个客户端client1想要获取锁时,需要在ParentLock这个节点下面创建一个临时顺序节点Lock1,之后client1查号ParentLock下面的所有临时顺序节点并排序,判断自己所创建的加点lock1是不是顺序最靠前的一个,如果是,则获取锁成功。这时候另一个客户端client2来获取锁,则在ParentLock下在创建一个临时顺序节点lock2,client2查找ParentLock下所有的临时顺序节点并排序,判断自己创建的节点lock2是不是顺序节点里最靠前的一个,发现lock2不是最小的,于是cline2向排序比他靠前的节点lock1注册Watch,用于监听lock1节点是否存在,这意味着clinet2抢锁失败,进入等待状态。这时候又有一个客户端client3前来获取锁,同样在ParentLock下创建临时顺序节点lock3,查找所有顺序节点并排序,判断自己创建的节点lock3是不是最靠前的一个,同样发现lock3不是最小的,于是client3向排序仅比他靠前的节点lock2注册watch,用于监听lock2节点是否存在,同样意味着clinet3抢锁失败,进入等待状态。这样一来,clinet1得到了锁,client2监听了client1,clinet3监听了clinet2,恰好行程一个等待队列。
释放锁:释放锁可以分为两种情况
当client1任务完成时,会显式的调用少出节点Lock1的指令来释放锁
当任务执行过程中,客户端奔溃,则client1会断开与zookeeper的连接,根据临时节点的特新,Lock1节点也会随之自动删除。由于client2一直监听这lock1的状态,当lock1节点删除后,client2会立刻受到通知,这时候client2在查询ParentLock下的所有节点,确认自己创建的节点lock2是不是最小的节点,如果是,则clinet2获取锁。
6.4、如何访问 redis 中的海量数据?避免事故产生
那我们如何去遍历大数据量呢?这个也是面试经常问的。我们可以采用redis的另一个命令scan。我们看一下scan的特点
1、复杂度虽然也是 O(n),但是它是通过游标分步进行的,不会阻塞线程
2、提供 count 参数,不是结果数量,是redis单次遍历字典槽位数量(约等于)
3、同 keys 一样,它也提供模式匹配功能;
4、服务器不需要为游标保存状态,游标的唯一状态就是 scan 返回给客户端的游标整数;
5、返回的结果可能会有重复,需要客户端去重复,这点非常重要;
6、单次返回的结果是空的并不意味着遍历结束,而要看返回的游标值是否为零。
一、scan命令格式
SCAN cursor [MATCH pattern] [COUNT count]
二、命令解释:scan 游标 MATCH <返回和给定模式相匹配的元素> count 每次迭代所返回的元素数量
SCAN命令是增量的循环,每次调用只会返回一小部分的元素。所以不会让redis假死SCAN命令返回的是一个游标,从0开始遍历,到0结束遍历
三、举例
redis > scan 0match user_token* count 5
- “6”
-
- “user_token:1000”
- “user_token:1001”
- “user_token:1010”
- “user_token:2300”
- “user_token:1389”
从0开始遍历,返回了游标6,又返回了数据,继续scan遍历,就要从6开始
redis > scan 6match user_token* count 5 - “10”
-
- “user_token:3100”
- “user_token:1201”
- “user_token:1410”
- “user_token:5300”
- “user_token:3389”
http://www.sohu.com/a/335031964_355142
6.5、rabbitmq如何实现消息的不丢失?
https://www.cnblogs.com/cnndevelop/p/12091348.html
6.6、dubbo原理
- client一个线程调用远程接口,生成一个唯一的ID(比如一段随机字符串,UUID等),Dubbo是使用AtomicLong从0开始累计数字的
- 将打包的方法调用信息(如调用的接口名称,方法名称,参数值列表等),和处理结果的回调对象callback,全部封装在一起,组成一个对象object
- 向专门存放调用信息的全局ConcurrentHashMap里面put(ID, object)
- 将ID和打包的方法调用信息封装成一对象connRequest,使用IoSession.write(connRequest)异步发送出去
- 当前线程再使用callback的get()方法试图获取远程返回的结果,在get()内部,则使用synchronized获取回调对象callback的锁, 再先检测是否已经获取到结果,如果没有,然后调用callback的wait()方法,释放callback上的锁,让当前线程处于等待状态。
- 服务端接收到请求并处理后,将结果(此结果中包含了前面的ID,即回传)发送给客户端,客户端socket连接上专门监听消息的线程收到消息,分析结果,取到ID,再从前面的ConcurrentHashMap里面get(ID),从而找到callback,将方法调用结果设置到callback对象里。
- 监听线程接着使用synchronized获取回调对象callback的锁(因为前面调用过wait(),那个线程已释放callback的锁了),再notifyAll(),唤醒前面处于等待状态的线程继续执行(callback的get()方法继续执行就能拿到调用结果了),至此,整个过程结束。
需要注意的是,这里的callback对象是每次调用产生一个新的,不能共享,否则会有问题;另外ID必需至少保证在一个Socket连接里面是唯一的。
两个问题
· 当前线程怎么让它“暂停”,等结果回来后,再向后执行?
答:先生成一个对象obj,在一个全局map里put(ID,obj)存放起来,再用synchronized获取obj锁,再调用obj.wait()让当前线程处于等待状态,然后另一消息监听线程等到服务端结果来了后,再map.get(ID)找到obj,再用synchronized获取obj锁,再调用obj.notifyAll()唤醒前面处于等待状态的线程。
· 正如前面所说,Socket通信是一个全双工的方式,如果有多个线程同时进行远程方法调用,这时建立在client server之间的socket连接上会有很多双方发送的消息传递,前后顺序也可能是乱七八糟的,server处理完结果后,将结果消息发送给client,client收到很多消息,怎么知道哪个消息结果是原先哪个线程调用的?
答:使用一个ID,让其唯一,然后传递给服务端,再服务端又回传回来,这样就知道结果是原先哪个线程的了。
6.7、Redis的几种数据结构及其使用场景
string介绍
string是以一种纯字符串作为value的形式存在的。也是这几种之中使用最多的数据结构。value可以存储json格式、数值型等。
string使用场景
string使用场景一般是存储简单的键值类型。比如用户信息,登录信息,配置信息等。还有一种用得比较多的是string的incr/decr操作,即自减/自增操作。调用它是原子性的,无论调用多少次,都一一计算成功。例如需要增减库存的操作。
尽管string的value可以存储很大,甚至500多MB的容量。但是在性能上来说,我们尽量存储value的值不要过1MB。
hash介绍
hash是一个集合,使用过hash的人都知道,hash的读取性能都一样快。在redis中,hash因为是一个集合,所以有两层。第一层是key:hash集合value,第二层是hashkey:string value。所以判断是否采用hash的时候可以参照有两层key的设计来做参考。并且注意的是,设置过期时间只能在第一层的key上面设置。
hash用场景
使用hash,一般是有那种需要两层key的应用场景,也可以是‘删除一个key可以删除所有内容’的场景。例如一个商品有很多规格,规格里面有不同的值。
如果需要删除商品时,可以一次性删除‘商品id’的key,则商品里面的所有规格也会删除,而不需要找到对应的规格再做处理。如果查找商品id与规格id1的商品时,则通过两个key查找即可。或者查找所有商品的规格,查找商品id即可。
需要注意的是,经过测试,在性能上来说一般hash里面的第二层key,不要超过200个为佳。尽管hash里面的key-value能达到500多MB的存储容量。
list介绍
list是一个集合,而在redis中,list的使用场景多种多样。在redis中,插入list中的值,只需要找到list的key即可,而不需要像hash一样插入两层的key。list是一种有序的、可重复的集合。
list使用场景
list可以使用左推、左拉、右推、右拉的方式。所以你可以使用list作为集合存储,比如存储某宝商铺里面的所有商品。
也可以用作轻量级别的队列来使用。左推左拉、右推右拉。
需要注意的是尽管redis可以使用推拉的队列模式,但是一定要注意场景。因为redis的队列是一种轻量级别的,没有队列重试、队列重放机制。消费完队列消息在redis代表已经删除了。
set介绍
set是一种无序的,不能重复的集合。并且在redis中,只有一个key。
set使用场景
如保存一些标签的名字。标签的名字不可以重复,顺序是可以无序的。
需要注意的是使用set一定不要存储大量的数据。value的值不宜过大,并且集合数量不宜过大。几百个集合的值,value不超过1MB为佳。
sortset介绍
sortset在redis中是有序的,并且不能重复。既有list的有序,又有set的不可重复性。
sortset使用场景
sortset的使用场景一般是排行榜之类的场景。
https://www.cnblogs.com/alunchen/p/9836170.html
6.8、redis的zset数据结构
Set数据结构类似于Set结构,只是ZSet结构中,每个元素都会有一个分值,然后所有元素按照分值的大小进行排列,相当于是一个进行了排序的链表。
如果ZSet是一个链表,而且内部元素是有序的,在进行元素插入和删除,以及查询的时候,就必须要遍历链表才行,时间复杂度就达到了O(n),这个在以单线程处理的Redis中是不能接受的。所以ZSet采用了一种跳跃表的实现。
跳跃表因为是一个根据分数权重进行排序的列表,可以再很多场景中进行应用,比如排行榜等等。
https://www.cnblogs.com/wuyizuokan/p/11108417.html
6.9、redis动态字符串sds
Redis String类型 没有使用C语言传统的字符串表示,而是自己构建了一种名为简单动态字符串(simple dynamic string,SDS) 的抽象类型,并将SDS用作Redis的默认字符串表示。在Redis里,包含字符串值得键值对在底层都是由DS来实现的。
SDS还被用作缓冲区(buffer):AOF模块中的AOF缓冲区,以及客户端状态中的输入缓冲区,都是由SDS实现的。
SDS相比较于c字符串的好处
取字符长度,SDS直接读取len属性。
杜绝缓冲区溢出。C字符串拼接时时假定已经为拼接的字符串预留了足够多的内存,如果这个假定不成立,那么就会产生缓冲区溢出。而SDS是这样做的:SDS的API会会先检查SDS的空间是否满足所需的要求,如果不满足,API自动将空间扩展至所需大小。
减少修改字符串长度时所需的内存重分配次数。
二进制安全。
兼容部分C字符串函数。
SDS空间分配策略:
通过未使用空间,SDS实现了空间预分配和惰性空间释放两种优化策略。
空间预分配: 用于优化SDS的字符增长操作:程序不仅会为SDS分配修改所必须要的空间,还会为SDS分配额外的未使用空间。(**具体:**1. len(SDS)< 1mB时,分配len(free)=len,2.如果len(SDS) >1mb,分配len(free)=1mb)
惰性空间释放: 用于优化SDS字符缩短操作:当缩短字符串时,程序并不立即使用内存重分配来回收缩短后多出来的字节,而是使用free属性将这些字节的数量记录起来,并等待将来使用。
7、算法与数据结构
7.1、快排算法(手写)
public class QuickSort {
public static void main(String[] args) {
int[] arr = { 49, 38, 65, 97, 23, 22, 76, 1, 5, 8, 2, 0, -1, 22 };
quickSort(arr, 0, arr.length - 1);
System.out.println("排序后:");
for (int i : arr) {
System.out.println(i);
}
}
private static void quickSort(int[] arr, int low, int high) {
if (low < high) {
// 找寻基准数据的正确索引
int index = getIndex(arr, low, high);
quickSort(arr, 0, index - 1);
quickSort(arr, index + 1, high);
}
}
private static int getIndex(int[] arr, int low, int high) {
int tmp = arr[low];
while (low < high) {
while (low < high && arr[high] >= tmp) {
high--;
}
arr[low] = arr[high];
while (low < high && arr[low] <= tmp) {
low++;
}
arr[high] = arr[low];
}
arr[low] = tmp;
return low;
}
}
7.2、链表反转
7.3、删除链表最后第n个值
7.4、二分查找
7.5、树,红黑树,b+树
7.6、手写一个队列
https://msd.misuland.com/pd/9786112860861905
8、网络
8.1、tcp,udp,http区别
8.2、三次握手,四次挥手
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)