Java基础面试重点合集

面了一个月大厂加几个中小行和证券公司,其中关于Java基础方面问到的一些知识点做了一些总结。当然主要是我个人使用,读者有需要的话可以把这篇文章当做一个大纲,然后去详细挖掘具体内容。个人认为我总结的这些知识点可以做到面试的全覆盖。

Java数据类型

基本数据类型

char 2字节 0-65535
byet 1字节 -128-127
short 2字节 ‘
int 4字节
long 8字节
boolean
float 4字节
double 8字节

引用数据类型


接口
数组

包装类

在将一个基本数据类型赋值给对应包装类时会自动装箱,将包装类赋值给对应基本数据类型会自动拆箱。包装类对象也是不可变对象。
包装类的比较要使用equals方法,因为使用比较实际上比较的是地址值。以Integer为例,在-128到127之间的Intgerger自动装箱时是从cache中获取的,也就是这个区间自动装箱的对象都是相同的,而在这个区间之外使用返回的结果是false.

为什么浮点数会出现精度丢失

在计算机底层浮点数是通过二进制数表示的,都有一定的长度,如果是超过这个长度的话就会被截断,从而出现精度丢失。
IEE754:一位表示符号位,10位表示指数,21位表示小数。小数*2的指数次方就转换成了原本的数。

大数类和高精度类

BigInteger:用来表示任意大小的整数,运算速度较慢但是准确,当转换成long时如果超出范围会报ArithmeticException。
BigDecimal:用来表示高精度浮点类型,运算不会出现精度的丢失,底层用BigInteger表示整数部分,用一个Int类型保存小数部分。使用compareTo()方法可以比较两者大小是否相等,如果使用equals()方法,如果小数部分位数不同比如1.2和1.20返回的是false。

面对对象

三大特性

封装:封装是指把一个对象的状态信息(也就是属性)隐藏在对象内部,不允许外部对象直接访问对象的内部信息。但是可以提供一些可以被外界访问的方法来操作属性。
继承:继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承,可以快速地创建新的类,可以提高代码的重用,程序的可维护性,节省大量创建新类的时间 ,提高我们的开发效率。
多态:表示一个对象具有多种的状态,具体表现为父类的引用指向子类的实例。例如子类重写了父类的方法,父类的引用指向子类对象时,调用该方法调用的是子类的方法,这就是多态。

接口和抽象类

都不能被实例化
都可以包含抽象方法
都可以实现默认方法
接口主要是用来让类具有某一类行为,而抽象类主要是为了代码的复用
接口可以实现多个,而抽象类只能继承一个
接口中的变量默认是public staic final,而抽象类默认是default

代码块的执行顺序

父类静态代码块->子类静态代码块->父类构造代码块->父类构造方法->子类构造代码块->子类构造方法

拷贝

java里面实现cloneable接口重写clone()方法可以实现拷贝
深拷贝是指对对象的引用对象也进行拷贝
浅拷贝是指只拷贝对象本身,对象的引用不进行拷贝

String

String是不可变对象,底层是个char[]但是jdk9变成了byte[]因为大部分字符lantin-1编码可以用一个字节来表示,少数字符才会需要两个字节,所以这样可以节省内存。
new String(“abc”)会先在堆中创建一个对象,然后判断串池中有没有,如果没有就放进串池。
Stringbuilder和Stringbuffer都是动态扩容的字符串工具类,底层使用的是char数组
Stringbuffer线程安全,但是性能较低,因为存在加锁的过程。

异常

分类

异常继承自Throwable接口,分为Exception和Error两类,Exception是程序本身的异常,而Error则是程序之外的错误,比如OOM,StarkoverFlow等
Exception又分为受检查异常和运行时异常,受检查异常在编译期间就能检查出来,必须要在代码中进行处理,例如Ioexception,而运行时异常时在运行时候产生的,例如除0异常

泛型

什么是泛型

泛型就是将类型参数化,在编译时才确定具体的类型。
在编译时会进行泛型参数,也就是说ArrayList和ArrayList对象在调用Object.getClass方法之后获得的Class对象是相同的。

