大厂面试通行证- Java基础


前言

小编整理了一些关于Java面试时会问到的一些基础问题,之后还会陆续整理一些关于数据结构、计算机网络、操作系统、数据库,框架,算法等等相关的知识,感兴趣的话就关注一下吧!!


一、Java基础概念

1.Java、Python、C++有什么区别
(1)C++、Java和Python都是面向对象的编程语言。
(2)C++接近于底层,方便直接操作内存。C++不仅支持传统的面向过程编程,也支持面向对象编程,能够使计算机高效运行的,并提高程序的编程质量与程序设计语言的问题描述能力。但是相比其他语言,C++没有垃圾回收机制,可能引起内存设漏;且内容较多较难,学起来相对困难;
(3)Java语法规则,采用严格的面向对象编程方法,Java跨平台实现一处编译多处执行,拥有垃圾回收机制、强大的类库以及很多大型的开发框架,比较适合企业级应用。另外,Java开发会占用大量的内存,启动时间较长,不直接支持硬件级别的处理。Java可用于Android & IOS 应用开发,视频游戏开发,桌面GUIs,软件开发,架构等。
(4)Python易于学习,语法简单,融入了很多现代编程语言的特性;python拥有强大的开源类库,可以迅速地开发程序,无论是网站还是小游戏都非常方便。不过,python的脚本的运行效率较低,不适合对运行效率要求较高的程序。python主要用于爬虫,Web开发,视频游戏开发,桌面GUIs,软件开发,架构等。
2.面向过程和面向对象的区别
(1)面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了;函数是是最小的单位,是一个执行者的角色。
优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、 Linux/Unix等一般采用面向过程开发,性能是最重要的因素。
缺点:没有面向对象易维护、易复用、易扩展
适用场景:一些硬件芯片编程,一些软件核心功能库
例如:人将大象装进冰箱,每一步都是函数。第一步,人打开冰箱;第二步,人把大象塞进去;第三步,人关上冰箱,之后不断重复上述过程。
(2)面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。类和对象是最小的单位,是一个指挥者的角色。面向对象有三大特性:封装、继承、多态
优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统 更加灵活、更加易于维护
缺点:性能比面向过程低
使用场景:应用软件编程。
面向对象的设计思想是将人,冰箱,大象看成对象,定义对象的属性(大象头,身体…)和方法(把大象放入冰箱)
人:打开冰箱,把大象塞进去,人关闭冰箱
冰箱:打开,关闭的方法
大象:进入冰箱
面向对象就是高度实物抽象化、面向过程就是自顶向下的编程!
3.Java和其他语言对比,有什么优势?
跨平台,一次编译,到处执行;安全,健壮性,提供了很多内置的类库,支持多线程和垃圾回收机制。
4.什么是类和对象
类:类是一个模板,它描述一类对象的行为和状态,抽象的,概念上的。例如:猫类。
对象:对象是类的一个实例,有状态和行为。例如,一条狗是一个对象,它的状态有:颜色、名字、品种;行为有:摇尾巴、叫、吃等。
类和对象的关系:对象是由类派生出来的,new出来的。
实例化:创建类的对象过程称为实例化,是将一个抽象的概念类,具体到该类实物的过程。实例化后,就可以使用实例化的对象调用相应的属性和方法。
创建类的对象=类的实例化=实例化类
属性=成员变量=field=域、字段
行为=方法=函数
具体实现步骤:1.创建类,设计类的成员;2.创建类的对象;3.调用对象的成员
5.什么是封装
封装:对程序和数据进行封装。
作用: 1)保护信息,阻止外部随意访问内部代码和数据,保证不受外部干扰,不被误用;2)隐藏细节,如一些不需要程序员修改和使用的信息,用户只需要知道怎么用就可以,不需要知道内部如何运行;3)有利于松耦合,提高系统的独立性;4)提高软件的复用率,降低成本。
6.什么是复用
对于有用的代码单元,可以多次复用。
两种复用方法:
1)组合:把已知类放入新的类中,新旧组合。就像拿零件组装汽车。
2)继承:以现有的类为基础,添加和修改代码来创建新类,就是继承。
7.什么是继承
继承就是子类继承父类的特征和行为,使得子类具有父类相同的特征和行为。Java类不支持多继承,只支持单继承,即一个类只有一个父类,extends 只能继承一个类。 但是Java接口支持多继承,即一个子接口可以有多个父接口,implements 关键字实现多个接口。
优点:抽象出来子类中相同的行为和特征,代码更加简洁,避免了代码的重复,实现复用,也便于维护
8.什么是多态
多态:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。多态是指通过指向父类的指针,来调用在不同子类中实现的方法。
比如牛奶可能有特仑苏、优酸乳、露露,这里所表现的的就是多种形态。特仑苏、优酸乳、露露都是牛奶的子类,我们只是通过牛奶这一个父类就能够引用不同的子类,这就是多态。
特点:要有继承,要有重写,父类的引用指向子类对象
优点:灵活 ,简单,能够提高编码效率,简化代码编写和修改,便于扩充功能。(开闭原则)

Animal animal = new Cat(); 

