Java面试之Java基础篇(offer 拿来吧你)_java面试吧

    //测试数组
    B b = new B();
    int[] arr = {1, 2, 3};
    b.test100(arr);//调用方法
    System.out.println(" main 的arr 数组");
    //遍历数组
    for (int i = 0; i < arr.length; i++) {
        System.out.print(arr[i] + "\t");
    }
    System.out.println();
	
	// 测试对象
	Person p = new Person();
    p.name = "jack";
    p.age = 10;
    b.test200(p);
    System.out.println("main 的p.age=" + p.age);

}

}

class B {
// 数组
public void test100(int[] arr) {
arr[0] = 200;//修改元素
//遍历数组
System.out.println(" test100 的arr 数组");
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + “\t”);
}
System.out.println();
}
// 对象
public void test200(Person p) {
p.age = 1000; //修改对象属性

    // 特例1
    //p = null;
    // 特例2
    //p = new Person();
	//p.name = "tom";
	//p.age = 99;
}

}



> 
> 输出结果:  
>  test100的arr数组:[200,2,3]  
>  main的arr数组:[200,2,3]  
>  main的p.age=1000  
>  分别从引用类型(数组和对象)的角度来举例,可以发现在引用数据类型中传递的是地址,可以通过形参影响实参!
> 
> 
> 


如果大家对上述例子有所了解,下面再添加几个特例



特例1
p = null;

特例2
p = new Person();
p.name = “tom”;
p.age = 99;

System.out.println(“main 的p.age=” + p.age);



> 
> 如果test200 执行的是p = null ,下面的结果是10  
>  如果test200 执行的是p = new Person();…, 下面输出的是10  
>  为什么会这样呢?  
>  p = null 代表指向p的地址已经断开,不影响p对象的值  
>  p = new Person() 代表指向了一个新的地址,无论怎么传递值,都不会影响p对象的值。  
>  具体可以查看[Java基础补充–查漏补缺(二)](https://bbs.csdn.net/topics/618545628)
> 
> 
> 


#### 6、Java支持的数据类型有哪些?什么是自动拆装箱?


Java语言支持的8种基本数据类型是: byte short int long float double boolean char  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/382fc9de2d184a0da97d93fd3fbe0a95.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA56CU6KGM56yU5b2V,size_20,color_FFFFFF,t_70,g_se,x_16)  
 自动装箱:可以把一个基本类型的数据直接赋值给对应的包装类型;  
 自动拆箱:可以把一个包装类型的对象直接赋值给对应的基本类型;



> 
> 以Integer对象为例子:  
>  Integer.parseInt(“”);是将字符串类型转换为int的基础数据类型  
>  Integer.valueOf(“”)是将字符串类型数据转换为Integer对象  
>  Integer.intValue();是将Integer对象中的数据取出,返回一个基础数据类型int
> 
> 
> 


基本类型和包装类型的区别?


* 成员变量包装类型不赋值就是 null ,而基本类型有默认值且不是 null。
* 包装类型可用于泛型,而基本类型不可以。
* 基本数据类型的局部变量存放在 Java 虚拟机栈中的局部变量表中,基本数据类型的成员变量(未被 static 修饰 )存放在 Java虚拟机的堆中。包装类型属于对象类型,我们知道几乎所有对象实例都存在于堆中。
* 相比于对象类型, 基本数据类型占用的空间非常小。


#### 7、int和Integer有什么区别,二者在做==运算时会得到什么结果?


int是基本数据类型,Integer是int的包装类。二者在做==运算时,Integer会自动拆箱为int类型,然后再进行比较。届时,如果两个int值相等则返回true,否则就返回false。


#### 8、Java中,什么是构造方法?什么是构造方法重载?什么是复制构造方法?


当新对象被创建的时候,构造方法会被调用。每一个类都有构造方法。在程序员没有给类提供构造方法的情况下,Java编译器会为这个类创建一个默认的构造方法。


**构造方法特点如下:**


* 名字与类名相同。
* 没有返回值,但不能用 void 声明构造函数。
* 生成类的对象时自动执行,无需调用。


构造方法不能重写。因为构造方法需要和类保持同名,而重写的要求是子类方法要和父类方法保持同名;Java中构造方法重载和方法重载很相似。可以为一个类创建多个构造方法。每一个构造方法必须有它自己唯一的参数列表。


#### 9、Java支持多继承么?


**Java中类不支持多继承,只支持单继承(即一个类只有一个父类)**。 但是java中的接口支持多继承,即一个子接口可以有多个父接口。(接口的作用是用来扩展对象的功能,一个子接口继承多个父接口,说明子接口扩展了多个功能,当类实现接口时,类就扩展了相应的功能)。


#### 10、深拷贝和浅拷贝区别了解吗?什么是引用拷贝?


