当前位置: 首页>后端>正文

java-se部分

一.JavaSE 部分

基础部分

Java中基本数据类型有哪些?

byte:8位,最大存储数据量是255,存放的数据范围是-128~127之间。

short:16位,

int:32位,最大数据存储容量是2的32次方减1,数据范围是负的2的31次方到正的2的31次方减1。

long:64位,最大数据存储容量是2的64次方减1,数据范围为负的2的63次方到正的2的63次方减1。

float:32位,

double:64位,

boolean:只有true和false两个取值。

char:16位,存储Unicode码,用单引号赋值。

Integer 和 int的区别

int是基本数据类型,变量中直接存放数值,变量初始化时值是0

Integer是引用数据类型,变量中存放的是该对象的引用,变量初始化时值时null

Integer是int类型的包装类,将int封装成Integer,符合java面向对象的特性,可以使用各种方法比如和其他数据类型间的转换

Integer和int的深入对比:

  1. 两个通过new生成的Integer对象,由于在堆中地址不同,所以永远不相等

  2. int和Integer比较时,只要数值相等,结果就相等,因为包装类和基本数据类型比较时,会自动拆箱,将Integer转化为int

  3. 通过new生成的Integer对象和非通过new生成的Integer对象相比较时,由于前者存放在堆中,后者存放在Java常量池中,所以永远不相等

  4. 两个非通过new生成的Integer对象比较时,如果两个变量的数值相等且在-128到127之间,结果就相等。这是因为给Integer对象赋一个int值,java在编译时,会自动调用静态方法valueOf(),根据java api中对Integer类型的valueOf的定义,对于-128到127之间的整数,会进行缓存,如果下次再赋相同的值会直接从缓存中取,即享元模式

String和StringBuilder和StringBuffer区别

三者底层都是char[]存储数据,JDK1.9之后使用的是byte[] ,因为往往我们存储都是短字符串,使用byte[]这样更节约空间。

由于String底层的char[]有final修饰,因此每次对String的操作都会在内存中开辟空间,生成新的对象,所以String不可变

StringBuilder和StringBuffer是可变字符串,没有final修饰,适合字符串拼接,另外StringBuffer是线程安全的,方法有synchronized修饰,但是性能较低,StringBuilder是线程不安全的,方法没有synchronized修饰,性能较高

String a = "A" 和 String a = new String("A") 创建字符串的区别

String c = "A" 首先去常量池找 “A”,如果有,会把a指向这个对象的地址 ,如果没有则在栈中创建三个char型的值'A',堆中创建一个String对象object,值为"A",接着object会被存放进字符串常量池中,最后将a指向这个对象的的地址

new String("A") : 如果常量池中么有“A”就会走上面相同的流程先创建“A”,然后在堆中创建一个String对象,它的值共享栈中已有的char值“A”。

下面代码创建了几个对象

  • String s = "a" +"b" + "c" + "d";这条语句创建了几个对象?

创建了一个对象,因为相对于字符串常量相加的表达式,编译器会在编译期间进行优化,直接将其编译成常量相加的结果。

  • String s; 创建几个对象?
    没有创建对象。

  • String a = "abc"; String b = "abc"; 创建了几个对象

    创建了一个对象,只是在第一条语句中创建了一个对象,a和b都指向相同的对象"abc",引用不是对象

== 和 equals 的区别是什么

==比较对象比较的是地址,对于Object对象中的equals 方法使用的也是 == ,比较的是对象的地址,默认情况下使用对象的equals比较Object中的equals方法,也就是比较地址,如果要实现自己的比较方式需要复写equals 方法。

对于包装类比如:Integer都是复写过equals方法,比较的是int 值。

final 和 finally 和 finalize 的区别

当用final修饰类的时,表明该类不能被其他类所继承。当我们需要让一个类永远不被继承,此时就可以用final修饰

finally作为异常处理的一部分,它只能用在try/catch语句中,并且附带一个语句块,表示这段语句最终一定会被执行(不管有没有抛出异常),经常被用在需要释放资源的情况下

