java面试题

92 阅读9分钟

== 和 equals 的区别是什么?

  1. 对于基本类型,==比较的是值;
  2. 对于引用类型,==比较的是地址;
  3. equals不能用于基本类型的比较;
  4. 如果没有重写equals,equals就相当于==;
  5. 如果重写了equals方法,equals比较的是对象的内容;

打包和部署

  1. Spring和Spring Boot都支持maven和Gradle通用打包管理技术。
  2. Spring Boot相对Spring的一些优点:
  3. 提供嵌入式容器支持;
  4. 使用命令java -jar独立运行jar;
  5. 部署时可以灵活指定配置文件;
  6. 最近项目是分布式的项目,都是通过分项目打包部署,然后部署在docker中运行。

String 类的常用方法都有那些?

equals、length、contains、replace、split、hashcode、indexof、substring、trim、toUpperCase、toLowerCase、isEmpty等等。

(1)常见String类的获取功能

length:获取字符串长度; charAt(int index):获取指定索引位置的字符; indexOf(int ch):返回指定字符在此字符串中第一次出现处的索引; substring(int start):从指定位置开始截取字符串,默认到末尾; substring(int start,int end):从指定位置开始到指定位置结束截取字符串;

(2)常见String类的判断功能

equals(Object obj): 比较字符串的内容是否相同,区分大小写; contains(String str): 判断字符串中是否包含传递进来的字符串; startsWith(String str): 判断字符串是否以传递进来的字符串开头; endsWith(String str): 判断字符串是否以传递进来的字符串结尾; isEmpty(): 判断字符串的内容是否为空串"";

(3)常见String类的转换功能

byte[] getBytes(): 把字符串转换为字节数组; char[] toCharArray(): 把字符串转换为字符数组; String valueOf(char[] chs): 把字符数组转成字符串。valueOf可以将任意类型转为字符串; toLowerCase(): 把字符串转成小写; toUpperCase(): 把字符串转成大写; concat(String str): 把字符串拼接;

(4)常见String类的其他常用功能

replace(char old,char new) 将指定字符进行互换 replace(String old,String new) 将指定字符串进行互换 trim() 去除两端空格 int compareTo(String str) 会对照ASCII 码表 从第一个字母进行减法运算 返回的就是这个减法的结果,如果前面几个字母一样会根据两个字符串的长度进行减法运算返回的就是这个减法的结果,如果连个字符串一摸一样 返回的就是0。

接口和抽象类有什么区别?

(1)接口

接口使用interface修饰; 接口不能实例化; 类可以实现多个接口;

①java8之前,接口中的方法都是抽象方法,省略了public abstract。②java8之后;接口中可以定义静态方法,静态方法必须有方法体,普通方法没有方法体,需要被实现;

(2)抽象类

抽象类使用abstract修饰; 抽象类不能被实例化; 抽象类只能单继承; 抽象类中可以包含抽象方法和非抽象方法,非抽象方法需要有方法体; 如果一个类继承了抽象类,①如果实现了所有的抽象方法,子类可以不是抽象类;②如果没有实现所有的抽象方法,子类仍然是抽象类。

索引怎么定义,分哪几种

  1. b-tree索引,如果不建立索引的情况下,oracle就自动给每一列都加一个B 树索引;
  2. normal:普通索引
  3. unique:唯一索引
  4. bitmap:位图索引,位图索引特定于只有几个枚举值的情况,比如性别字段;
  5. 基于函数的索引

mysql 的内连接、左连接、右连接有什么区别?

  1. 内连接,显示两个表中有联系的所有数据;
  2. 左链接,以左表为参照,显示所有数据,右表中没有则以null显示
  3. 右链接,以右表为参照显示数据,,左表中没有则以null显示

Java 容器都有哪些?

(1)Collection

① set

HashSet、TreeSet

② list

ArrayList、LinkedList、Vector

(2)Map

HashMap、HashTable、TreeMap

说一下 HashSet 的实现原理? HashSet实际上是一个HashMap实例,数据存储结构都是数组+链表。

HashSet是基于HashMap实现的,HashSet中的元素都存放在HashMap的key上面,而value都是一个统一的对象PRESENT。

private static final Object PRESENT = new Object();

HashSet中add方法调用的是底层HashMap中的put方法,put方法要判断插入值是否存在,而HashSet的add方法,首先判断元素是否存在,如果存在则插入,如果不存在则不插入,这样就保证了HashSet中不存在重复值。 通过对象的hashCode和equals方法保证对象的唯一性。

ArrayList 和 LinkedList 的区别是什么?

ArrayList是动态数组的数据结构实现,查找和遍历的效率较高;

LinkedList 是双向链表的数据结构,增加和删除的效率较高;

Redis,什么是缓存穿透?怎么解决?

1、缓存穿透

一般的缓存系统,都是按照key去缓存查询,如果不存在对用的value,就应该去后端系统查找(比如DB数据库)。一些恶意的请求会故意查询不存在的key,请求量很大,就会对后端系统造成很大的压力。这就叫做缓存穿透。

2、怎么解决?

对查询结果为空的情况也进行缓存,缓存时间设置短一点,或者该key对应的数据insert之后清理缓存。