* 浅拷贝:浅拷贝会在堆上创建一个新的对象(区别于引用拷贝的一点),不过,如果原对象内部的属性是引用类型的话,浅拷贝会直接复制内部对象的引用地址,也就是说拷贝对象和原对象共用同一个内部对象。
* 深拷贝 :深拷贝会完全复制整个对象,包括这个对象所包含的内部对象。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/9906b1dbc4144aada47c4e41d80eaca6.png)


#### 11、Object 的常用方法有哪些?



> 
> clone方法:用于创建并返回当前对象的一份拷贝;  
>  getClass方法:用于返回当前运行时对象的Class;  
>  toString方法:返回对象的字符串表示形式; .  
>  finalize方法:实例被垃圾回收器回收时触发的方法;  
>  equals方法:用于比较两个对象的内存地址是否相等,一般需要重写;  
>  hashCode方法:用于返回对象的哈希值;  
>  notify方法:唤醒一个在此对象监视器上等待的线程。如果有多个线程在等待只会唤醒一一个。  
>  notifyAll方法:作用跟notify() 一样,只不过会唤醒在此对象监视器上等待的所有线程,而不是一个线程。  
>  wait方法:让当前对象等待;
> 
> 
> 


#### 12、说一说hashCode()和equals()的关系?


hashCode()用于获取哈希码(散列码),eauqls()用于比较两个对象是否相等,它们应遵守如下规定:


* 如果两个对象相等,则它们必须有相同的哈希码。
* 如果两个对象有相同的哈希码,则它们未必相等。


**为什么要重写hashCode()和equals()?**


Object类提供的equals()方法默认是用==来进行比较的,也就是说只有两个对象是同一个对象时,才能返回相等的结果。而实际的业务中,我们通常的需求是,若两个不同的对象它们的内容是相同的,就认为它们相等。鉴于这种情况,Object类中equals()方法的默认实现是没有实用价值的,所以通常都要重写。


类没有重写 equals()方法 :通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象,使用的默认是 Object类equals()方法。  
 类重写了 equals()方法 :一般我们都重写 equals()方法来比较两个对象中的属性是否相等;若它们的属性相等,则返回 true(即,认为这两个对象相等)。


**==和equals()有什么区别?**  
 对于基本数据类型来说,== 比较的是值。  
 对于引用数据类型来说,== 比较的是对象的内存地址。


#### 13、String、StringBuffer、StringBuilder 的区别?


主要从**可变性、线程安全性、性能**三方面进行考虑:


* 可变性:String 是不可变的(使用final关键字修饰字符数组来保存字符串),StringBuilder和StringBuffer非常类似,均代表可变的字符序列,而且方法也一样,都是继承 AbstractStringBuilder。
* 线程安全性 :String 中的对象是不可变的,也就可以理解为常量,线程安全;StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。
* 性能:每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象;StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用;相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/79ccd68b1638425fadcad59751670d18.png)  
 **效率: StringBuilder > StringBuffer > String**


**String、StringBuffer 和StringBuilder 的选择**


* 1.如果字符串存在大量的修改操作,一般使用StringBuffer或StringBuilder
* 2.如果字符串存在大量的修改操作,并在单线程的情况,使用StringBuilder
* 3.如果字符串存在大量的修改操作,并在多线程的情况,使用StringBuffer
* 4.如果我们字符串很少修改,被多个对象引用,使用String,比如配置信息等


操作少量的数据: 适用 String  
 单线程操作字符串缓冲区下操作大量数据: 适用 StringBuilder  
 多线程操作字符串缓冲区下操作大量数据: 适用 StringBuffer


#### 14、说一说你对字符串拼接的理解?


* +运算符:如果拼接的都是字符串直接量,则适合使用 + 运算符实现拼接;
* StringBuilder:如果拼接的字符串中包含变量,并不要求线程安全,则适合使用StringBuilder;
* StringBuffer:如果拼接的字符串中包含变量,并且要求线程安全,则适合使用StringBuffer;
* String类的concat方法:如果只是对两个字符串进行拼接,并且包含变量,则适合使用concat方法;


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


##### 语法区别(构造方法、静态方法、普通成员变量、非抽象的普通方法、访问类型,继承)


1.抽象类可以有构造方法,接口中不能有构造方法。  
 2.抽象类中可以有普通成员变量,接口中没有普通成员变量  
 3.抽象类中可以包含非抽象的普通方法,接口中的所有方法必须都是抽象的,不能有非抽象的普通方法。  
 4.抽象类中的抽象方法的访问类型可以是public,protected、但接口中的抽象方法只能是public类型的,并且默认即为public abstract类型。  
 5.抽象类中可以包含静态方法,接口中不能包含静态方法;抽象类和接口中都可以包含静态成员变量,抽象类中的**静态成员变量的访问类型可以任意**,但接口中定义的变量只能是public static final类型,并且默认即为public static final类型。  
 7.一个类可以实现多个接口,但只能继承一个抽象类。