finalize()是在java.lang.Object里定义的,也就是说每一个对象都有这么个方法。这个方法在gc启动,该对象被回收的时候被调用。其实gc可以回收大部分的对象(凡是new出来的对象,gc都能搞定,一般情况下我们又不会用new以外的方式去创建对象),所以一般是不需要程序员去实现finalize的。

JDK 和 JRE 有什么区别?

JRE(Java Runtime Enviroment) :是Java的运行环境,JRE是运行Java程序所必须环境的集合,包含JVM标准实现及 Java核心类库

JDK(Java Development Kit) :是Java开发工具包,它提供了Java的开发环境(提供了编译器javac等工具,用于将java文件编译为class文件)和运行环境(提 供了JVM和Runtime辅助包,用于解析class文件使其得到运行)。JDK是整个Java的核心,包括了Java运行环境(JRE),一堆Java工具tools.jar和Java标准类库 (rt.jar)。

面向对象四大特性

抽象 : 是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面,抽象只关注对象的哪些属性和行为,并不关注这此行为的细节是什么 - 举例:定义一个persion类,了就是对这种事物的抽象

封装:对数据的访问只能通过已定义的接口,封装就是隐藏一切可隐藏的东西,只向外界提供最简单的编程接口,比如在Java中,把不需要暴露的内容和实现细节隐藏起来,或者private修饰,然后提供专门的访问方法,如JavaBean。 - 生活举例:电脑主机就是把主板等封装到机壳,提供USB接口,网卡接口,电源接口等。 JavaBean就是一种封装。

继承:新类(子类,派生类)继承了原始类的特性,子类可以从它的父类哪里继承方法和实例变量,并且类可以修改或增加新的方法使之更适合特殊的需要。

多态:多态是指允许不同类的对象对同一消息做出响应。对象的多种形态,当编译时类型和运行时类型不一样,就是多态,意义在于屏蔽子类差异

方法覆盖和重载

方法的覆盖是子类和父类之间的关系,方法的重载是同一个类中方法之间的关系。
覆盖只能由一个方法,或只能由一对方法产生关系;方法的重载是多个方法之间的关系。
覆盖要求参数列表相同;重载要求参数列表不同。

普通类和抽象类

抽象类不能被实例化, 需要通过子类实例化
抽象类可以有构造函数,被继承时子类必须继承父类一个构造方法,抽象方法不能被声明为静态。
抽象方法只需申明,而无需实现,抽象类中可以允许普通方法有主体
含有抽象方法的类必须申明为抽象类
抽象的子类必须实现抽象类中所有抽象方法,否则这个子类也是抽象类

接口和抽象类

定义接口使用interface,定义抽象类使用abstract class

接口由全局常量,抽象方法,(java8后:静态方法,默认方法)

抽象类由构造方法,抽象方法,普通方法

接口和类是实现关系,抽象类和类是继承关系

IO流

你知道BIO,NIO,AIO么?讲一下你的理解

BIO (Blocking I/O):同步阻塞I/O 模式,以流的方式处理数据,数据的读取写入必须阻塞在一个线程内等待其完成。适用于连接数目比较小且固定的架构

NIO (New I/O):同时支持阻塞与非阻塞模式,以块的方式处理数据,适用于连接数目多且连接比较短(轻操作)的架构,比如聊天器

AIO ( Asynchronous I/O):异步非阻塞I/O 模型,适用于连接数目多且连接比较长(重操作)的架构

java 中四大基础流

InputStream : 输入字节流, 也就是说它既属于输入流, 也属于字节流 ,

OutputStream: 输出字节流, 既属于输出流, 也属于字节流

Reader: 输入字符流, 既属于输入流, 又属于字符流

Writer: 输出字符流, 既属于输出流, 又属于字符流

读文本用什么流,读图片用什么流

文本用字符输入流,读图片用字节输入流

字符流和字节流有什么区别

字符流适用于读文本,字节流适用于读图片,视频,文件等。