将子类对象 Cat 转化为父类对象 Animal。这个时候 animal 这个引用调用的方法是子类方法。
9.Java中的方法重写和方法重载是什么意思?
重载:在同一个类里面两个或者是多个方法的方法名相同但是参数不同的情况。
重写/覆盖:发生在子类和父类之间,必须有相同的方法名,参数列表和返回类型。覆盖者可能不会限制它所覆盖的方法的访问,子类可以根据需要,定义特定于自己的行为,可以添加新的功能。
10.什么是Java虚拟机
Java虚拟机是一个可以执行Java字节码的虚拟机进程。
11.为什么Java被称作是“平台无关的编程语言”?
Java源程序先经过javac编译成二进制的字节码文件,再通过JVM将字节码文件解释成对应平台的机器码进行执行,所以Java所谓的跨平台就是在不同平台上安装了不同的JVM,JVM屏蔽了与平台相关的信息。使得Java编译时只需要生成在JVM上运行的字节码,由虚拟机在具体平台运行,从而实现一次编译,处处执行。
机器码,完全依附硬件而存在,并且不同硬件由于内嵌指令集不同,即使相同的0 1代码,意思也可能是不同的。比如不同型号的CPU,同一条指令10001101,也可能会解析为不同的结果。
12. JDK和JRE的区别
JRE(Java Runtime Environment)是Java运行时环境,包含了Java虚拟机,Java基础类库。JRE是Java语言编写的程序运行所需要的软件环境。
JDK(Java Development Kit)是Java开发工具包,是程序员使用Java语言编写Java程序所需的开发工具包。JDK包含了JRE,同时还包含了编译Java源码的编译器Javac,还包含了很多Java程序调试和分析的工具:Jconsole,Jvisualvm等工具软件以及Java程序编写所需的文档和demo例子程序。
如果需要运行Java程序,只需安装JRE就可以了。如果需要编写Java程序,需要安装JDK。
13. Java支持的数据类型有哪些?
Java支持的数据类型包括两种,基本数据类型和引用类型。
基本类型包括8种:
byte(1字节),[-128,127]
short(2字节), [-32768,32767]
int(4字节)
long(8字节)
boolean ,默认值false
char(2字节), [-32768,32767]
float(4字节),默认值0.0
double(8字节); 默认值0.0
基本数据类型的好处:Java提供了基本数据类型,这种数据的变量不需要使用new创建,他们不会在堆上创建,而是直接在栈内存中存储,因此会更加高效。
另一种是引用类型:是对象的引用,如String等。JVM虚拟栈中存的是对象的地址,创建的对象实质在堆中,通过地址来找到堆中的对象的过程,即为引用类型,默认值null。
14. Java 中都有哪些引用类型?
每种编程语言都有自己操作内存中元素的方式,例如在C和C++里是通过指针,而在 Java 中则是通过“引用”。
1)强引用:Java中默认声明的就是强引用,只要强引用存在,垃圾回收器将永远不会回收被引用的对象,哪怕内存不足时,JVM也会直接抛出OutOfMemoryError,不会去回收。如果想中断强引用与对象之间的联系,可以显示的将强引用赋值为null,这样一来,JVM就可以适时的回收对象了。
2)软引用:软引用是用来描述一些非必需但仍有用的对象。在内存足够的时候,软引用对象不会被回收,只有在内存不足时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常。这种特性常常被用来实现缓存技术,比如网页缓存,
3)弱引用:弱引用的引用强度比软引用要更弱一些,无论内存是否足够,只要 JVM 开始进行垃圾回收,那些被弱引用关联的对象都会被回收。
4)虚引用:是最弱的一种引用关系,如果一个对象仅持有虚引用,那么它就和没有任何引用一样,它随时可能会被回收。虚引用的作用:主要用来跟踪对象被垃圾回收的活动。
15.什么是自动拆装箱
Java语言是面向对象的语言,但是Java中的基本数据类型却是不面向对象的。为了让基本类型也具有对象的特征,就出现了包装类型。比如,在集合类中,我们是无法将int 、double等类型放进去的。因为集合的容器要求元素是Object类型。在Java SE5中,为了减少开发人员的工作,Java提供了自动拆箱与自动装箱功能。
自动装箱: 就是将基本数据类型自动转换成对应的包装类。
自动拆箱:就是将包装类自动转换成对应的基本数据类型。
比如:把int转化成Integer,double转化成Double等等。反之就是自动拆箱。
16.Java中,什么是构造方法?什么是构造方法重载?
当新对象被创建时,构造方法会被调用。每一个类都有构造方法。当程序中没有提供构造方法的情况下,Java编译器会为这个类创建一个默认的构造方法。
Java中构造方法重载和方法重载很相似。可以为一个类创建多个构造方法。每一个构造方法必须有它自己唯一的参数列表。
17.修饰符的访问权限范围
成员变量可以使用权限修饰符,局部变量不可以使用权限修饰符
在这里插入图片描述
18.什么是泛型,泛型的好处?
泛型,即参数化类型,支持创建可以按类型进行参数化的类。可以把类型参数看作是使用参数化类型时指定的类型的一个占位符,就像方法的形式参数是运行时传递的值的占位符一样。泛型就是编写模板代码来适应任意类型;
泛型的好处是简单易用,类型安全,使用时不必对类型进行强制转换,它通过编译器对类型进行检查。

1.关键字

1.super和this关键字
1)super关键字:用于引用父类中的属性和方法,super.属性、super.方法()
2)this关键字:用于引用本类中的属性和方法,this.属性、this.方法()
2.final关键字在 Java 中有什么作用?
final 修饰的类叫最终类,该类不能被继承。
final 修饰的方法不能被重写。
final 修饰的变量叫常量,常量必须初始化,初始化之后值就不能被修改。
3. static关键字是什么意思?
“static”关键字表明一个成员变量或者是成员方法可以在没有所属的类的实例变量的情况下被访问。
static关键字的作用如下:
1)修饰变量:静态变量在类加载的时候被创建并初始化,只被创建一次(类加载只进行一次),可以修改;静态变量属于整个类而不属于某个对象。
2)修饰方法:可以通过类名来访问,不需要创建对象来访问,可以用来实现单例模式;静态方法只能调用静态方法和静态变量,不能调用非静态的方法。
3)修饰静态代码块:在类加载的时候执行,且只执行一次

二、抽象与接口

1. 抽象类必须要有抽象方法吗?
不需要,抽象类不一定非要有抽象方法。

abstract class Cat {
    public static void sayHi() {
        System.out.println("hi~");
    }
}

抽象类并没有抽象方法但完全可以正常运行。
2. 普通类和抽象类有哪些区别?
普通类不能包含抽象方法,抽象类可以包含抽象方法。
抽象类不能直接实例化,也就是不能创建对象,普通类可以直接实例化。
3. 抽象类能使用 final 修饰吗?
不能,定义抽象类就是让其他类继承的,如果定义为 final 该类就不能被继承,这样彼此就会产生矛盾,所以 final 不能修饰抽象类
4.接口和抽象类的区别是什么?
接口:接口泛指供别人调用的方法或者函数。
抽象类:包含抽象方法的类,叫做抽象类。使用abstract关键字修饰,只有声明,没有具体实现。
区别:抽象类的子类使用 extends 来继承;接口必须使用 implements 来实现接口。类可以实现很多个接口;但是只能继承一个抽象类。
接口中只能进行方法的声明不能实现,抽象类既可以声明,也可以进行方法的实现,成员变量也可以是各种类型的,而接口中成员变量只能是public static final类型的;抽象类可以有 main 方法、构造函数,并且我们能运行它;接口不能有。接口一般用于抽象功能,抽象类用于抽象类别。