限定通配符和非限定通配符

限定通配符extends和super可以用来规定传入类型的范围
非限定通配符?指可以传入任意引用类型,如List<?>

反射

什么是反射

反射是指在程序运行中,对于任意一个类或者对象,都可以获取他的全部属性和方法,并且进行调用,这种动态获取信息以及动态调用方法的机制称为反射
反射的优点是具有较高的动态性,提升了程序设计的灵活性,反射的缺点是性能较低,因为需要解析字节码文件。

如何获取Class对象

通过Class.forname(“全类名”)
通过Object.getClass()
通过类名.class如String.class

反射的API类型

Class类:可以获取类的属性方法
Field类:获取类的成员变量等,获取或者设置值
Method:获取类的方法
Constructor:获取构造方法

注解

什么是注解

注解是java程序中的一些辅助信息,用来标识某些类,方法属性等等。
例如@Override就是用于标识重写的方法,这样编译器就会对其进行语法检查@Deprecated注解可以用来标识一个方法已经被废除@Supresswarning用来抑制编译警告

自定义注解

@Document:被标注的注解会写入doc文档中
@Retention:指定注解的生命周期,例如RententionPolicy.RUNTIME
@Target:指定注解的作用范围,例如Element.TYPE,Element.FIELD
@Inherited:注解是否可以被继承

其他

序列化机制

序列化机制是为了实现将对象存储到文件中或者将对象通过网络传输,然后从文件或者网络中读取这个对象。静态变量不会序列化因为序列化是针对对象而言的。
通常是实现Serialiazable接口,也可以通过实现Externializable接口,这个接口继承自Serialiazable接口,实现这个接口需要重写两个write和read方法,通过这俩方法可以自定义保存哪些数据
transient关键字可以标识不想序列化的部分
SerialVersionUID可以用来标识序列化对象的版本,一般需要显示指定,因为如果不显示指定那么在类改变时这个UID也就改变了,反序列化就会出现问题

IO流

IO流是指输入和输出,是指java程序和计算机内存之间的数据交互。分为根据方向分为输入流和输出流,根据传输的内容分为字符流和字节流
设计模式:装饰器模式BufferInputStream和InputStream;适配器模式,InputeSteamReader传入一个Inputstream对象,然后在将自身传入Reader对象得到Reader

JavaIO模型

Java应用程序是无法直接进行IO操作的,只有发出系统调用让操作系统来执行,发出系统调用其实是在操作系统的用户态和内核态之间拷贝数据,操作系统和网卡之间或者和硬盘之间的数据交换是由操作系统内核实现的,也就是说发起一次IO请求其实有两个过程,首先是将网卡或者硬盘的数据读取到操作系统内核,然后read调用操作系统将内核态的数据拷贝到用户态。
阻塞和非阻塞:发起系统调用之后的过程线程一定是阻塞的,所以阻塞和非阻塞就是指操作系统和网卡或者硬盘进行数据交换时线程是否阻塞。
同步和异步:同步是指发出IO请求之后要等IO结束才继续执行后续执行,异步的话则是发出IO请求之后直接进行其他指令,后台将数据准备好之后再通过通知回调等机制通知调用者。
BIO:BIO同步阻塞。是指线程在发出IO请求之后就阻塞,知道操作系统将数据拷贝到了用户态才唤醒线程
NIO:同步非阻塞IO,第一个阶段线程不阻塞,而是一直轮询发出read调用,操作系统统内核态拷贝数据到用户态还是阻塞的。NIO的轮询过程很耗费cpu,会产生很多无效的系统调用,所以通常采用IO多路复用的机制,将所有IO连接(socket连接)传入内核,轮询的过程交给操作系统内核进行处理,当数据准备好了用户再发起系统调用由操作系统将数据拷贝到用户态。 IO多路复用机制分为select,poll,epoll。
AIO:异步非阻塞IO,线程发出IO请求之后直接返回不会阻塞,不需要等待这个结果,被调用者处理完成后通知回调、通知等机制来通知调用者
select,poll,eploll:这是三种IO多路复用模型,select和poll都是知道了有IO事件发生然后对所有的IO进行轮询,二者的区别是poll没有最大连接限制,因为他是基于链表存储,epoll则是基于事件驱动的,有一个list保存了已经发生的IO事件,可以直接从将这个list中的事件复制到用户态。

集合

集合的分类

集合主要集成自Collection和Map两大接口,Collection又分为List,Set,Queue,List是可重复集合,主要有ArrayList,LinkedList,Vector三大实现类,Set是不可重复集合,主要有HashSet和Treeset两大实现类,Map是键值对集合,实现类主要有HashMap,HashTable和ConcurrentHashMap以及TreeMap。
线程安全的集合主要有Vector,Stack(继承自Vector),HashTable,ConcurrentHashMap.

ArrayList与LinkedList

ArrayList是动态扩容数组,底层是数组实现,只能存储引用数据类型,当存满了就会1.5倍扩容,将原来数组中的对象复制到新的数组中。
LinkedList是基于链表实现的有序可重复集合,所以不需要扩容
ArrayList支持随机查询,LinkeList不支持,但是LinkedList增删元素更快,因为ArrayList需要移动元素
ArrayList的空间损耗主要是由于数组中有空间没有存放对象,而LinkedList则是由于存储指针带来的额外空间损耗

Array与ArrayList

Array可以存放基本数据类型,而ArrayList不行
ArrayList支持动态扩容以及提供了更多的方法,比如add(),remove()

LinkedList与ArrayDeque

两者都实现了Queue接口
ArrayDeque是通过动态数组+双指针实现
ArrayDeque性能更好,因为除了扩容之外其他时候增加删除元素都是O(1),LinkedList虽然也是O1但是要申请和释放堆空间
LinkedList支持存储Null
ArrayDeque也可以用于实现栈

PriorityQueue

优先队列,底层使用了动态数组和二叉堆的数据结构实现
增加删除元素的时间复杂度都是Ologn
默认小顶堆,可以接受一个比较器作为构造参数

HashMap

HashMap底层是通过数组+链表以及红黑树的方式实现的
put操作首先通过元素计算出Hashcode,然后和数组长度相与得出下标值,如果数组对应下标的位置不存在元素则将元素放入,如果发生Hash冲突,则判断是否有相同的key,如果有就覆盖value,如果没有,就用链表法解决冲突,当链表长度大于8且数组长度大于64时将链表转换为红黑树,因为红黑树需要左旋右旋变色等操作,所以如果元素较少时使用红黑树性能损耗比较大,所以元素数量足够多时才会使用。
当保存的元素总数达到数组长度*负载因子时,就需要进行翻倍的扩容。首先开辟一个新的数组,然后根据HashCode新增的一位判断插入位置,如果新增的一位是0,则放在新数组相同下标下,如果是1则放在原下标+原数组长度下标。hash冲突采用尾插法,因为头插法在多线程情况下会出现环化的情况。
HashMap线程不安全主要体现在多线程put操作相同的key值会被覆盖;put和get并发时,如果put的线程触发了rehash,另一个线程还有可能会get到null值。

ConcurrentHashMap

底层采用Node+cas+Sychronized实现的线程安全的HashMap
在put操作时如果对应下标没有元素,则采用cas的方式对这个数组下标位置添加元素,如果有元素,则锁住第一个node节点,在该下标的链表或者红黑树上添加元素。这样就实现了细粒度的加锁。
get方法不需要加锁,因为用volitail修饰了value和next指针,线程对其进行的修改对其他线程是可见的。
哈希桶也使用了volitail修饰保证扩容时可见
并发度默认是16,如果设置为其他值如17则并发度是32,必须是2的n次方
弱一致性,遍历过程中允许增删元素。
key和value不支持null值,因为hashmap可以通过map.containskey()来判断是否含有null值,而多线程情况下这样判断会有并发安全问题
HashTable是使用Sychronized对同步方法加锁实现线程安全,效率很低

比较

元素实现Comparable接口,重写comparato方法
传入一个实现Comparator的比较器,重写compare()方法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Cc2018qaq

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值