##### 应用区别(系统架构、代码的重用):


接口更多的是在**系统架构设计方法发挥作用**,主要用于定义**模块之间的通信契约**。而抽象类在代码实现方面发挥作用,可以实现**代码的重用**。


### 异常篇


Java 异常类层次结构图概览 :  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/e100a3eca280478da0be7e7783efbac3.png)


#### 1、Exception 和 Error 有什么区别?


在 Java 中,所有的异常都有一个共同的祖先 java.lang 包中的 Throwable 类。Throwable 类有两个重要的子类:


* Exception :程序本身可以处理的异常,可以通过 catch 来进行捕获。Exception 又可以分为 Checked Exception (受检查异常,必须处理) 和 Unchecked Exception (不受检查异常,可以不处理)。
* Error :Error 属于程序无法处理的错误 ,我们没办法通过 catch 来进行捕获不建议通过catch捕获 。例如 Java虚拟机运行错误(Virtual MachineError)、虚拟机内存不够错误(OutOfMemoryError)、类定义错误(NoClassDefFoundError)等。这些异常发生时,Java 虚拟机(JVM)一般会选择线程终止。


#### 2、运行时异常(RuntimeException)包含哪些?


RuntimeException 及其子类都统称为非受检查异常,常见的有(建议记下来,日常开发中会经常用到):


NullPointerException(空指针错误)  
 IllegalArgumentException(参数错误比如方法入参类型错误)  
 NumberFormatException(字符串转换为数字格式错误,IllegalArgumentException的子类)  
 ArrayIndexOutOfBoundsException(数组越界错误)  
 ClassCastException(类型转换错误)  
 ArithmeticException(算术错误)  
 SecurityException (安全错误比如权限不够)  
 UnsupportedOperationException(不支持的操作错误比如重复创建同一用户)


#### 3、try-catch-finally 如何使用?


* try块 : 用于捕获异常。其后可接零个或多个 catch 块,如果没有 catch 块,则必须跟一个 finally 块。
* catch块: 用于处理 try 捕获到的异常。
* finally 块 : 无论是否捕获或处理异常,finally 块里的语句都会被执行。当在 try块或 catch 块中遇到 return 语句时,finally 语句块将在方法返回之前被执行。



try {
System.out.println(“Try to do something”);
throw new RuntimeException(“RuntimeException”);
} catch (Exception e) {
System.out.println("Catch Exception -> " + e.getMessage());
} finally {
System.out.println(“Finally”);
}
输出:
Try to do something
Catch Exception -> RuntimeException
Finally



> 
> 1、不管有木有出现异常,finally块中代码都会执行;  
>  2、当try和catch中有return时,finally仍然会执行;  
>  3、finally是在return语句执行之后,返回之前执行的(此时并没有返回运算后的值,而是先把要返回的值保存起来,不管finally中的代码怎么样,返回的值都不会改变,仍然是之前保存的值),所以函数返回值是在finally执行前就已经确定了;  
>  4、finally中如果包含return,那么程序将在这里返回,而不是try或catch中的return返回,返回值就不是try或catch中保存的返回值了。
> 
> 
> 


#### 4、异常使用有哪些需要注意的地方?


* 不要把异常定义为静态变量,因为这样会导致异常栈信息错乱。每次手动抛出异常,我们都需要手动 new 一个异常对象抛出。
* 抛出的异常信息一定要有意义。
* 建议抛出更加具体的异常比如字符串转换为数字格式错误的时候应该抛出NumberFormatException而不是其父类IllegalArgumentException。
* 使用日志打印异常之后就不要再抛出异常了(两者不要同时存在一段代码逻辑中)。


### 泛型


Java 泛型(Generics) 是 JDK 5 中引入的一个新特性。使用泛型参数,可以增强代码的**可读性以及稳定性**。


编译器可以对泛型参数进行检测,并且通过泛型参数可以指定传入的对象类型。比如 ArrayList persons = new ArrayList() 这行代码就指明了该 ArrayList 对象只能传入 Persion 对象,如果传入其他类型的对象就会报错。



ArrayList extends AbstractList


优点:1,**类型安全**、2,**消除强制类型转换**、3,**潜在的性能收益**。


注意:泛型只是提高了数据传输安全性,并没有改变程序运行的性能


#### 1、什么是类型擦除?


类型擦除:泛型信息只存在于代码编译阶段,在进入JVM之前,与泛型相关的信息会被擦除掉,专业术语叫做类型擦除。在泛型类被类型擦除的时候,之前泛型类中的类型参数部分如果没有指定上限,如则会被转译成普通的Object类型,如果指定了上限如< T extends String >则类型参数就被替换成类型上限。



List list = new ArrayList()