三、常用类

1.有哪些常用的Java类
所有类的父类Object类、Java数学运算Math类、StringBuilder类、String类、日期Date类、包装类、集合类、异常类

1.Object类

1.Object类有哪些方法
1)getClass方法:获取运行时类型,返回值为Class对象
2)hashCode方法:返回该对象的哈希值
3)equals方法:判断两个对象是否相等。
4)clone方法:clone就是复制, 在Java语言中,对象调用clone方法被,用于复制对象。所谓的复制对象,首先要分配一个和源对象同样大小的空间,在这个空间中创建一个新的对象。
5)toString方法:返回一个String字符串。
6)wait方法:多线程时用到的方法,作用是让当前线程进入等待状态,同时也会让当前线程释放它所持有的锁。直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,当前线程被唤醒,由阻塞状态变为就绪状态。
7)notify方法:多线程时用到的方法,唤醒该对象等待的某个线程
8)notifyAll方法:多线程时用到的方法,唤醒该对象等待的所有线程
9)finalize:对象在被GC释放之前一定会调用finalize方法,对象被释放前最后的挣扎,因为无法确定该方法什么时候被调用,很少使用。
2.==和equals()的关系
双=是比较两个对象的地址是否相等。对于基本类型来说是值比较,对于引用类型来说是比较的是地址;
而 equals本质上是双=,也是比较地址(引用),只是很多类重写了 equals 方法,比如 String把它变成了值比较,所以一般情况下 equals 比较值是否相等。
总结来说,==和equals对于基本类型的比较是相等的,对于引用类型要看情况。
在这里插入图片描述
3.hashcode()与equal()的关系?
hashcode()的作用:根据对象的地址来返回一串int型数字。
hashCode()与equal()的关系:两个对象的equals()相同,hashCode一定相同。hashCode相同,但equals不一定相同。
因为在散列表中,hashCode()相等即两个键值对的哈希值相等,然而哈希值相等,并不一定能得出键值对相等。Hashcode具有一定的随机性和偶然性。
4.为什么重写equals()时,一定需要重写hashCode()方法?
因为重写了equals()之后,判断两个属性值相同的对象时,会返回true,如果没有重写hashCode(),那么程序还是按照默认的使用内存地址的方法去计算,那么一定会返回false。
5.equals()与hashcode()什么时候重写?
(1)当我们需要重新定义两个对象是否相等的条件时,需要进行重写。
(2)当我们自定义一个类时,想要把它的实例保存在集合时,就需要重写equals()和hashCode()方法。如果重写了equals方法,那么如果两个对象的属性值相同,那么程序会在第三步判断中返回true。hashCode()方法,它是一个本地方法,底层是运用对象的内存地址通过哈希函数来计算的。

2.String

1.创建String 对象的两种方式
在常量池中拿对象:String str1 = “abcd”;
直接在堆内存空间创建(new)一个新的对象:String str2 = new String(“abcd”);
只要使用 new 方法,便需要创建新的对象。
2.String 类型的常量池
String 类型的常量池主要使用方法有两种:
直接使用双引号声明出来的 String 对象会直接存储在常量池中。
如果不是用双引号声明的 String 对象,可以使用 String 提供的 intern 方法
String.intern() 是一个 Native 方法,它的作用是:如果运行时常量池中已经包含一个等于此 String 对象内容的字符串,则返回常量池中该字符串的引用;如果没有,在常量池中记录此字符串的引用,并返回该引用。
3.String str = new String(“abc”)创建了几个对象?
如果常量池没有“abc”,创建了两个对象。首先在堆中创建一个指定的对象"abc",并让str引用指向该对象;在字符串常量池(方法区)中查看,是否存在内容为"abc"字符串对象;若存在,则将new出来的字符串对象与字符串常量池中的对象联系起来;若不存在,则在字符串常量池中创建一个内容为"abc"的字符串对象,并将堆中的对象与之联系起来。
4.Java 中操作字符串都有哪些类?它们之间有什么区别?
String被final关键字修饰,不能继承。
操作字符串的类有:String、StringBuffer、StringBuilder。
String 和 StringBuffer、StringBuilder 的区别在于 String 声明的是不可变的对象,每次操作都会生成新的 String 对象,然后将指针指向新的 String 对象,而 StringBuffer、StringBuilder是可变的对象,可以在原有对象的基础上进行操作,所以在经常改变字符串内容的情况下最好不要使用 String。
StringBuffer 和 StringBuilder 最大的区别在于,StringBuffer 是线程安全的,而 StringBuilder 是非线程安全的,但 StringBuilder 的性能却高于 StringBuffer,所以在单线程环境下推荐使用 StringBuilder,多线程环境下推荐使用 StringBuffer。

3.集合类