对一定不存在的key进行过滤。可以把所有的可能存在的key放到一个大的Bitmap中,查询时通过该Bitmap过滤。

3、缓存雪崩

当缓存服务器重启或者大量缓存集中在某一时间段失效,这样在失效的时候,会给后端系统带来很大的压力,导致系统崩溃。

4、如何解决?

在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其它线程等待; 做二级缓存; 不同的key,设置不同的过期时间,让缓存失效的时间尽量均匀;

Redis怎么实现分布式锁?

使用Redis实现分布式锁

redis命令:set users 10 nx ex 12   原子性命令

//使用uuid,解决锁释放的问题 @GetMapping public void testLock() throws InterruptedException { String uuid = UUID.randomUUID().toString(); Boolean b_lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 10, TimeUnit.SECONDS); if(b_lock){ Object value = redisTemplate.opsForValue().get("num"); if(StringUtils.isEmpty(value)){ return; } int num = Integer.parseInt(value + ""); redisTemplate.opsForValue().set("num",++num); Object lockUUID = redisTemplate.opsForValue().get("lock"); if(uuid.equals(lockUUID.toString())){ redisTemplate.delete("lock"); } }else{ Thread.sleep(100); testLock(); } } 备注:可以通过lua脚本,保证分布式锁的原子性。

怎么保证缓存和数据库数据的一致性?

1、淘汰缓存

数据如果为较为复杂的数据时,进行缓存的更新操作就会变得异常复杂,因此一般推荐选择淘汰缓存,而不是更新缓存。

2、选择先淘汰缓存,再更新数据库

假如先更新数据库,再淘汰缓存,如果淘汰缓存失败,那么后面的请求都会得到脏数据,直至缓存过期。

假如先淘汰缓存再更新数据库,如果更新数据库失败,只会产生一次缓存穿透,相比较而言,后者对业务则没有本质上的影响。

3、延时双删策略

如下场景:同时有一个请求A进行更新操作,另一个请求B进行查询操作。

请求A进行写操作,删除缓存 请求B查询发现缓存不存在 请求B去数据库查询得到旧值 请求B将旧值写入缓存 请求A将新值写入数据库 次数便出现了数据不一致问题。采用延时双删策略得以解决。

public void write(String key,Object data){ redisUtils.del(key); db.update(data); Thread.Sleep(100); redisUtils.del(key); } 这么做,可以将1秒内所造成的缓存脏数据,再次删除。这个时间设定可根据俄业务场景进行一个调节。

4、数据库读写分离的场景

两个请求,一个请求A进行更新操作,另一个请求B进行查询操作。

请求A进行写操作,删除缓存 请求A将数据写入数据库了, 请求B查询缓存发现,缓存没有值 请求B去从库查询,这时,还没有完成主从同步,因此查询到的是旧值 请求B将旧值写入缓存 数据库完成主从同步,从库变为新值 依旧采用延时双删策略解决此问题。

Redis持久化有几种方式?

redis提供了两种持久化的方式,分别是RDB(Redis DataBase)和AOF(Append Only File)。

RDB,简而言之,就是在不同的时间点,将redis存储的数据生成快照并存储到磁盘等介质上;

AOF,则是换了一个角度来实现持久化,那就是将redis执行过的所有写指令记录下来,在下次redis重新启动时,只要把这些写指令从前到后再重复执行一遍,就可以实现数据恢复了。

其实RDB和AOF两种方式也可以同时使用,在这种情况下,如果redis重启的话,则会优先采用AOF方式来进行数据恢复,这是因为AOF方式的数据恢复完整度更高。

如果你没有数据持久化的需求,也完全可以关闭RDB和AOF方式,这样的话,redis将变成一个纯内存数据库,就像memcache一样。

Redis为什么是单线程的?

  1. 代码更清晰,处理逻辑更简单;
  2. 不用考虑各种锁的问题,不存在加锁和释放锁的操作,没有因为可能出现死锁而导致的性能问题;
  3. 不存在多线程切换而消耗CPU;
  4. 无法发挥多核CPU的优势,但可以采用多开几个Redis实例来完善;

Redis取值存值问题

1、先把Redis的连接池拿出来

`JedisPool pool = new JedisPool(new JedisPoolConfig(),"127.0.0.1");

Jedis jedis = pool.getResource();`

2、存取值

jedis.set("key","value"); jedis.get("key"); jedis.del("key"); //给一个key叠加value jedis.append("key","value2");//此时key的值就是value + value2; //同时给多个key进行赋值: jedis.mset("key1","value1","key2","value2");

3、对map进行操作

Map<String,String> user = new HashMap(); user.put("key1","value1"); user.put("key2","value2"); user.put("key3","value3"); //存入 jedis.hmset("user",user); //取出user中key1 List<String> nameMap = jedis.hmget("user","key1"); //删除其中一个键值 jedis.hdel("user","key2"); //是否存在一个键 jedis.exists("user"); //取出所有的Map中的值: Iterator<String> iter = jedis.hkeys("user").iterator(); while(iter.next()){ jedis.hmget("user",iter.next()); }