、两个 String 其实只有第⼀个起作⽤,后⾯⼀个没什么卵⽤,只不过 JDK7 才开始⽀持 Listlist = new ArrayList<> 这种写法。  
 2、第⼀个 String 就是告诉编译器,List 中存储的是 String 对象,也就是起类型检查的作⽤,之后编译器会擦除泛型占位符,以保证兼容以前的代码。


#### 2、项目中哪里用到了泛型?


* 自定义接口通用返回结果 CommonResult 通过参数 T 可根据具体的返回类型动态指定结果的数据类型
* 定义 Excel 处理类ExcelUtil 用于动态指定 Excel 导出的数据类型
* 构建集合工具类(参考 Collections 中的 sort, binarySearch 方法)。


### 反射


#### 1、什么是反射?


每个类都有一个Class对象,包含了与类有关的信息。当编译一个新类时,会产生一个同名的.class文件,该文件内容保存着Class对象。类加载相当于Class对象的加载,类在第一次使用时才动态加载到JVM中。也可以使用Class.forName(“com.mysql.jdbc.Driver”)这种方式来控制类的加载,该方法会返回一个Class对象。


具体来说,通过反射机制,我们可以实现如下的操作:


* 程序运行时,可以通过反射获得任意一个类的Class对象,并通过这个对象查看这个类的信息;
* 程序运行时,可以通过反射创建任意一个类的实例,并访问该实例的成员;
* 程序运行时,可以通过反射机制生成一个类的动态代理类或动态代理对象。


#### 2、反射机制的优缺点?


* 优点:运行期类型的判断,class.forName() 动态加载类,提⾼代码的灵活度;
* 缺点:尽管反射⾮常强⼤,但也不能滥⽤;1、性能开销 :反射涉及了动态类型的解析,所以 JVM ⽆法对这些代码进⾏优化。2、安全限制 :使⽤反射技术要求程序必须在⼀个没有安全限制的环境中运⾏。如果⼀个程序必须在有安全限制的环境中运⾏,如 Applet,那么这就是个问题了;3、内部暴露(可能导致代码功能失调并破坏可移植性);


#### 3、在实际项目中有哪些应用场景?


* 使用JDBC时,如果要创建数据库的连接,则需要先通过反射机制加载数据库的驱动程序;
* 多数框架都支持注解/XML配置,从配置中解析出来的类是字符串,需要利用反射机制实例化;
* 面向切面编程(AOP)的实现方案,是在程序运行时创建目标对象的代理类,这必须由反射机制来实现。


### Java序列化


如果我们需要持久化 Java 对象比如将 Java 对象保存在文件中,或者在网络传输 Java 对象,这些场景都需要用到序列化。


* 序列化: 将数据结构或对象转换成二进制字节流的过程
* 反序列化:将在序列化过程中所生成的二进制字节流的过程转换成数据结构或者对象的过程


**序列化的主要目的是通过网络传输对象或者说是将对象存储到文件系统、数据库、内存中。**  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/0bc0eedc82db431381402c415c784fec.png)


#### 1、实际开发中有哪些用到序列化和反序列化的场景?


* 1、对象在进行网络传输(比如远程方法调用 RPC 的时候)之前需要先被序列化,接收到序列化的对象之后需要再进行反序列化;
* 2、将对象存储到文件中的时候需要进行序列化,将对象从文件中读取出来需要进行反序列化。
* 3、将对象存储到缓存数据库(如 Redis)时需要用到序列化,将对象从缓存数据库中读取出来需要反序列化。


#### 2、序列化协议对应于 TCP/IP 4 层模型的哪一层?


我们知道网络通信的双方必须要采用和遵守相同的协议。TCP/IP 四层模型是下面这样的,序列化协议属于哪一层呢?


* 应用层 、传输层 、网络层 、网络接口层  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/b0248c6d35af48d5b28381e58b33dc71.png)  
 表示层做的事情主要就是对应用层的用户数据进行处理转换为二进制流。反过来的话,就是将二进制流转换成应用层的用户数据。这不就对应的是序列化和反序列化么?


因为,OSI 七层协议模型中的应用层、表示层和会话层对应的都是 TCP/IP 四层模型中的应用层,所以序列化协议属于 TCP/IP 协议应用层的一部分。


### 集合类


#### 1、Java中有哪些容器(集合类)?