1. Java集合类框架的基本接口有哪些?
集合类接口指定了一组叫做元素的对象。集合类接口的每一种具体的实现类都可以选择以它自己的方式对元素进行保存和排序。有的集合类允许重复的键,有些不允许。
Java集合类里面最基本的接口有:
1)Collection:代表一组对象,每一个对象都是它的子元素。
2)Set:不包含重复元素的Collection。
3)List:有顺序的collection,并且可以包含重复元素。
4)Map:可以把键(key)映射到值(value)的对象,键不能重复。
在这里插入图片描述
2.Map接口的实现类有哪些?
HashMap、LinkedHashMap、TreeMap、HashTable
3. List、Set、Map 之间的区别是什么?
在这里插入图片描述
4. HashSet和TreeSet有什么区别?
HashSet是由一个hash表来实现的,因此,它的元素是无序的。add(),remove()方法的时间复杂度是O(1)。
TreeSet是由一个树形的结构来实现的,它里面的元素是有序的。因此,add(),remove()方法的时间复杂度是O(logn)。
5. 如何决定使用 HashMap 还是 TreeMap?
对于在Map中插入、删除和定位元素这类操作,HashMap是最好的选择。然而,假如你需要对一个有序的key集合进行遍历,TreeMap是更好的选择。基于你的collection的大小,也许向HashMap中添加元素会更快,将map换为TreeMap进行有序key的遍历。
6. HashMap 和 Hashtable 有什么区别?
hashMap去掉了HashTable 的contains方法,但是加上了containsValue()和containsKey()方法。
hashTable同步的,而HashMap是非同步的,效率上比hashTable要高。
hashMap允许空键值,而hashTable不允许。
7. ArrayList的扩容为什么是原容量的1.5倍
ArrayList底层是数组elementData,用于存放插入的数据。初始大小是0,当有数据插入时,默认大小DEFAULT_CAPACITY = 10。
当插入数据,导致size + 1 > elementData.length,也就是需要从容量超过目前数组长度时,需要进行扩容。
新的容量是旧容量加上旧容量值右移一位得到的。并且将旧数组内容通过Array.copyOf全部复制到新数组。此时,size还未真正+1,新旧数组长度(size一致),不过容量不同。
扩容因子最适合范围为(1, 2)。
k=1.5时,就能充分利用前面已经释放的空间。如果k >= 2,新容量刚刚好永远大于过去所有废弃的数组容量。
为什么不取扩容固定容量呢?
扩容的目的需要综合考虑这两种情况:
扩容容量不能太小,防止频繁扩容,频繁申请内存空间 + 数组频繁复制
扩容容量不能太大,需要充分利用空间,避免浪费过多空间;
而扩容固定容量,很难决定到底取多少值合适,取任何具体值都不太合适,因为所需数据量往往由数组的客户端在具体应用场景决定。依赖于当前已经使用的量 * 系数, 比较符合实际应用场景。
8.说一下 HashSet 的实现原理?
HashSet底层由HashMap实现
HashSet的值存放于HashMap的key上
HashMap的value统一为PRESENT
9.数组(Array)和列表(ArrayList)有什么区别?什么时候应该使用Array而不是ArrayList?
1)Array可以包含基本类型和对象类型,ArrayList只能包含对象类型。
2)Array大小是固定的,ArrayList的大小是动态变化的。
3)ArrayList提供了更多的方法和特性,比如:addAll(),removeAll(),iterator()等等。
4)对于基本类型数据,集合使用自动装箱来减少编码工作量。但是,当处理固定大小的基本数据类型的时候,这种方式相对比较慢。
10. ArrayList和LinkedList有什么区别?
ArrayList和LinkedList都实现了List接口,他们有以下的不同点:
1)ArrayList是基于索引的数据接口,它的底层是数组。它可以以O(1)时间复杂度对元素进行随机访问。与此对应,LinkedList是以元素列表的形式存储它的数据,每一个元素都和它的前一个和后一个元素链接在一起,在这种情况下,查找某个元素的时间复杂度是O(n)。
2)相对于ArrayList,LinkedList的插入,添加,删除操作速度更快,因为当元素被添加到集合任意位置的时候,不需要像数组那样重新计算大小或者是更新索引。
3)LinkedList比ArrayList更占内存,因为LinkedList为每一个节点存储了两个引用,一个指向前一个元素,一个指向下一个元素。
11. ArrayList 和 Vector 的区别是什么?
Vector是同步的,而ArrayList不是。然而,如果你想要在迭代的时候对列表进行改变,你应该使用CopyOnWriteArrayList。
ArrayList比Vector快,它因为有同步,不会过载。
ArrayList更加通用,因为我们可以使用Collections工具类轻易地获取同步列表和只读列表。
12. 哪些集合类是线程安全的?
vector:就比arraylist多了个同步化机制(线程安全),因为效率较低,现在已经不太建议使用。在web应用中,特别是前台页面,往往效率(页面响应速度)是优先考虑的。
statck:堆栈类,先进后出。
hashtable:就比hashmap多了个线程安全。
enumeration:枚举,相当于迭代器。
13. 什么是Java优先级队列(Priority Queue)?
PriorityQueue是一个基于优先堆的无界队列,它的元素是按照自然顺序排序的。在创建的时候,我们可以给它提供一个负责给元素排序的比较器。PriorityQueue不允许null值,因为他们没有自然顺序,或者说他们没有任何的相关联的比较器。PriorityQueue不是线程安全的,入队和出队的时间复杂度是O(log(n))。
18. 如何权衡是使用无序的数组还是有序的数组?
有序数组最大的好处在于查找的时间复杂度是O(log n),而无序数组是O(n)。有序数组的缺点是插入操作的时间复杂度是O(n),因为值大的元素需要往后移动来给新元素腾位置。相反,无序数组的插入时间复杂度是常量O(1)。
14. 快速失败(fail-fast)和安全失败(fail-safe)的区别是什么?
1)快速失败(fail—fast)
在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的结构进行了修改(增加、删除),则会抛出Concurrent Modification Exception。
原理:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果结构发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。
注意:这里异常的抛出条件是检测到 modCount=expectedmodCount 这个条件。如果集合发生变化时修改modCount值刚好又设置为了expectedmodCount值,则异常不会抛出。因此,不能依赖于这个异常是否抛出而进行并发操作的编程,这个异常只建议用于检测并发修改的bug。
场景:java.util包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过程中被修改)。
2)安全失败(fail—safe)
采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。
原理:由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发Concurrent Modification Exception。
缺点:基于拷贝内容的优点是避免了Concurrent Modification Exception,但同样地,迭代器并不能访问到修改后的内容,即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。
场景:java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改。

1.HashMap

