Java基础面试总结

Java基础面试总结

集合基础知识

面向对象

封装:封装的意义,在于明确标识出允许外部使用的所有成员函数和数据项,内部细节对外部调用透明,外部调用无需修改或者关心内部实现
继承:继承基类的方法,并做出自己的改变和/或扩展,子类共性的方法或者属性直接使用父类的,而不需要自己再定义,只需扩展自己个性化的
多态:基于对象所属类的不同,外部对同一个方法的调用,实际执行的逻辑不同

JDK、JRE、JVM之间的区别

JDK:Java Develpment Kit java 开发工具
JRE:Java Runtime Environment java运行时环境
JVM:java Virtual Machine java 虚拟机

谈谈ConcurrentHashMap的扩容机制

1.7版本

1.7版本的ConcurrentHashMap是基于Segment分段实现的
每个Segment相对于一个小型的HashMap
每个Segment内部会进行扩容,和HashMap的扩容逻辑类似
先生成新的数组,然后转移元素到新数组中
扩容的判断也是每个Segment内部单独判断的,判断是否超过阈值

1.8版本
1.8版本的ConcurrentHashMap不再基于Segment实现
当某个线程进行put时,如果发现ConcurrentHashMap正在进行扩容那么该线程一起进行扩容
如果某个线程put时,发现没有正在进行扩容,则将key-value添加到ConcurrentHashMap中,然后判断是否超过阈值,超过了则进行扩容
ConcurrentHashMap是支持多个线程同时扩容的
扩容之前也先生成一个新的数组
在转移元素时,先将原数组分组,将每组分给不同的线程来进行元素的转移,每个线程负责一组或多组的元素转移工作

Jdk1.7到Jdk1.8 HashMap 发生了什么变化(底层)?
1.7中底层是数组+链表,1.8中底层是数组+链表+红黑树,加红黑树的目的是提高HashMap插入和查询整体效率
1.7中链表插入使用的是头插法,1.8中链表插入使用的是尾插法,因为1.8中插入key和value时需要判断链表元素个数,所以需要遍历链表统计链表元素个数,所以正好就直接使用尾插法
1.7中哈希算法比较复杂,存在各种右移与异或运算,1.8中进行了简化,因为复杂的哈希算法的目的就是提高散列性,来提供HashMap的整体效率,而1.8中新增了红黑树,所以可以适当的简化哈希算法,节省CPU资源

CopyOnWriteArrayList的底层原理是怎样的

首先CopyOnWriteArrayList内部也是用过数组来实现的,在向CopyOnWriteArrayList添加元素时,会复制一个新的数组,写操作在新数组上进行,读操作在原数组上进行
并且,写操作会加锁,防止出现并发写入丢失数据的问题
写操作结束之后会把原数组指向新数组
CopyOnWriteArrayList允许在写操作时来读取数据,大大提高了读的性能,因此适合读多写少的应用场景,但是CopyOnWriteArrayList会比较占内存,同时可能读到的数据不是实时最新的数据,所以不适合实时性要求很高的场景

Java中的异常体系是怎样的

Java中的所有异常都来自顶级父类Throwable。
Throwable下有两个子类Exception和Error。
Error是程序无法处理的错误,一旦出现这个错误,则程序将被迫停止运行。
Exception不会导致程序停止,又分为两个部分RunTimeException运行时异常和CheckedException检查异常。
RunTimeException常常发生在程序运行过程中,会导致程序当前线程执行失败。CheckedException常常发生在程序编译过程中,会导致程序编译不通过。

第一代线程安全集合类

Vector、HasnTable
是怎么保证线程安排的:使用synchronized修饰方法
缺点:效率低下

第二低线程非安全集合类

ArrayList、HashMap
线程不安全,但是性能好,用来替代vector、Hashtable
使用ArrayList、HashMap,需要线程安全怎么办呢?
使用Collections.synchronized(list);Collections.synchronizedMap(m);

第三代线程安全集合类

在大量兵法情况下如何提高集合的效率和安全呢?
java.util.concurrent.*;
ConcurrentHashMap;
CopyOnWriteArraylist;
CopyOnwriterArraySet;
底层大都采用Lock锁,保证安全的同时,性能也很高

List和Set区别

List:
可以允许重复的对象
可以插入多个null元素
是一个有序容器,保持了每个元素的插入顺序,输出的顺序就是插入的顺序

Set:
不允许重复对象
无序容器,你无法保证每个元素的存储顺序
只允许一个null元素

List 和 Map 区别

map和list的用途很多,如果一个文件有100万行,用map遍历一遍要比list快很多。

另外map中查询可以用containKey这个函数,速度非常快,map有四种遍历方法,其中有一种是专门遍历大数据的map一般存储后删除重复数据,但是使用IdentityHashMap可以使得Key重复。