![在这里插入图片描述](https://img-blog.csdnimg.cn/17edb28567da47c2908387ef3f7e88e7.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA56CU6KGM56yU5b2V,size_19,color_FFFFFF,t_70,g_se,x_16)


#### 2、ArrayList 与 LinkedList 区别?


* 是否保证线程安全: ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全;
* 底层数据结构: ArrayList 底层使用的是 Object 数组;LinkedList 底层使用的是 双向链表 数据结构
* 插入和删除是否受元素位置的影响:ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。 LinkedList 采用链表存储,所以,如果是在头尾插入或者删除元素不受元素位置的影响
* 是否支持快速随机访问: LinkedList 不支持高效的随机元素访问,而 ArrayList 支持。
* 内存空间占用: ArrayList 的空 间浪费主要体现在在 list 列表的结尾会预留一定的容量空间,而 LinkedList 的空间花费则体现在它的每一个元素都需要消耗比 ArrayList 更多的空间


#### 3、Arraylist 和 Vector 的区别?


* ArrayList 是 List 的主要实现类,底层使用 Object[ ]存储,适用于频繁的查找工作,线程不安全 ;
* Vector 是 List 的古老实现类,底层使用 Object[ ]存储,线程安全的。


#### 4、ArrayList的扩容机制


使用ArrayList()创建ArrayList对象时,不会定义底层数组的长度,当第一次调用add(E e) 方法时,初始化定义底层数组的长度为10,之后调用add(E e)时,如果需要扩容,则调用grow(int minCapacity) 进行扩容,长度为原来的1.5倍。


#### 5、HashMap 和 HashSet 的区别


**HashSet 底层就是基于 HashMap 实现的**  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/435fc143f6d64ef08ccf54fc7d7fbec3.png)


#### 6、HashMap 和 Hashtable 的区别


* 线程是否安全: HashMap 是非线程安全的,Hashtable 是线程安全的,因为 Hashtable 内部的方法基本都经过synchronized 修饰。(如果你要保证线程安全的话就使用 ConcurrentHashMap );
* 效率: 因为线程安全的问题,HashMap 要比 Hashtable 效率高一点;
* 对 Null key 和 Null value 的支持:HashMap 可以存储 null 的 key 和 value,但 null 作为键只能有一个,null 作为值可以有多个;Hashtable 不允许有 null 键和 null 值,否则会抛出 NullPointerException。
* 初始容量大小和每次扩充容量大小的不同 :1、Hashtable 默认的初始大小为 11,之后每次扩充,容量变为原来的 2n+1;HashMap 默认的初始化大小为 16。之后每次扩充,容量变为原来的 2 倍;2、创建时如果给定了容量初始值,那么 Hashtable 会直接使用你给定的大小,而 HashMap 会将其扩充为 2 的幂次方大小(HashMap 中的tableSizeFor()方法保证,下面给出了源代码)。
* 底层数据结构:HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)时,将链表转化为红黑树(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树),以减少搜索时间(后文中我会结合源码对这一过程进行分析)。Hashtable 没有这样的机制。


#### 7、HashMap底层实现原理


HashMap底层就是一个数组结构,数组中的每一项又是一个链表。当新建一个HashMap的时候,就会初始化一个数组。


JDK1.7之前:数组 + 链表


现在JDK1.8之后:数组+链表+红黑树进行数据的存储,当链表上的元素个数超过 8 个并且数组⻓度 >= 64 时⾃动转化成红⿊树,节点变成树节点,以提⾼搜索效率和插⼊效率到 O(logN)。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/dea402b62f9149408ced654a0cc0a626.png)



> 
> 红黑树特点:  
>  1、每个节点或者是黑色,或者是红色。  
>  2、根节点是黑色。  
>  3、每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!]  
>  4、如果一个节点是红色的,则它的子节点必须是黑色的。  
>  5、从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。[这里指到叶子节点的路径]包含n个内部节点的红黑树的高度是 O(log(n)).
> 
> 
> 


优点:红黑树是一种平衡树,他复杂的定义和规则都是为了保证树的平衡性。如果树不保证他的平衡性就是下图:很显然这就变成一个链表了。


#### 8、HashMap为什么用红黑树,而不是用B+树、AVL树?


##### 红黑树 和 AVL树区别


* 两者都是平衡二叉树,但是红黑树不要求所有节点高度差不超过1,只要求1给节点到所有节点的路径中,最长路径不能超过最短路径的两倍,所以红黑树保持的是**大致平衡**。
* 在进行插入或者删除元素后,平衡二叉树的旋转操作 消耗高于红黑树(省略了没有必要的调整)
* 红黑树的插入、删除的效率高于AVL树


##### 红黑树 和 B+树区别


1、如果用B+树的话,在数据量不是很多的情况下,数据都会“挤在”一个结点里面。这个时候遍历效率就退化成了链表  
 2、B和B+树主要用于数据存储在磁盘上的场景,比如数据库索引就是用B+树实现的;而红黑树多用于内存中排序,也就是内部排序。


#### 9、HashMap实现存储和读取?