1.Java中的HashMap是什么?
Java中的HashMap是以键值对的形式存储元素。底层被封装为一个Entry对象。每一个Entry包含一个key-value键值对。
在JDK1.7中HashMap是基于数组和链表实现的,JDK1.8中HashMap是基于数组、链表和红黑树实现的。链表是主要为了解决哈希冲突而存在的。当链表中的节点数据超过八个之后,该链表会转为红黑树来提高查询效率,从原来的O(n)到O(logn)。红黑树查询删除快,新增慢。
hashmap数组的默认初始长度capacity是16,负载因子loadFactory,代表了table的填充度有多少,默认是0.75。hashmap数组只允许一个key为null,允许多个value为null。Threshold= capacity* load factor。
Hashmap的时间复杂度是O(1);
2.为什么使用红黑树不适用完全平衡二叉树(AVL)?
红黑树和AVL树都是平衡二叉搜索树,查找时间复杂度O(logn),AVL树查找速度会相对快,但插入,删除效率低。红黑树则更加通用,在查找、添加,删除的结果比较好
3.HashMap基本操作
(1)get操作
get方法就是计算出要获取元素的hash值,找到对应位置table[i],再查看是否有链表,遍历链表,通过key的equals方法比对查找对应的记录。
(2)JDK1.7的Put操作
put元素时,首先判断表table是否为空,如果为空,初始化一个数组,不为空就根据元素的key通过hashcode方法重新计算hash值,根据hash值得到这个元素在数组中的位置(indexFor);判断在该位置上的数组是否存放了其他元素,如果为空,直接存储该元素;如果存在元素,判断要存储元素是否相同,相同就覆盖,不相同就将这个位置上的元素将以链表的形式存放,使用头插法插入元素。如果数组中该位置没有元素,就直接将该元素放到数组的该位置上。
(3)扩容操作
Hashmap1.7的扩容中主要进行两步,第一步把数组长度变为原来的两倍,第二部把旧数组的元素重新计算hash插入到新数组中(transfer())。transfer()方法将老数组中的数据逐个链表地遍历,扔到新的扩容后的数组中,我们的数组索引位置的计算是通过 对key值的hashcode进行hash扰乱运算后,再通过和 length-1进行位运算得到最终数组索引位置。
在jdk1.8时,不用重新计算hash,只用看看原来的hash值新增的一位是零还是1,如果是1,这个元素在新数组中的位置是原数组的位置加原数组长度,如果是零就采用头插法插入到原数组中。
inflateTable()用于分配地址空间,扩容需要参考threshold,threshold= capacity*loadFactory。
4.JDK1.8的put操作与1.7的差别
1)数组为空的话调用resize()扩容
2)如果链表长度大于8,转为红黑树结构,执行完成后看size是否大于阈值threshold,大于就扩容,否则直接结束。JDK1.8中会将节点插入到链表尾部。
5.根据hash值如何得到这个元素在数组中的位置,并使得尽量散列平均?
Hash算法本质上就是三步:取key的hashCode值、高位运算、取模运算。当使用hash()计算key的索引位置时,为了确保散列平均,首先使用异或运算,就是用hashcode方法得到的hash值的高16位和低16位进行异或操作;为了算出实际的存放位置(indexFor方法),使用得到的hash值和数组长度-1的值取与(h & (length-1))。这样得到的结果和传统取模运算结果一致,而且效率比取模运算高。
hash函数为什么是高16位和低16位异或:最终目的还是为了让哈希后的结果更均匀的分布,减少哈希碰撞,提升hashmap的运行效率
6.hashmap大小为什么是2的幂次方?
为了减少冲突,使元素分布的更加均匀。为了提高取模操作效率,使用h&(length-1)的方法。将数组长度设置成2幂次方,是因为2的幂次方-1后的值每一位上都是1,然后和h值与的时候,最终的结果只和key的hashcode值本身有关,这样不会造成空间浪费并且分布均匀。
如果不是2的幂次方,比如15。那么length-1的2进制就会变成1110。在h为随机数的情况下,和1110做&操作。尾数永远为0。那么0001、1001、1101等尾数为1的位置就永远不可能被entry占用。这样会造成浪费,不随机等问题。
7.Hashmap是线程安全的嘛,为什么线程不安全?
Hashmap是线程不安全的。
在JDK1.7中,当并发执行扩容操作时会造成环形链和数据丢失的情况。
在JDK1.8中,在并发执行put操作时会发生数据覆盖的情况。
8.有哪些方式实现Hashmap线程安全?
1)使用集合工具类下面的synchronizedMap
Map<String,String> map=Collections.synchronizedMap(new HashMap<>());
2)使用ConcurrentHashMap。
Map<String,String> map=new ConcurrentHashMap<>();

2.ConcurrentHashMap