ArrayList 与 LinkedList 区别

ArrayList底层是数组,查询快、增删慢;LinkedList底层是链表,查询慢、增删快。

ArrayList 与 Vector 区别

1、Vector是线程安全的,ArrayList不是线程安全的。

2、ArrayList在底层数组不够用时在原来的基础上扩展0.5倍,Vector是扩展1倍。

3.ArrayList:线程不安全,效率高,常用
Vector:线程安全的,效率低

HashMap 和 HashTable 的区别

  • HashTable 线程安全,HashMap非线程安全。
  • Hashtable不允许 null 值,HashMap允许 null 值。
  • 两者的遍历方式大同小异,Hashtable仅仅比HashMap多一个elements方法。

HashSet 和 HashMap 区别

HashSet:
HashSet实现了Set接口,它不允许集合中出现重复元素。当我们提到HashSet时,第一件事就是在将对象存储在HashSet之前,要确保重写hashCode()方法和equals()方法,这样才能比较对象的值是否相等,确保集合中没有储存相同的对象。

HashMap:
HashMap实现了Map接口,Map接口对键值对进行映射。Map中不允许出现重复的键(Key)。Map接口有两个基本的实现。

HashMap 和 ConcurrentHashMap 的区别

ConcurrentHashMap对整个桶数组进行了分段,而HashMap则没有。

ConcurrentHashMap在每一个分段上都用锁进行保护,从而让锁的粒度更精细一些,并发性能更好, 而HashMap没有锁机制,不是线程安全的。

HashMap 的工作原理及代码实现

HashMap的主干是一个Entry数组。Entry是HashMap的基本组成单元,每一个Entry包含一个key-value键值对。

(1)jdk1.8之前,list+链表

(2)jdk1.8之后,list+链表(当链表长度达到8时,转化为红黑树)

(3)HashMap扩容因子默认0.75,会浪费1/4的空间,达到扩容因子时,会将list扩容一倍,0.75是时间与空间的一个平衡值。

ConcurrentHashMap 的工作原理及代码实现

与HashMap类似,ConcurrentHashMap用数组存储Node结构,Node结构中存储key和value,hash值及next指针。


Java异常处理方式

  • try、catch、finally:捕获异常
  • throw:抛出异常、方法内部、具体的异常
  • throws:声明异常、具体的异常类

String、StringBuffer、StringBuiler的区别及使用

String
  • string是只读字符串、具有不可变性
  • 字符串内容改变实际上地址也改变了
StringBuffer
  • 线程安全
  • 可变性
StringBuiler
  • 线程不安全
  • 可变性
  • 效率高
  • 声明一个StringBuiler时指向一个堆空间,具有两个重要属性value和count

怎么声明一个类不会被继承,什么场景下使用

如果一个类被final修饰,此类不可以有子类,不能被其他类继承,如果一个中的所有方法都没有重写的需要,当前类没有子类也罢。
例如:Math


重载和重写的区别

  • 定义不同:重载是定义相同的方法名、参数不同,重写是子类重写父类的方法
  • 范围不同:重载是在一个类中,重写是子类与父类之间的
  • 多态不同:重载是编译时的多态性,重写是运行时的多态性
  • 参数不同:重载的参数个数、参数类型、参数的顺序可以不同,重写父类子方法参数必须相同

多线程基础

线程池的作用是什么

线程池:其实就是一个 容纳多个线程的容器 ,其中的线程可以反复使用,省去了频繁创建线程对象的操作 ,无需反复创建线程而消耗过多资源

创建线程的方式及实现

  • 继承 Thread 类
    Thread 类实现了 Runnable 接口并定义了操作线程的一些方法
  • 实现 Runnable 接口
    通过实现Runnable 接口来创建线程类 RThread,但是使用的时候,仍需要创建Thread 对象,把RThread的对象当成参数传入。
    具体操作:
    (1)实现Runnable 接口创建线程类 RThread
    (2)重写run()方法
    调用步骤:
    (1)创建RThread 类的对象 rThread
    (2)创建Thread类对象,并把rThread当成参数传入,相当于对rThread进行了封装。
    (3)通过start()方法启动线程
  • 通过 ExecutorService 和 Callable\ 实现有返回值的线程
  • 基于线程池的execute(),创建临时线程

sleep() 、join()、yield()有什么区别

  • wait 将当前运行的线程挂起(即让其进入阻塞状态),直到notify或notifyAll方法来唤醒线程。
  • sleep 就是正在执行的线程主动让出 cpu,cpu 去执行其他线程,在 sleep 指定的时间过后,cpu
    才会回到这个线程上继续往下执行。
  • join() 的作用:让“主线程”等待“子线程”结束之后才能继续运行
  • Thread.yield()方法作用是:暂停当前正在执行的线程对象,并执行其他线程