![在这里插入图片描述](https://img-blog.csdnimg.cn/fe10001ce7184bdba1beb897662440cf.png)


##### Put方法的执行过程


* 首先判断数组是否为空,如果是,则进行初始化。
* 其次,根据\*\*(n - 1) &hash\*\*求出要添加对象所在的索引位置,判断此索引的内容是否为空,如果是,则直接存储。
* 如果不是,则判断索引位置的对象和要存储的对象是否相同,首先判断hash值知否相等,在判断key是否相等。(1.两个对象的hash值不同,一定不是同一个对象。2.hash值相同,两个对象也不一定相等)。
* 如果是同一个对象,则直接进行覆盖,返回原值。
* 如果不是,则判断是否为树节点对象,如果是,直接添加
* 当既不是相同对象,又不是树节点,直接将其插入到链表的尾部。在进行判断是否需要进行树化。
* 最后,判断hashmap的size是否达到阈值,进行扩容resize()处理。


##### Get方法的执行过程


通过 key 的 hash 值找到在 table 数组中的索引处的 Entry,然后返回该 key 对应的 value 即可。



> 
> 在这⾥能够根据 key 快速的取到 value 除了和 HashMap 的数据结构密不可分外,还和 Entry 有莫大的关系。HashMap 在存储过程中并没有将 key,value 分开来存储,⽽是当做⼀个整体 key-value 来处理的,这个整体就是Entry 对象。同时 value 也只相当于 key 的附属⽽已。在存储的过程中,系统根据 key 的 HashCode 来决定 Entry 在 table 数组中的存储位置,在取的过程中同样根据 key 的 HashCode 取出相对应的 Entry 对象(value 就包含在  
>  ⾥⾯)
> 
> 
> 


#### 10、HashMap的扩容机制?


有两种情况会调用resize 方法:  
 1.第一次调用HashMap的put方法时,会调用resize方法对table数组进行初始化,如果不传入指定值,默认大小为16。  
 2.扩容时会调用resize,即size > threshold时,table 数组大小翻倍。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/5fbbd89763c6464f89fe2c8a6105dea8.png)


#### 11、HashMap 的 size 为什么必须是 2 的整数次方?


为了快速运算出键值对存储的索引和让键值对均匀分布



> 
> 首先计算键值对的索引要满足两个要求:不能越界、均匀分布;而 h % length (h根据key计算出来的哈希值)就能满足这一点,但是取模运算速度较慢。  
>  容量为2的次幂时、而 h & (length-1)刚好也能满足,而且按位与运算速度很快。
> 
> 
> 


#### 12、HashTable 和 ConcurrentHashMap 的区别?



> 
> HashMap 与 ConcurrentHashMap的区别是:HashMap不是线程安全的,ConcurrentHashMap是线程安全的。
> 
> 
> 


HashTable 和 ConcurrentHashMap 相⽐,效率低。Hashtable 之所以效率低主要是使⽤了 synchronized 关键字对 put 等操作进⾏加锁,而 **synchronized 关键字加锁是对整张 Hash 表的**,即每次锁住整张表让**线程独占**,致使效率低下;ConcurrentHashMap 在对象中保存了⼀个 **Segment 数组**,即将整个 Hash 表划分为多个分段;而**每个Segment元素,即每个分段则类似于⼀个Hashtable**;在执行 put 操作时⾸先根据 hash 算法定位到元素属于哪个 Segment,然后对该 Segment 加锁即可,因此,ConcurrentHashMap 在多线程并发编程中可是实现多线程 put操作。


#### 13、Iterator 怎么使用?有什么特点?


迭代器是⼀种设计模式,它是⼀个对象,它可以遍历并选择序列中的对象,⽽开发⼈员不需要了解该序列的底层结构。迭代器通常被称为“轻量级”对象,因为创建它的代价⼩。Java 中的 Iterator 功能⽐较简单,并且只能单向移动:


1. 使⽤⽅法 iterator() 要求容器返回⼀个 Iterator。第⼀次调⽤ Iterator 的 next() ⽅法时,它返回序列的第⼀个元素。注意:iterator() ⽅法是 java.lang.Iterable 接⼝,被 Collection 继承。
2. 使⽤ next() 获得序列中的下⼀个元素。
3. 使⽤ hasNext() 检查序列中是否还有元素。
4. 使⽤ remove() 将迭代器新返回的元素删除。


#### 14、Iterator 和 Enumeration 接口的区别?


与 Enumeration 相⽐,Iterator 更加安全,因为当⼀个集合正在被遍历的时候,它会阻⽌其它线程去修改集合。否则会抛出 ConcurrentModificationException 异常。


1. Iterator 的⽅法名比 Enumeration 更科学;
2. Iterator 有 fail-fast 机制,⽐ Enumeration 更安全;
3. Iterator 能够删除元素,Enumeration 并不能删除元素。



> 
> 快速失败机制是java集合的一种错误检测机制,当迭代集合时集合的结构发生改变,就会产生fail-fast机制。  
>  一旦发现遍历的同时,其他人来修改,就立刻抛出异常。fail\_salf:当遍历的时候,其他人来修改,应当有相应的策略,例如牺牲一致性来遍历整个数组。
> 
> 
> 