1.JDK1.7和JDK1.8的ConcurrentHashMap
JDK1.7 的 ConcurrentHashMap 底层采⽤分段的数组+链表实现,⾸先将数据分为⼀段⼀段的存储,然后给每⼀段数据配⼀把锁,当⼀个线程占⽤锁访问其中⼀个段数据时,其他段的数据也能被其他线程访问。
Segment实现了ReentrantLock,所以 Segment 是⼀种可重⼊锁,扮演锁的⻆⾊。 HashEntry⽤于存储键值对数据。⼀个 ConcurrentHashMap ⾥包含⼀个Segment数组。 Segment 的结构和 HashMap 类似,是⼀种数组和链表结构,⼀个Segment 包含⼀个 HashEntry数组,每个HashEntry是⼀个链表结构的元素,每个Segment守护着⼀个 HashEntry数组⾥的元素,当对HashEntry数组的数据进⾏修改时,必须⾸先获得对应的 Segment的锁。
JDK1.8采⽤的数据结构跟 HashMap1.8 的结构⼀样,采用数组+链表/红⿊⼆叉树实现。
JDK1.8的ConcurrentHashMap 取消了 Segment 分段锁,直接采用transient volatile HashEntry<K,V>[] table保存数据,采用table数组元素作为锁,从而实现了对每一行数据进行加锁,并发控制使用Synchronized和CAS来操作来保证并发安全。数据结构跟 HashMap1.8 的结构类似,数组+链表/红⿊⼆叉树。Java 8 在链表⻓度超过⼀定阈值8时将链表(寻址时间复杂度为 O(N))转换为红⿊树(寻址时间复杂度为 O(log(N)))。synchronized 只锁定当前链表或红⿊⼆叉树的⾸节点,这样只要 hash 不冲突,就不会产⽣并发,效率⼜提升 N倍。
2.ConcurrentHashMap基本操作
1.添加元素
1)当添加一对键值对的时候,首先会去判断保存这些键值对的数组是不是初始化了,如果没有初始化就先调用initTable()方法来进行初始化过程;
2)然后通过计算hash值来确定放在数组的哪个位置;
3)如果没有hash冲突就直接CAS插入,如果hash冲突的话,则取出这个节点,如果取出来的节点的hash值是MOVED(-1)的话,则表示当前正在对这个数组进行扩容,复制到新的数组,则当前线程也去帮助复制;最后一种情况就是,如果这个节点,不为空,也不在扩容,则通过synchronized来加锁,进行添加操作
4)然后判断当前取出的节点位置存放的是链表还是树;如果是链表的话,则遍历整个链表,直到取出来的节点的key来个要放的key进行比较,如果key相等,并且key的hash值也相等的话,则说明是同一个key,则覆盖掉value,否则的话则添加到链表的末尾;如果是树的话,则调用putTreeVal方法把这个元素添加到树中去
5)最后在添加完成之后,调用addCount()方法统计size,判断在该节点处共有多少个节点(注意是添加前的个数),如果达到8个以上了的话,则调用treeifyBin方法来尝试将处的链表转为树,或者扩容数组。
2.取元素
取元素的时候,相对来说比较简单,通过计算hash来确定该元素在数组的哪个位置,然后在通过遍历链表或树来判断key和key的hash,取出value值。
计算hash值,定位到该table索引位置,如果是首节点符合就返回,如果遇到扩容的时候,会调用标志正在扩容节点ForwardingNode的find方法,查找该节点,匹配就返回,以上都不符合的话,就往下遍历节点,匹配就返回,否则最后就返回null
3.扩容操作
通过扩容数组的方式来把这些节点给分散开。然后将这些元素复制到扩容后的新的数组中,同一个链表中的元素通过hash值的数组长度位来区分,是还是放在原来的位置还是放到扩容的长度的相同位置去 。在扩容完成之后,如果某个节点的是树,同时现在该节点的个数又小于等于6个了,则会将该树转为链表。
3.Concurrenthashmap如何实现线程安全
在JDK1.7的时,ConcurrentHashMap (分段锁) ⾸先将数据分为⼀段⼀段的存储,然后给每⼀段数据配⼀把锁,当⼀个线程占⽤锁访问其中⼀个段数据时,其他段的数据也能被其他线程访问。多线程访问容器⾥不同数据段的数据,就不会存在锁竞争,提⾼并发访问率。Segment实现了ReentrantLock,所以 Segment 是⼀种可重⼊锁,扮演锁的⻆⾊。
JDK1.8的ConcurrentHashMap 取消了 Segment 分段锁,使⽤ Node 数组+链表+红⿊树的数据结构来实现,采用transient volatile HashEntry<K,V>[] table保存数据,将table数组元素作为锁,从而实现了对每一行数据进行加锁,并发控制使用Synchronized和CAS来操作来保证并发安全。
4.HashMap与ConcurrentHashMap区别
HashMap不支持并发操作,没有同步方法,ConcurrentHashMap支持并发操作,通过继承 ReentrantLock(JDK1.7重入锁)/CAS和synchronized(JDK1.8内置锁)来进行加锁(分段锁),每次需要加锁的操作锁住的是一个 segment,这样只要保证每个 Segment 是线程安全的,也就实现了全局的线程安全。
JDK1.8之前HashMap的结构为数组+链表,JDK1.8之后HashMap的结构为数组+链表+红黑树;JDK1.8之前ConcurrentHashMap的结构为segment数组+数组+链表,JDK1.8之后ConcurrentHashMap的结构为数组+链表+红黑树。
5.Java中ConcurrentHashMap的并发度是什么?
ConcurrentHashMap把实际map划分成若干部分来实现它的可扩展性和线程安全。这种划分是使用并发度获得的,它是 ConcurrentHashMap类构造函数的一个可选参数,默认值为16,这样在多线程情况下就能避免争用。

四、反射

1. 什么是反射?
Java反射:在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个属性和方法;这种动态获取的信息以及动态调用对象方法的功能称为java语言的反射机制。
Java反射机制主要提供了以下功能:
在运行时构造任意一个类的对象。
在运行时判断任意一个类所具有的成员变量和方法。
在运行时判断任意一个对象所属的类。
在运行时调用任意一个对象的方法。
2.反射的原理
Class类也是类的一种,手动编写的类被编译后会产生一个Class对象,表示创建的类的类型信息,而且这个Class对象保存在同名.class的文件中(字节码文件) 。每个通过关键字class标识的类,在内存中有且只有一个与之对应的Class对象来描述其类型信息,无论创建多少个实例对象,其依据的都是用一个Class对象。 Class类只存私有构造函数中(单例模式),因此对应Class对象只能有JVM创建和加载 Class类的对象作用是运行时提供或获得某个对象的类型信息。
在类加载的时候,jvm会创建一个class对象,获取class对象的方式的主要有三种:
(1) 根据类名:类名.class
(2) 根据对象:对象.getClass()
(3) 根据全限定类名:Class.forName(全限定类名)
在这里插入图片描述
2. 什么是 Java 序列化?什么情况下需要序列化?
序列化是Java提供的一种保存对象状态的机制,简单说就是为了保存在内存中的各种对象的状态,并且可以把保存的对象状态再读出来。
什么情况下需要序列化:
a)当你想把的内存中的对象状态保存到一个文件中或者数据库中时候;
b)当你想用套接字在网络上传送对象的时候;
c)当你想通过远程方法调用RMI传输对象的时候;

五、动态代理

1. 动态代理是什么?有哪些应用?
动态代理:当想要给实现了某个接口的类中的方法,加一些额外的处理。比如说加日志,加事务等。可以给这个类创建一个代理,故名思议就是创建一个新的类,这个类不仅包含原来类方法的功能,而且还在原来的基础上添加了额外处理的新类。这个代理类并不是定义好的,是动态生成的。具有解耦意义,灵活,扩展性强。
动态代理的应用:Spring的AOP、加事务、加权限、加日志
2. 怎么实现动态代理?
首先必须定义一个接口,还要有一个InvocationHandler(将实现接口的类的对象传递给它)处理类。再有一个工具类Proxy,代理类调用他的newInstance()可以产生代理对象,利用到InvocationHandler,拼接代理类源码,将其编译生成代理类的二进制码,利用加载器加载,并将其实例化产生代理对象,最后返回。