描述线程池的相关配置(最大、最小是多少)

1.在大量的异步任务到达的情况下,使用线程池能够提升性能;

2、提供一种资源管理和调度的方法。


## redis基础 ### Redis介绍优缺点 1、Redis不仅仅只支持简单的K-V形式的数据存储,还支持list、set、hash、zset等等集合类数据的存储;

2、Redis支持实时的数据备份,及时宕机,也可以把数据恢复过来;

3、Redis支持数据的持久化,可以存放在内存memory中的数据直接保存在磁盘上;

1、查找最新的回复。 如果在传统的关系型数据库,这就需要使用select * from table where name=“” order by time desc limit 100;这十分消耗数据库性能,但是通过Redis,就可以直接在Redis里面通过Id创建一个List,指定长度1w,当需要查找时,直接输出该list的后100条记录。

2、排行问题 常见的排行问题,例如最热话题、游戏排名等等,这些都可以通过Redis来轻松实现,直接使用ZRank即可得到。

3、删除过期数据 Redis不是真正意义上的可持久化数据库,可以给数据加上一个有效时间,在有效时间超过时,Redis会自动删除对应数据。

Redis有哪些数据类型,及各应用场景

String:

String通常用于保存单个字符串或JSON字符串数据
计数器

Hash类型:

常用于存储一个对象
购物车:以用户id为key,商品id为field,商品数量为value,恰好构成了购物车的3个要素。

List:

对数据量大的集合数据删减
任务队列

set:
利用集合操作,可以取不同兴趣圈子的交集,以非常方便的实现如共同关注、共同喜好、二度好友等功能。对上面的所有集合操作,你还可以使用不同的命令选择将结果返回给客户端还是存储到一个新的集合中。

利用唯一性,可以统计访问网站的所有独立 IP、存取当天[或某天]的活跃用户列表。

zSet:
统计注册 IP 数
统计每日访问 IP 数
统计页面实时 UV 数
统计在线用户数
统计用户每天搜索不同词条的个数
统计真实文章阅读数

描述Redis事务目的和作用

Redis 事务可以一次执行多个命令

按顺序地串行化执行,执行中不会被其它命令插入,不许加塞

缓存穿透是什么,解决方案、原因

缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透。

解决方案:
持久层查询不到就缓存空结果,查询时先判断缓存中是否exists(key) ,如果有直接返回空,没有则查询后返回。

缓存雪崩是什么,解决方案、原因

雪崩:缓存大量失效的时候,引发大量查询数据库。

解决办法:
​ 用锁/分布式锁或者队列串行访问
​ 缓存失效时间均匀分布

1 加锁排队. 限流-- 限流算法.

2 数据预热

Redis集群特点

1、所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽。
2、节点的fail是通过集群中超过半数的节点检测失效时才生效。
3、客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可。
4、redis-cluster把所有的物理节点映射到[0-16383]slot上(不一定是平均分配),cluster 负责维护

5、Redis集群预分好16384个哈希槽,当需要在 Redis 集群中放置一个 key-value 时, redis 先对key 使用 crc16 算法算出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节

Redis集群什么时候master不能用?

投票机制。投票过程是集群中所有master参与,如果半数以上master节点与master节点通信超时,认为当前master节点挂掉.

什么时候整个Redis集群不可用?

如果集群任意master挂掉,且当前master没有slave.集群进入fail状态,也可以理解成集群的slot映射[0-16383]不完整时进入fail状态. 如果集群超过半数以上master挂掉,无论是否有slave,集群进入fail状态.

数据库基础知识

数据库设计的三大范式

第一范式:每个列都不可以再拆分
第二范式:在第一范式的基础上,非主键列完全依赖于主键,而不能是依赖与主键的一部分
第三范式:在第二范式的基础上,非主键列只依赖于主键,不依赖于其他非主键

如何优化 MySQL

SQL语句优化和索引优化:

  • 使用正确的索引
  • 优化子查询(join)
  • 表连接最好都在where条件以前有、
  • 尽量避免在字段开头模糊查询,会导致数据库引擎放弃索引进行全表扫描
  • 尽量避免使用 or,会导致数据库引擎放弃索引进行全表扫描

数据库结构优化

  • 最小数据长度
  • 适当分表、分库策略
  • 内存增大

查询缓存
创建高性能索引
根据三大范式设计表结构
避免多个范围条件查询


MySQL索引使用的注意事项

索引如目录一样,本质上索引就是一种快速检索数据的一种数据结构。

1、应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。

2、对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。

3、应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描

4、尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描 如:select id from t where num=10 or num=20