### IO


#### 1、什么是IO?


I/O(Input/Outpu) 即输入/输出 。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/52cc60f65b2048f480da090e4e4b3a23.png)  
 输入设备(比如键盘)和输出设备(比如显示器)都属于外部设备。网卡、硬盘这种既可以属于输入设备,也可以属于输出设备。  
 **从计算机结构的视角来看的话, I/O 描述了计算机系统与外部设备之间通信的过程。**



> 
> 为了保证操作系统的稳定性和安全性,一个进程的地址空间划分为 用户空间(User space) 和 内核空间(Kernel space ); 用户进程想要执行 IO 操作的话,必须通过 系统调用 来间接访问内核空间
> 
> 
> 


常见的 IO 模型?  
 UNIX 系统下, IO 模型一共有 5 种: 同步阻塞 I/O、同步非阻塞 I/O、I/O 多路复用、信号驱动 I/O 和异步 I/O。


#### 2、Java 中 3 种常见 IO 模型


##### BIO (Blocking I/O)


BIO 属于同步阻塞 IO 模型 。  
 同步阻塞 IO 模型中,应用程序发起 read 调用后,会一直阻塞,直到内核把数据拷贝到用户空间。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/7879316bf7de43d59a3c2ca0d2902542.png)  
 在客户端连接数量不高的情况下,是没问题的。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。


##### NIO (Non-blocking/New I/O)


NIO 中的 N 可以理解为 Non-blocking,不单纯是 New。它是支持面向缓冲的,基于通道的 I/O 操作方法。 对于高负载、高并发的(网络)应用,应使用 NIO 。


Java 中的 NIO 可以看作是 I/O 多路复用模型。也有很多人认为,Java 中的 NIO 属于同步非阻塞 IO 模型。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/eb33da72ada343149ffe0b92bfab2a35.png)  
 同步非阻塞 IO 模型中,应用程序会一直发起 read 调用,等待数据从内核空间拷贝到用户空间的这段时间里,线程依然是阻塞的,直到在内核把数据拷贝到用户空间。  
 相比于同步阻塞 IO 模型,同步非阻塞 IO 模型确实有了很大改进。通过轮询操作,避免了一直阻塞。


但是,这种 IO 模型同样存在问题:应用程序不断进行 I/O 系统调用轮询数据是否已经准备好的过程是十分消耗 CPU 资源的。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/1ea90fc7faaf4740a468167cc1b88e29.png)  
 IO 多路复用模型中,线程首先发起 select 调用,询问内核数据是否准备就绪,等内核把数据准备好了,用户线程再发起 read 调用。read 调用的过程(数据从内核空间 -> 用户空间)还是阻塞的。



> 
> IO 多路复用模型,通过减少无效的系统调用,减少了对 CPU 资源的消耗。
> 
> 
> 


##### AIO (Asynchronous I/O)


异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/afb815228d2e4032abbb84b270908a4f.png)  
 总结:简单总结一下 Java 中的 BIO、NIO、AIO  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/d18422ffcc384aef8c35fbcf4343672a.png)


#### 3、介绍一下Java中的IO流


IO(Input Output)用于实现对数据的输入与输出操作,Java把不同的输入/输出源(键盘、文件、网络等)抽象表述为流(Stream)。流是从起源到接收的有序数据,有了它程序就可以采用同一方式访问不同的输入/输出源。


* 按照数据流向,可以将流分为**输入流和输出流**,其中输入流只能读取数据、不能写入数据,而输出流只能写入数据、不能读取数据。
* 按照数据类型,可以将流分为**字节流和字符流**,其中字节流操作的数据单元是8位的字节,而字符流操作的数据单元是16位的字符。
* 按照处理功能,可以将流分为**节点流和处理流**,其中节点流可以直接从/向一个特定的IO设备(磁盘、网络等)读/写数据,也称为低级流,而**处理流是对节点流的连接或封装**,用于**简化数据读/写功能**或提高效率,也称为高级流。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/9c4e7336e028493f9b6e0bb8b62bb8d4.png)



> 
> 黑色字体的是抽象基类,其他所有的类都继承自它们。红色字体的是节点流,蓝色字体的是处理流。
> 
> 
> 


#### 4、Java IO涉及的设计模式有哪些?


##### 装饰器模式


装饰器(Decorator)模式 可以在**不改变原有对象的情况下拓展其功能**。  
 装饰器模式通过组合替代继承来扩展原始类的功能,在一些继承关系比较复杂的场景(IO 这一场景各种类的继承关系就比较复杂)更加实用。  
 对于字节流来说, FilterInputStream (对应输入流)和FilterOutputStream(对应输出流)是装饰器模式的核心,分别用于增强 InputStream 和OutputStream子类对象的功能。