六、克隆

1. 为什么要使用克隆?
如果想对一个对象进行处理,又想保留原有的数据进行接下来的操作,就需要克隆了,Java语言中克隆针对的是类的实例。
2.克隆的原理
clone在堆上分配内存,分配的内存和源对象(即调用clone方法的对象)相同,然后再使用原对象中对应的各个域,填充新对象的域, 填充完成之后,clone方法返回一个新的相同的对象,同样可以把这个新对象的引用发布到外部。
3. 如何实现对象克隆?
1)实现Cloneable接口并重写Object类中的clone()方法;Cloneable 接口实际上是个标识接口,没有任何接口方法。
2)实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆。
4. 深拷贝和浅拷贝区别是什么?
浅拷贝是指在填充新的对象的时候,只是进行简单的字段赋值,即复制对象的引用地址,两个对象指向同一个内存地址,所以修改其中任意的值,另一个值都会随之变化,这就是浅拷贝(例:assign())
深拷贝是将对象及值复制过来,两个对象修改其中任意的值另一个值不会改变,这就是深拷贝(例:JSON.parse()和JSON.stringify(),但是此方法无法复制函数类型)

七、异常类

1.Java中的两种异常类型是什么?他们有什么区别?
Java中有两种异常:受检查的(checked)异常和不受检查的(unchecked)异常。不受检查的异常不需要在方法或者是构造函数上声明,就算方法或者是构造函数的执行可能会抛出这样的异常,并且不受检查的异常可以传播到方法或者是构造函数的外面。相反,受检查的异常必须要用throws语句在方法或者是构造函数上声明。
2.Java中Exception和Error有什么区别?
Exception和Error都是Throwable的子类。Exception用于用户程序可以捕获的异常情况。Error定义了不期望被用户程序捕获的异常。
3. throw和throws有什么区别?
1)Throw用于方法内部,Throws用于方法声明上
2)Throw后跟异常对象,Throws后跟异常类型
3)Throw后只能跟一个异常对象,Throws后可以一次声明多种异常类型
4.异常处理完成以后,Exception对象会发生什么变化?
Exception对象会在下一个垃圾回收过程中被回收掉。
5. final、finally、finalize 有什么区别?
final可以修饰类、变量、方法,修饰类表示该类不能被继承、修饰方法表示该方法不能被重写、修饰变量表示该变量是一个常量不能被重新赋值。
finally一般作用在try-catch代码块中,在处理异常的时候,通常我们将一定要执行的代码方法finally代码块中,表示不管是否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码。
finalize是一个方法,属于Object类的一个方法,而Object类是所有类的父类,该方法一般由垃圾回收器来调用,当我们调用System的gc()方法的时候,由垃圾回收器调用finalize(),回收垃圾。

1.Throwable

Throwable 是 Java 语言中所有错误与异常的父类。
Throwable 包含两个子类:Error(错误)和 Exception(异常),它们通常用于指示发生了异常情况。
Throwable 包含了其线程创建时线程执行堆栈的快照,它提供了 printStackTrace() 等接口用于获取堆栈跟踪数据等信息。

2.Error(错误)

程序中无法处理的错误,表示运行应用程序中出现了严重的错误。 此类错误一般表示代码运行时 JVM 出现问题。通常有 Virtual MachineError(虚拟机运行错误)、NoClassDefFoundError(类定义错误)等。比如 OutOfMemoryError:内存不足错误;StackOverflowError:栈溢出错误。此类错误发生时,JVM 将终止线程。
这些错误是不受检异常,非代码性错误。因此,当此类错误发生时,应用程序不应该去处理此类错误。按照Java惯例,我们是不应该实现任何新的Error子类的!

3.Exception(异常)

程序本身可以捕获并且可以处理的异常。Exception 这种异常又分为两类:运行时异常和编译时异常。
运行时异常:这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。如NullPointerException(空指针异常)、IndexOutO fBoundsException(下标越界异常)等,
运行时异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过。
非运行时异常 (编译异常):属于Exception类及其子类。,是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。
在这里插入图片描述

八、IO流