5、下面的查询也将导致全表扫描:(不能前置百分号)select id from t where name like ‘c%’

6.索引不会包含有NULL值的列

7.使用短索引

8.索引列排序

9、不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。

10、在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使 用,并且应尽可能的让字段顺序与索引顺序相一致。

什么情况下索引会生效

使用!= 或者 <> 导致索引失效

类型不一致导致的索引失效:字段varchar,条件为int,会导致失效

函数导致的索引失效

运算符导致的索引失效:对列进行了(+,-,*,/,!), 那么都将不会走索引

模糊搜索导致索引失效

说说分库与分表设计

对于访问极为频繁且数据量巨大的单表来说,我们首先要做的就是减少单表的记录条数,以便减少数据查询所需要的时间,提高数据库的吞吐,这就是所谓的分表!

场景:分表能够解决单表数据量过大带来的查询效率下降的问题,但是,却无法给数据库的并发处理能力带来质的提升。面对高并发的读写访问,当数据库master服务器无法承载写操作压力时,不管如何扩展slave服务器,此时都没有意义了。因此,我们必须换一种思路,对数据库进行拆分,从而提高数据库写入能力,这就是所谓的分库!


分库与分表带来的分布式困境与应对之策

SQL语句优化

1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。

2.应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。

3.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,

4.应尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描

5.尽量避免大事务操作,提高系统并发能力。

6.尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。

MySQL 遇到的死锁问题

所谓死锁:是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去.此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

至少有3个(或以上)的并发删除操作;
并发删除操作,有可能删除到同一条记录,并且保证删除的记录一定存在;


加密

什么是加密

维护信息安全

加密分为几种

1.对称加密

  • 常见的有AES,DES,3DES

2.非对称加密

  • 常见的有RSA,ECC,DSA

3.线性散列

  • 常见的有MD5,SHA1,SHA256,HMAC

可逆和不可逆

解释: 加密后, 密文可以反向解密得到密码原文.

用途: 一般用于保存用户手机号、身份证等敏感但能解密的信息。
常见的对称加密算法有: AES、DES、3DES、Blowfish、IDEA、RC4、RC5、RC6、HS256

一旦加密就不能反向解密得到密码原文.

解释: 一旦加密就不能反向解密得到密码原文.种类: Hash加密算法, 散列算法, 摘要算法等
用途:一般用于效验下载文件正确性,一般在网站上下载文件都能见到;存储用户敏感信息,如密码、 卡号等不可解密的信息。
常见的不可逆加密算法有: MD5、SHA、HMAC

说说盐值加密

简单说就是为了使相同的密码拥有不同的hash值的一种手段


java锁机制

说说线程安全问题

就是我们要确保在多条线程访问的时候,我们的程序还能按照我们预期的行为去执行

volatile 实现原理

可见性:即当一个线程修改了声明为volatile变量的值,新值对于其他要读该变量的线程来说是立即可见的。而普通变量是不能做到这一点的,普通变量的值在线程间传递需要通过主内存来完成。

有序性:volatile变量的所谓有序性也就是被声明为volatile的变量的临界区代码的执行是有顺序的,即禁止指令重排序。

受限原子性:这里volatile变量的原子性与synchronized的原子性是不同的,synchronized的原子性是指只要声明为synchronized的方法或代码块儿在执行上就是原子操作的。而volatile是不修饰方法或代码块儿的,它用来修饰变量,对于单个volatile变量的读/写操作都具有原子性,但类似于volatile++这种复合操作不具有原子性。所以volatile的原子性是受限制的。并且在多线程环境中,volatile并不能保证原子性。

public class VolatileTest {
    private static volatile VolatileTest instance = null;
    private VolatileTest(){}
    public static VolatileTest getInstance(){
        if(instance == null){
            instance = new VolatileTest();
        }
        return instance;
    }

    public static void main(String[] args) {
        VolatileTest.getInstance();
    }
}

synchronize 实现原理

synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证共享变量的内存可见性

Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:

普通同步方法,锁是当前实例对象静态同步方法,锁是当前类的class对象同步方法块,锁是括号里面的对象

CAS 乐观锁

CAS是乐观锁的一种实现,CAS全称是比较和替换
CAS是一种无锁操作,不需要加锁,避免了线程切换的开销。
在这里插入图片描述

乐观锁的业务场景及实现方式

乐观锁:乐观锁在操作数据时非常乐观,认为别人不会同时修改数据。

因此乐观锁不会上锁,只是在执行更新的时候判断一下在此期间别人是否修改了数据:如果别人修改了数据则放弃操作,否则执行操作。

悲观锁:悲观锁在操作数据时比较悲观,认为别人会同时修改数据。

乐观锁的实现方式主要有两种:CAS机制和版本号机制


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值