我们常见的BufferedInputStream(字节缓冲输入流)、DataInputStream 等等都是FilterInputStream 的子类,BufferedOutputStream(字节缓冲输出流)、DataOutputStream等等都是FilterOutputStream的子类。



> 
> 装饰器类需要跟原始类继承相同的抽象类或者实现相同的接口
> 
> 
> 


##### 适配器模式


适配器(Adapter Pattern)**模式主要用于接口互不兼容的类的协调工作**,你可以将其联想到我们日常经常使用的电源适配器。


适配器模式中存在被适配的对象或者类称为 适配者(Adaptee) ,作用于适配者的对象或者类称为适配器(Adapter) 。适配器分为对象适配器和类适配器。类适配器使用继承关系来实现,对象适配器使用组合关系来实现。


IO 流中的字符流和字节流的接口不同,它们之间可以协调工作就是基于适配器模式来做的,更准确点来说是对象适配器。通过适配器,我们可以将字节流对象适配成一个字符流对象,这样我们可以直接通过字节流对象来读取或者写入字符数据。


##### 适配器模式和装饰器模式有什么区别呢?


**装饰器模式** 更侧重于动态地增强原始类的功能,装饰器类需要跟原始类继承相同的抽象类或者实现相同的接口。并且,装饰器模式支持对原始类嵌套使用多个装饰器。


**适配器模式** 更侧重于让接口不兼容而不能交互的类可以一起工作,当我们调用适配器对应的方法时,适配器内部会调用适配者类或者和适配类相关的类的方法,这个过程透明的。



> 
> 适配器和适配者两者不需要继承相同的抽象类或者实现相同的接口。
> 
> 
> 


### 并发编程


#### 1、什么是线程和进程?


**进程:**是程序运行和**资源分配**的基本单位,一个程序至少有一个进程,一个进程至少有一个线程。进程在执行过程中拥有独立的内存单元,而多个线程共享内存资源,减少切换次数,从而效率更高。  
 **线程:**是进程的一个实体,是**cpu调度和分派**的基本单位,是比程序更小的能独立运行的基本单位。同一进程中的多个线程之间可以并发执行。  
 **区别:** 线程是进程划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。线程执行开销小,但不利于资源的管理和保护;而进程正相反。



> 
> 程序计数器为什么是私有的?  
>  1、字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。  
>  2、在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。
> 
> 
> 一句话简单了解堆和方法区  
>  堆和方法区是所有线程共享的资源,其中堆是进程中最大的一块内存,主要用于存放新创建的对象(几乎所有对象都在这里分配内存),方法区主要用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
> 
> 
> 


#### 2、创建线程的几种方式?


* 继承Thread类创建线程;
* 实现Runnable接口创建线程;
* 通过Callable和Future创建线程;
* 通过线程池创建线程。


#### 2、并发与并行的区别


* 并发:两个及两个以上的作业在同一 时间段 内执行。
* 并行:两个及两个以上的作业在同一 时刻 执行。


#### 3、同步和异步的区别


* 同步 : 发出一个调用之后,在没有得到结果之前, 该调用就不可以返回,一直等待。
* 异步:调用在发出之后,不用等待返回结果,该调用直接返回。


![img](https://img-blog.csdnimg.cn/img_convert/5b884ddf99c5d684a481255823ee531a.png)
![img](https://img-blog.csdnimg.cn/img_convert/d8ff509f0cf76e6c27595d88ee38aeda.png)
![img](https://img-blog.csdnimg.cn/img_convert/7d591c324d806a3f8b56516b5bde72bd.png)

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[需要这份系统化资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618545628)**

。  
>  2、在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。
> 
> 
> 一句话简单了解堆和方法区  
>  堆和方法区是所有线程共享的资源,其中堆是进程中最大的一块内存,主要用于存放新创建的对象(几乎所有对象都在这里分配内存),方法区主要用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
> 
> 
> 


#### 2、创建线程的几种方式?


* 继承Thread类创建线程;
* 实现Runnable接口创建线程;
* 通过Callable和Future创建线程;
* 通过线程池创建线程。


#### 2、并发与并行的区别


* 并发:两个及两个以上的作业在同一 时间段 内执行。
* 并行:两个及两个以上的作业在同一 时刻 执行。


#### 3、同步和异步的区别


* 同步 : 发出一个调用之后,在没有得到结果之前, 该调用就不可以返回,一直等待。
* 异步:调用在发出之后,不用等待返回结果,该调用直接返回。


[外链图片转存中...(img-25obdeeI-1714412745539)]
[外链图片转存中...(img-sO5m1iGw-1714412745540)]
[外链图片转存中...(img-dHjhoYVc-1714412745540)]

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[需要这份系统化资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618545628)**

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值