Java I/O 使用了装饰者模式来实现
1.阻塞IO和非阻塞IO
这两个概念是程序级别的。主要描述的是程序请求操作系统IO操作后,如果IO资源没有准备好,阻塞IO会一直等待;非阻塞IO会继续执行,并且使用线程一直轮询,直到有IO资源准备好了。
2.同步IO和非同步IO
这两个概念是操作系统级别的。主要描述的是操作系统在收到程序请求IO操作后,如果IO资源没有准备好,同步IO不响应,直到IO资源准备好以后再响应;非同步IO会返回一个标记(好让程序和自己知道以后的数据往哪里通知),当IO资源准备好以后,再用事件机制返回给程序。
3.同步、异步和阻塞、非阻塞
在一个网络请求中,客户端会发一个请求到服务端。
同步、异步针对请求;阻塞、非阻塞针对客户端
1)客户端发了请求后,就一直等着服务端响应。客户端:阻塞。请求:同步
2)客户端发了请求后,就去干别的事情了。时不时的过来检查服务端是否给出了相应。客户端:非阻塞。请求:同步。
3)换成异步请求。客户端发了请求后,就坐在椅子上,等着服务端返回响应。客户端:阻塞。请求:异步
4)客户端发了请求后,就去干别的事情了。等到服务端给出响应后,再过来处理业务逻辑。客户端;非阻塞。请求:异步。
4. Java 中 IO 流分为几种?
按功能来分:输入流(input)、输出流(output)。
按类型来分:字节流和字符流。
字节流和字符流的区别是:字节流按 8 位传输以字节为单位输入输出数据,字符流按 16 位传输以字符为单位输入输出数据。
5.JAVA有哪几种IO模型?有什么区别?
1)BIO(同步阻塞IO):可靠性差,吞吐量低,适用于连接比较少且比较固定的场景。JDK1.4之前唯一的选择。编程模型最简单。
应用进程被阻塞,直到数据从内核缓冲区复制到应用进程缓冲区中才返回。应该注意到,在阻塞的过程中,其它应用进程还可以执行,因此阻塞不意味着整个操作系统都被阻塞。因为其它应用进程还可以执行,所以不消耗 CPU 时间,这种模型的 CPU 利用率会比较高。
写数据的时候会先检查输出缓冲区的空间,如果小于你要发送的数据大小,那么这次调用会被阻塞,直到缓冲区腾出空间,才能获取执行权写入。
如果tcp/ip正在读取输出缓冲区,这里有锁,此时socket需要写数据,也会阻塞,等释放了锁,才能往里写。
如果写的数据非常大,但是输出缓冲区上限比你要发的小,也会阻塞,分两阶段解决,先写一部分数据,剩余的挂起,等tcp/ip发送完,再发剩下的。
如果空间满,会等待。
读操作用户态(还有个buffer接受缓冲区,一次读多少个字节)调用read(),先检查输入缓冲区是否有数据,有就读,否则阻塞调用进程,直到输入缓冲区中有数据,才会中断唤醒。
如果buffer小于输入缓冲区的长度,一次读不完,多读几次
在这里插入图片描述
2)NIO(同步非阻塞IO):可靠性比较好,吞吐量也比较高,适用于连接比较多并且连接比较短(轻操作),例如聊天室,JDK1.4开始支持。编程模型最复杂。
应用进程执行系统调用之后,如果内核中的数据还没有准备好,那么它并不会阻塞用户进程,而是立刻返回一个错误。应用进程可以继续执行,但是需要不断的执行系统调用来获知I/O 是否完成,这种方式称为轮询(polling),并且当内核数据准备好后,拷贝数据到用户内存会是阻塞的。由于 CPU 要处理更多的系统调用,因此这种模型的 CPU 利用率比较低。
非阻塞模式下,写数据,写操作会先检查输出缓冲区够不够,不够,尽可能拷贝多的数据,返回到用户态告诉你,写了多少数据,下次你跟据这个,重写。直到发完。如果一点空间都没了,立马返回给上层一个-1,告诉上层写失败,缓冲区满了。
读数据,有数据就读,没数据就返回没数据,读结束。
在这里插入图片描述
**3)AIO(异步非阻塞IO):**可靠性是最好的,吞吐量也是非常高。适用于连接比较多,并且连接比较长(重操作)。例如相册服务器。JDK7版本才支持的。编程模型比较简单,需要操作系统的支持。
应用进程执行 aio_read 系统调用会立即返回,应用进程可以继续执行,不会被阻塞,内核会在所有操作完成之后向应用进程发送信号,没有任何阻塞过程。
异步 I/O 与I/O 复用的区别在于,异步I/O不阻塞进程,而I/O复用会阻塞在调用函数阶段。异步 I/O 的信号是通知应用进程 I/O 完成,而I/O 复用是通知应用进程可以开始 I/O。
并且I/O复用也是要轮询查看数据是否就绪,而异步I/O则不需要。
AIO与NIO的区别在于:NIO虽然进程大部分时间都不会被阻塞,但是它仍然要求进程去主动的检查,并且当数据准备完成以后,也需要进程主动的再次调用recvfrom来将数据拷贝到用户内存。而AIO则完全不同。它就像是用户进程将整个IO操作交给了内核完成,然后完成之后发信号通知。在此期间,用户进程不需要去检查IO操作的状态,也不需要主动的去拷贝数据。
在这里插入图片描述
6.Java NIO的几个核心组件是什么,分别有什么作用?
在这里插入图片描述
核心组件:channel、buffer和selector
客户端发出请求,然后写入到buffer缓冲区,每个客户端都对应一个buffer缓冲区,每个channel对应一个buffer缓冲区。buffer和channel都是可读可写的。接着channel会请求注册到selector。select会根据channel上发生的读写事件,将请求交由某个空闲的线程处理。selector对应一个或者多个线程。
7.select、poll和epoll有什么区别?
他们是NIO中多路复用(多个客户端都可以同时过来请求)的三种实现机制,是由Linux操作系统提供的。这三种机制在底层实际上是一个C++的API。
用户空间和内核空间:操作系统为了不让应用程序随意破坏底层基层,保护系统安全,将内核划分为两个部分,一个是用户空间,一个是内核空间。用户空间用于运行应用程序,不能直接访问底层的硬件设备,所有与硬件设备的交互必须通过内核空间。那么用户空间如果有数据需要和硬件设备进行交互,就需要把这些数据从用户空间复制到内核空间,再由内核空间将这些数据写入到硬件设备。内核空间是为操作系统划分的空间,用于系统的运行
文件描述符File Descriptor(FD):是一个抽象的概念,形式上是一个整数,实际上是一个索引值,指向内核中为每个进程维护进程所打开的文件的记录表。当程序打开一个文件或者创建一个文件时,内核就会向进程返回一个文件描述符。Unix,Linux操作系统才拥有。
select机制:select会维护一个文件描述符的集合FD_set。Select会扫描(遍历)所有的文件描述符,将fd_set从用户空间复制到内核空间,激活socket连接,完成IO操作。fd set是一个数组结构。
存在的问题:如果连接很多,FD_set也会很大,开销也会很大,FD_set会受到操作系统的大小限制(x64上大小为2048),复制会造成性能损失。
Poll机制:和selecter机制是类似的,把fd_set结构进行了优化,FD集合的大小就没有了操作系统的限制。Pollfd结构来代替fd_set,通过链表实现的。
EPoll:Event Poll是事件驱动的。Epoll不再扫描所有的FD,与内核交互只将用户关心的FD的事件存放到内核的一个事件表当中。这样,可以减少用户空间与内核空间之前需要拷贝的数据。
在这里插入图片描述
8.Java的NIO当中是用的那种机制?
可以查看DefaultSelectorProvider源码。在windows下,WindowsSelectorProvider。而Linux下,根据Linux的内核版本,2.6版本以上,就是EPollSelectorProvider,否则就是默认的PollSelectorProvider。


总结

本文所有的Java面试资料都是小编整理和总结的,有些地方可能过于琐碎和复杂。如果有什么错误的地方,请在评论区指出,欢迎各位大佬批评指正。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值