字节流操作的基本单元为字节;字符流操作的基本单元为Unicode码元。

字节流默认不使用缓冲区;字符流使用缓冲区。

字节流通常用于处理二进制数据,实际上它可以处理任意类型的数据,但它不支持直接写入或读取Unicode码元;字符流通常处理文本数据,它支持写入及读取Unicode码元

BufferedInputStream 用到什么设计模式

主要运用了俩个设计模式,适配器和装饰者模式

带缓冲区的流

BufferedInputStream 带缓冲区的字节输入

BufferedOutputStream 带缓冲区的输出流

BufferedReader : 带缓冲区的字符输入流

BufferedWriter : 带缓冲区的字符输出流

集合篇

说一下Java中的集合体系

Collection接口

List:

  • ArrayList:底层数据结构是数组,查询性能高,增删性能低

  • Vector:底层数据结构是数组,查询性能高,增删性能低

  • LinkedList:底层数据结构是双向链表,查询性能低,增删性能高

Set:

  • HashSet:无序不重复的,使用HashMap的key存储元素,判断重复依据是hashCode()和equals()

  • TreeSet:有序不重复的,底层使用TreeMap的key存储元素,排序方式分为自然排序,比较器排序

Map接口

  • HashMap:key的值没有顺序,线程不安
  • TreeMap:key的值可以自然排序,线程不安全
  • HashTable:它的key和value都不允许为null,线程安全
  • Properties:它的key和value都是String类型的,线程安全

HashMap和HashTable的区别

HashMap和HashTable都是实现了Map接口的集合框架,他们的区别

  • HashTable是线程安全的,它的实现方法都加了synchronized关键字,因此它的性能较低

  • HashMap是线程不安全的,它实现方法没有加synchronized,因此它的性能较高

  • HashMap的key和value都允许为null,HashTable中的key和value都不能为null,如果不考虑线程安全,建议使用HashMap,如果需要考虑线程安全的高并发实现,建议使用ConcurrentHashMap

ArrayList和LinkedList区别

都属于线性结构,ArrayList是基于数组实现的,开辟的内存空间要求联系,可以根据索引随机访问元素性能高,但是插入和删除元素性能差,因为这会涉及到移位操作

LinkedList是基于双链表实现的,开配的内存空间不要求连续,因此不支持索引,查找元素需要从头查找,因此性能差,但是添加删除只需要改变指针指向即可,性能高. LinkedList会增加内存碎片化,增加内存管理难度

根据实际需要,如果项目中使用查找较多,使用ArrayList,如果使用增删较多,请使用LinkedList

ArrayList和Vector区别

ArrayList是线程不安全的,Vector相反是线程安全的,方法加了同步锁,线程安全但是性能差,ArrayList底层数组容量不足时,会自动扩容0.5倍,Vector会自动扩容1倍

一个User的List集合,如何实现根据年龄排序

第一种方式,让User类实现Comparable接口,覆写compareTo方法,方法中自定义根据年龄比较的算法

第二种方式,调用Collections.sort方法,传入一个比较器,覆写compare方法,方法中自定义根据年龄比较的算法

HashMap底层用到了那些数据结构?

JDK1.7及其之前:数组,链表 ; JDK1.8开始:数组,链表,红黑树

什么是Hash冲突

哈希冲突,也叫哈希碰撞,指的是两个不同的值,计算出了相同的hash,也就是两个不同的数据计算出同一个下标,通常解决方案有:

  • 拉链法,把哈希碰撞的元素指向一个链表

  • 开放寻址法,把产生冲突的哈希值作为值,再进行哈希运算,直到不冲突

  • 再散列法,就是换一种哈希算法重来一次

  • 建立公共溢出区,把哈希表分为基本表和溢出表,将产生哈希冲突的元素移到溢出表

HashMap为什么要用到链表结构

当我们向HashMap中添加元素时,会先根据key尽心哈希运算,把hash值模与数组长度得到一个下标,然后将该元素添加进去。但是如果产生了哈希碰撞,也就是不同的key计算出了相同的hash值,这就出问题了,因此它采用了拉链法来解决这个问题,将产生hash碰撞的元素,挂载到链表中

HashMap为什么要用到红黑树

当HashMap中同一个索引位置出现哈希碰撞的元素多了,链表会变得越来越长,查询效率会变得越来越慢。因此在JDK1.8之后,当链表长度超过8个,会将链表转坏为红黑树来提高查询

HashMap链表和红黑树在什么情况下转换的?

当链表的长度大于等于8,同时数组的长度大于64,链表会自动转化为红黑树,当树中的节点数小于等于6,红黑树会自动转化为链表

HashMap在什么情况下扩容?HashMap如何扩容的?

HashMap的数组初始容量是16,负载因子是0.75,也就是说当数组中的元素个数大于12个,会成倍扩容

tips:为啥子是0.75:负载因子过小容易浪费空间,过大容易造成更多的哈希碰撞,产生更多的链表和树,因此折衷考虑采用了0.75

为啥子是成倍扩容:需要保证数组的长度是2的整数次幂

为嘛数组的长度必须是2的整数次幂:我们在存储元素到数组中的时候,是通过hash值模与数组的长度,计算出下标的。但是由于计算机的运算效率,加减法>乘法>除法>取模,取模的效率是最低的。开发者们为了让你用的开心,也是呕心沥血。将取模运算转化成了与运算,即数组长度减1的值和hash值的与运算,以此来优化性能。但是这个转化有一个前提,就是数组的长度必须为2的整数次幂

HashMap是如何Put一个元素的

首先,将key进行hash运算,将这个hash值与上当前数组长度减1的值,计算出索引。此时判断该索引位置是否已经有元素了,如果没有,就直接放到这个位置

如果这个位置已经有元素了,也就是产生了哈希碰撞,那么判断旧元素的key和新元素的key的hash值是否相同,并且将他们进行equals比较,如果相同证明是同一个key,就覆盖旧数据,并将旧数据返回,如果不相同的话

再判断当前桶是链表还是红黑树,如果是红黑树,就按红黑树的方式,写入该数据,

如果是链表,就依次遍历并比较当前节点的key和新元素的key是否相同,如果相同就覆盖,如果不同就接着往下找,直到找到空节点并把数据封装成新节点挂到链表尾部。然后需要判断,当前链表的长度是否大于转化红黑树的阈值,如果大于就转化红黑树,最后判断数组长度是否需要扩容。

HashMap是如何Get一个元素的

首先将key进行哈希运算,计算出数组中的索引位置,判断该索引位置是否有元素,如果没有,就返回null,如果有值,判断该数据的key是否为查询的key,如果是就返回当前值的value

如果第一个元素的key不匹配,判断是红黑树还是链表,如果是红黑树,就就按照红黑树的查询方式查找元素并返回,如果是链表,就遍历并匹配key,让后返回value值

你知道HahsMap死循环问题吗

HashMap在扩容数组的时候,会将旧数据迁徙到新数组中,这个操作会将原来链表中的数据颠倒,比如a->b->null,转换成b->a->null

这个过程单线程是没有问题的,但是在多线程环境,就可能会出现a->b->a->b....,这就是死循环

在JDK1.8后,做了改进保证了转换后链表顺序一致,死循环问题得到了解决。但还是会出现高并发时数据丢失的问题,因此在多线程情况下还是建议使用ConcurrentHashMap来保证线程安全问题

说一下你对ConcurrentHashMap的理解

ConcurrentHashMap,它是HashMap的线程安全,支持高并发的版本

在jdk1.7中,它是通过分段锁的方式来实现线程安全的。意思是将哈希表分成许多片段Segment,而Segment本质是一个可重入的互斥锁,所以叫做分段锁。

在jdk1.8中,它是采用了CAS操作和synchronized来实现的,而且每个Node节点的value和next都用了volatile关键字修饰,保证了可见性


https://www.xamrdz.com/backend/3vm1931830.html

相关文章: