Java 基础面试题(超全面 2020年总结)

本 面试题, 正在整理中,v1.0版本,将会在2020.2.29日之前完成.近期会持续更新

文章目录

一、Java基础

1、 什么是 Java 虚拟机(JVM)?为什么 Java 被称作是“平台无关的编程语言”?

  • Java 虚拟机是一个可以执行 Java 字节码的虚拟机进程。Java 源文件被编译成能被 Java 虚拟机执行的字节码文件。
  • Java 被设计成允许应用程序可以运行在任意的平台,而不需要程序员为每一个平台单独重写或者是重新编译。Java 虚拟机让这个变为可能,因为它知道底层硬件平台的指令长度和其他特性。

2、JDK、JRE、JVM 分别是什么关系?

  • JDK:Java Development Kit 的简称,java 开发工具包,提供了 java 的开发环境和运行环境。
  • JRE:Java Runtime Environment 的简称,java 运行环境,为 java 的运行提供了所需环境。
  • JVM:Java虚拟机(JVM)是运行 Java 字节码的虚拟机。JVM有针对不同系统的特定实现(Windows,Linux,macOS),目的是使用相同的字节码,它们都会给出相同的结果。

具体来说 JDK 其实包含了 JRE,同时还包含了编译 java 源码的编译器 javac,还包含了很多 java 程序调试和分析的工具。简单来说:如果你需要运行 java 程序,只需安装 JRE 就可以了,如果你需要编写 java 程序,需要安装 JDK。

2.1 [扩展]什么是字节码?采用字节码的好处是什么?(了解)

什么是字节码?采用字节码的好处是什么?
  在 Java 中,JVM可以理解的代码就叫做字节码(即扩展名为 .class 的文件),它不面向任何特定的处理器,只面向虚拟机。Java 语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以 Java 程序运行时比较高效,而且,由于字节码并不针对一种特定的机器,因此,Java程序无须重新编译便可在多种不同操作系统的计算机上运行。
Java 程序从源代码到运行一般有下面3步:
在这里插入图片描述
  我们需要格外注意的是 .class->机器码 这一步。在这一步 JVM 类加载器首先加载字节码文件,然后通过解释器逐行解释执行,这种方式的执行速度会相对比较慢。而且,有些方法和代码块是经常需要被调用的(也就是所谓的热点代码),所以后面引进了 JIT 编译器,而JIT 属于运行时编译。当 JIT 编译器完成第一次编译后,其会将字节码对应的机器码保存下来,下次可以直接使用。而我们知道,机器码的运行效率肯定是高于 Java 解释器的。这也解释了我们为什么经常会说 Java 是编译与解释共存的语言。

3、Java 支持的数据类型有哪些?

Java 支持的数据类型包括基本数据类型和引用类型。基本数据类型如下。

  • 整数值型:byte,short,int,long
  • 字符型:char
  • 浮点类型:float,double
  • 布尔型:boolean

注意:

  1. String 是引用类型不是基本类型,引用类型声明的变量是指该变量在内存中实际存储的是一个引用地址,实体在堆中。
  2. 引用类型包括类、接口、数组等。String 类还是 final 修饰的。
  3. 整数默认 int 型,小数默认是 double 型。Float 和 long 类型的必须加后缀。比如 float f = 100f。

4、什么是自动拆装箱?

自动装箱和拆箱就是基本类型和引用类型之间的转换,至于为什么要转换,因为基本类型转换为引用类型后,就可以 new 对象,从而调用包装类中封装好的方法进行基本类型之间的转换或者 toString(当然用类名直接调用也可以,便于一眼看出该方法是静态的),还有就是如果集合中想存放基本类型,泛型的限定类型只能是对应的包装类型。

5、什么是面向对象?

面向对象是一种思想,世间万物都可以看做一个对象,这里只讨论面向对象编程(OOP),Java 是一个支持并发、基于类和面向对象的计算机编程语言,面向对象软件开发具有以下
优点

  • 代码开发模块化,更易维护和修改;
  • 代码复用性强;
  • 增强代码的可靠性和灵活性;
  • 增加代码的可读性。

6、Java 面向对象编程基本特性?

  1. 封装
    封装就是把抽象的数据和对数据进行的操作封装在一起,数据被保存在内部,程序的其他部分只有通过被授权的操作(成员方法)才能对数据进行操作。

java提供了四种控制修饰符控制方法和变量访问的权限:

  1. public:对外公开
  2. protected:对子类和同一包中的类公开
  3. 没有修饰符号:向同一个包的类公开
  4. private:只有类本身可以访问,不对外公开
  1. 继承(extends )
    继承是使用已存在的类的定义作为基础建立新类的技术。继承可以解决代码复用问题,当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过extend语句来声明继承父类。

关于继承如下 3 点请记住:

  1. 子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问,只是拥有。
  2. 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
  3. 子类可以用自己的方式实现父类的方法。
  1. 多态
    所谓多态,就是指一个引用(类型)在不同情况下的多种状态,你也可以这样理解:父类型的引用指向子类型的对象。

多态有两个好处:

  1. 应用程序不必为每一个派生类编写功能调用,只需要对抽象基类进行处理即可。大大提高程序的可复用性。//继承
  2. 派生类的功能可以被基类的方法或引用变量所调用,这叫向后兼容,可以提高可扩充性和可维护性。 //多态的真正作用,

7、& 与 && 的区别?

& 运算符有两种用法:(1) 按位,(2) 逻辑
&& 运算符是短路运算。

  • 逻辑跟短路的差别是非常巨大的,虽然二者都要求运算符左右两端的布尔值都是 true 整个表达式的值才是 true
  • && 之所以称为短路运算是因为,如果 && 左边的表达式的值是 false,右边的表达式会被直接短路掉,不会进行运算。

很多时候我们可能都需要用 && 而不是 &,例如在验证用户登录时判定用户名不是 null 而且不是空字符串,应当写为:username != null && !username.equals(“”)
二者的顺序不能交换,更不能用 & 运算符,因为第一个条件如果不成立,根本不能进行字符串的 equals 比较,否则会产生 NullPointerException 异常。

注意:逻辑或运算符(|)和短路或运算符(||)的差别也是如此。

在这里插入图片描述

8、== 与 equals的区别?(重要)

  1. ==
    对于基本类型和引用类型 == 的作用效果是不同的,如下所示:
    基本类型:比较的是值是否相同;
    引用类型:比较的是引用(对象的地址)是否相同;
    代码示例:
        String x = "Dreamhai";
        String y = "Dreamhai";
        String z = new String("Dreamhai");

        System.out.println("x == y->"+(x == y));
        System.out.println("x == z->"+(x == z));

        System.out.println("x.equals(z)->"+x.equals(z));
        System.out.println("y.equals(z)->"+y.equals(z));

运行结果:
x == y->true
x == z->false
x.equals(z)->true
y.equals(z)->true

代码解读:因为 x 和 y 指向的是同一个引用,所以 == 也是 true,而 new String()方法则重写开辟了内存空间,所以 == 结果为 false,而 equals 比较的一直是值,所以结果都为 true。

  1. equals
    equals方法不能作用于基本数据类型的变量,只能用于类变量。(对于基本数据类型要用其包装类)
    如果没有对equals方法进行重写,则比较的是引用类型的变量所指向的对象的地址;
    诸如String、Date等类对equals方法进行了重写的话,比较的是所指向的对象的内容。

【注】Object类中的equals方法和“==”是一样的,没有区别,而String类,Integer类等等一些类,是重写了equals方法,才使得equals和“==不同”,所以,当自己创建类时,自动继承了Object的equals方法,要想实现不同的等于比较,必须重写equals方法。

Object类中的equals方法

public boolean equals(Object obj) {
    return (this == obj);
}

String类中的equals方法

public boolean equals(Object anObject) {
     if (this == anObject) {
         return true;
     }
     if (anObject instanceof String) {
         String anotherString = (String)anObject;
         int n = value.length;
         if (n == anotherString.value.length) {
             char v1[] = value;
             char v2[] = anotherString.value;
             int i = 0;
             while (n-- != 0) {
                 if (v1[i] != v2[i])
                     return false;
                 i++;
             }
             return true;
         }
     }
     return false;
 }

Integer 类中的equals中的方法

    public boolean equals(Object obj) {
        if (obj instanceof Integer) {
            return value == ((Integer)obj).intValue();
        }
        return false;
    }

9、为什么重写equals时必须重写hashCode方法? (重要)

当对象的equals()方法被重写时,通常有必要重写 hashCode() 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。
(1)两个对象相等,hashcode一定相等
(2)两个对象不等,hashcode不一定不等
(3)hashcode相等,两个对象不一定相等
(4)hashcode不等,两个对象一定不等
hashcode是用于散列数据的快速存取,如利用HashSet/HashMap/Hashtable类来存储数据时,都是根据存储对象的hashcode值来进行判断是否相同的。这样如果我们对一个对象重写了euqals,意思是只要对象的成员变量值都相等那么euqals就等于true,但不重写hashcode,那么我们再new一个新的对象,当原对象.equals(新对象)等于true时,两者的hashcode却是不一样的,由此将产生了理解的不一致。

参考链接: https://www.cnblogs.com/wang-meng/p/7501378.html

HashMap中的get与put:

  • put:
  1. 首先根据put元素的key获取hashcode,然后根据hashcode算出数组的下标位置,如果下标位置没有元素,直接放入元素即可。
  2. 如果该下标位置有元素,则需要已有元素和put元素的key对象比较equals方法,如果equals不一样,则说明可以放入进map中,会在该数组位置创建一个链表,后put进入的元素到放链表头,原来的元素向后移动。
  • get:
  1. 根据元素的key获取hashcode,然后根据hashcode获取数组下标位置,如果只有一个元素则直接取出。如果该位置是一个链表,则需要调用equals方法遍历链表中的所有元素与当前的元素比较,得到真正想要的对象。(当两个对象的hashcode不同的话,肯定他们不能equals。)

10、Java 中 finalfinallyfinalize 的区别?

  1. final 是一个修饰符,
    a. 如果一个类被声明为 final 则其不能再派生出新的子类,所以一个类不能既被声明为 abstract 又被声明为 final [所谓不可同时出现]的;
    b. 将变量或方法声明为 final 可以保证它们在使用中不被改变(对于对象变量来说其引用不可变,即不能再指向其他的对象,但是对象的值可变)。

注:

  1. 被声明为 final 的变量必须在声明时给定初值,而在以后的引用中只能读取不可修改,被声明为 final 的方法也同样只能使用不能重载。
  2. 使用 final 关键字如果编译器能够在编译阶段确定某变量的值则编译器就会把该变量当做编译期常量来使用,如果需要在运行时确定(譬如方法调用)则编译器就不会优化相关代码;将类、方法、变量声明为 final 能够提高性能,这样 JVM 就有机会进行估计并进行优化;接口中的变量都是 public static final 的。

final关键字主要用在三个地方:变量、方法、类。

  1. 对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。
  2. 当用final修饰一个时,表明这个类不能被继承。final类中的所有成员方法都会被隐式地指定为final方法。
  3. 使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升(现在的Java版本已经不需要使用final方法进行这些优化了)。类中所有的private方法都隐式地指定为final
  1. finally用来在异常处理中,如果抛出一个异常,则相匹配的 catch 子句就会执行,然后控制就会进入 finally
      finally是对Java异常处理模型的最佳补充。finally结构使代码总会执行,而不管无异常发生。使用finally可以维护对象的内部状态,并可以清理非内存资源。特别是在关闭数据库连接这方面,如果程序员把数据库连接的close()方法放到finally中,就会大大降低程序出错的几率。

异常处理:

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

在以下4种特殊情况下,finally块不会被执行:

  1. 在finally语句块第一行发生了异常。 因为在其他行,finally块还是会得到执行
  2. 在前面的代码中用了System.exit(int)已退出程序。 exit是带参函数 ;若该语句在异常语句之后,finally会执行
  3. 程序所在的线程死亡。
    关闭CPU。
  1. finalize():是一个方法,它是在对象被垃圾回收之前由Java虚拟机来调用的。
      finalize()方法是GC运行机制的一部分,finalize()方法是在GC清理它所从属的对象时被调用的,如果执行它的过程中抛出了无法捕获的异常,GC将终止对改对象的清理,并且该异常会被忽略;直到下一次GC开始清理这个对象时,它的finalize()会被再次调用。

11、请你说一下this、super的区别

  1. this:代表对象本身,可以理解为:指向对象本身的一个指针。
    this的用法在java中大体可以分为3种:
    a. 普通的直接引用
      这种就不用讲了,this相当于是指向当前对象本身:
    b. 形参与成员名字重名,用this来区分:
    形参与成员名字重名
    c. 引用构造函数
      this(参数):调用本类中另一种形式的构造方法(应该为构造方法中的第一条语句)
     引用构造函数
  2. super:代指父类,可以用于调用父类的普通方法和构造方法。
    调用父类构造方法:super()(无参构造方法)或 super(参数)(有参构造方法)
    调用父类普通方法:super.方法名(参数)
    this和super的区别

12、什么是值传递和引用传递?

  1. 值传递是对基本型变量而言的,传递的是该变量的一个副本,改变副本不影响原变量。
  2. 引用传递一般是对于对象型变量而言的,传递的是该对象地址的一个副本,并不是原对象本身。

一般认为,Java 内的传递都是值传递,Java 中实例对象的传递是引用传递。

13、是否可以在 static 环境中访问非 static 变量?

不可以.
因为 static 变量在 Java 中是属于类的。当类被 Java 虚拟机载入的时候,会对 static 变量进行初始化。如果你的代码尝试不用实例来访问非 static 的变量,编译器会报错,因为这些变量还没有被创建出来,还没有跟任何实例关联上。

14、Java 中的方法覆盖(Overriding)和方法重载(Overloading)是什么意思?

方法的重载重写都是实现多态的方式,区别在于重载实现的是编译时的多态性,而重写实现的是运行时的多态性。
重载发生在一个类中,同名的方法如果有不同的参数列表(类型不同、个数不同、顺序不同)则视为重载。
重写发生在子类与父类之间,重写要求子类重写之后的方法与父类被重写方法有相同的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。重载对返回类型没有特殊的要求。
方法重载的规则:

  1. 方法名一致,参数列表中参数的顺序,类型,个数不同。
  2. 重载与方法的返回值无关,存在于父类和子类,同类中。
  3. 可以抛出不同的异常,可以有不同修饰符。

方法重写的规则:

  1. 参数列表、方法名、返回值类型必须完全一致;
  2. 构造方法不能被重写;
  3. 声明为 final 的方法不能被重写;
  4. 声明为 static 的方法不存在重写(重写和多态联合才有意义);
  5. 访问权限不能比父类更低;
  6. 重写之后的方法不能抛出更宽泛的异常;

15、Java 支持多继承么?

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

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

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

Java 中构造方法重载和方法重载很相似。可以为一个类创建多个构造方法。每一个构造方法必须有它自己唯一的参数列表。

Java 不支持像 C++ 中那样的复制构造方法,这个不同点是因为如果你不自己写构造方法的情况下,Java 不会创建默认的复制构造方法。

17、接口和抽象类的区别是什么?

18、从设计层面来说,抽象是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。

Java 提供和支持创建抽象类和接口。它们的实现有共同点,不同点在于: 接口中所有的方法隐含的都是抽象的,而抽象类则可以同时包含抽象和非抽象的方法。

类可以实现很多个接口,但是只能继承一个抽象类。类可以不实现抽象类和接口声明的所有方法,当然,在这种情况下,类也必须得声明成是抽象的。

抽象类可以在不提供接口方法实现的情况下实现接口。 Java 接口中声明的变量默认都是 final 的。抽象类可以包含非 final 的变量。Java 接口中的成员函数默认是 public 的。抽象类的成员函数可以是 private,protected 或者是 public。

接口是绝对抽象的,不可以被实例化。抽象类也不可以被实例化,但是,如果它包含 main 方法的话是可以被调用的。也可以参考 JDK8 中抽象类和接口的区别。

19、用最有效率的方法计算 2 乘以 8?

2 << 3(左移3位相当于乘以2的3次方,右移3位相当于除以2的3次方)。

20、手写单例模式(饿汉和懒汉模式)和工厂模式?

21、String和StringBuilder、StringBuffer的区别?

22、java 中的 Math.round(-1.5) 等于多少?

23、String str="i"与 String str=new String(“i”)一样吗?

24、String 类的常用方法都有那些?

25、抽象类必须要有抽象方法吗?

26、普通类和抽象类有哪些区别?

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

28、BIO、NIO、AIO 有什么区别?

29、String类可以被继承吗?

String类在声明时使用final关键字修饰,被final关键字修饰的类无法被继承。
看一下String类的源代码片段:

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

    /** Cache the hash code for the string */
    private int hash; // Default to 0

    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    private static final long serialVersionUID = -6849794470754667710L;
... 此处省去了若干行代码...
}

30、在 Java 中,如何跳出当前的多重嵌套循环?(了解)

在最外层循环前加一个标记如outfor,然后用break outfor;可以跳出多重循环。例如以下代码:

public class TestBreak {
    public static void main(String[] args) {
        outfor: for (int i = 0; i < 10; i++){
            for (int j = 0; j < 10; j++){
                if (j == 5){
                    break outfor;
                }
                System.out.println("j = " + j);
            }
        }
    }
}

运行结果如下所示:
j = 0
j = 1
j = 2
j = 3
j = 4

31、当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里是值传递还是引用传递?

是值传递。Java 语言的方法调用只支持参数的值传递。当一个对象实例作为一个参数被传递到方法中时,参数的值就是对该对象的内存地址。这个值(内存地址)被传递后,同一个内存地址指向堆内存当中的同一个对象,所以通过哪个引用去操作这个对象,对象的属性都是改变的。

32、char 型变量中能不能存储一个中文汉字,为什么?

char类型可以存储一个中文汉字,因为Java中使用的编码是Unicode(不选择任何特定的编码,直接使用字符在字符集中的编号,这是统一的唯一方法),一个char 类型占2个字节(16 比特),所以放一个中文是没问题的。

33、

34、

35、

36、

37、

38、

39、

40、

二、Java 容器

关于集合的详解,请参考 >>请点击<<

1、说出一些集合框架的优点?

集合框架的部分优点如下:

  1. 使用核心集合类降低开发成本,而非实现我们自己的集合类。
  2. 随着使用经过严格测试的集合框架类,代码质量会得到提高。
  3. 通过使用JDK附带的集合类,可以降低代码维护成本。
  4. 复用性和可操作性。

2、集合框架中的泛型有什么优点?

  1. Java1.5引入了泛型,所有的集合接口和实现都大量地使用它。
  2. 泛型允许我们为集合提供一个可以容纳的对象类型,因此,如果你添加其它类型的任何元素,它会在编译时报错。
  3. 这避免了在运行时出现ClassCastException,因为你将会在编译时得到报错信息。
  4. 泛型也使得代码整洁,我们不需要使用显式转换和instanceOf操作符。
  5. 它也给运行时带来好处,因为不会产生类型检查的字节码指令。

3、Java集合框架的基础接口有哪些?

  1. Collection为集合层级的根接口。一个集合代表一组对象,这些对象即为它的元素。Java平台不提供这个接口任何直接的实现。
  2. Set是一个不能包含重复元素的集合。
  3. List是一个有序集合,可以包含重复元素。你可以通过它的索引来访问任何元素。List更像长度动态变换的数组。
  4. Map是一个将key映射到value的对象.一个Map不能包含重复的key:每个key最多只能映射一个value。

一些其它的接口有Queue、Dequeue、SortedSet、SortedMap和ListIterator。

4、Iterator是什么?

Iterator接口提供遍历任何Collection的接口。我们可以从一个Collection中使用迭代器方法来获取迭代器实例。迭代器取代了Java集合框架中的Enumeration。迭代器允许调用者在迭代过程中移除元素。

5、Iterater和ListIterator之间有什么区别?

  1. 我们可以使用Iterator来遍历Set和List集合,而ListIterator只能遍历List。
  2. Iterator只可以向前遍历,而LIstIterator可以双向遍历。
  3. ListIterator从Iterator接口继承,然后添加了一些额外的功能,比如添加一个元素、替换一个元素、获取前面或后面元素的索引位置。

6、fail-fast与fail-safe有什么区别?

Iterator的fail-fast属性与当前的集合共同起作用,因此它不会受到集合中任何改动的影响。Java.util包中的所有集合类都被设计为fail-fast的,
而java.util.concurrent中的集合类都为fail-safe的。

Fall—fast迭代器抛出ConcurrentModificationException,
fall—safe迭代器从不抛出ConcurrentModificationException。

7、HashMap和HashTable有何不同?

  1. HashMap允许key和value为null,而HashTable不允许,如果添加null ,回报NullPointerException
  2. HashTable是同步的,而HashMap不是。所以HashMap适合单线程环境,HashTable适合多线程环境。
  3. 在Java1.4中引入了LinkedHashMap,HashMap的一个子类,假如你想要遍历顺序,你很容易从HashMap转向LinkedHashMap,但是HashTable不是这样的,它的顺序是不可预知的。
  4. HashMap提供对key的Set进行遍历,因此它是fail-fast的,但HashTable提供对key的Enumeration进行遍历,它不支持fail-fast。
  5. HashTable被认为是个遗留的类,如果你寻求在迭代的时候修改Map,你应该使用CocurrentHashMap。

8、如何决定选用HashMap还是TreeMap?

对于在Map中插入、删除和定位元素这类操作,HashMap是***的选择。然而,假如你需要对一个有序的key集合进行遍历,TreeMap是更好的选择。基于你的collection的大小,也许向HashMap中添加元素会更快,将map换为TreeMap进行有序key的遍历。

9、ArrayList和Vector有何异同点?

ArrayList和Vector在很多时候都很类似。

  1. 两者都是基于索引的,内部由一个数组支持。
  2. 两者维护插入的顺序,我们可以根据插入顺序来获取元素。
  3. ArrayList和Vector的迭代器实现都是fail-fast的。
  4. ArrayList和Vector两者允许null值,也可以使用索引值对元素进行随机访问。

以下是ArrayList和Vector的不同点。

  1. Vector是同步的,而ArrayList不是。然而,如果你寻求在迭代的时候对列表进行改变,你应该使用CopyOnWriteArrayList。
  2. ArrayList比Vector快,它因为有同步,不会过载。
  3. ArrayList更加通用,因为我们可以使用Collections工具类轻易地获取同步列表和只读列表。

10、Array和ArrayList有何区别?什么时候更适合用Array?

Array和ArrayList有何区别:

  1. Array可以容纳基本类型和对象,而ArrayList只能容纳对象。
  2. Array是指定大小的,而ArrayList大小是固定的。
  3. Array没有提供ArrayList那么多功能,比如addAll、removeAll和iterator等。尽管ArrayList明显是更好的选择,但也有些时候Array比较好用。

适用:

  1. 如果列表的大小已经指定,大部分情况下是存储和遍历它们。
  2. 对于遍历基本数据类型,尽管Collections使用自动装箱来减轻编码任务,在指定大小的基本类型的列表上工作也会变得很慢。
  3. 如果你要使用多维数组,使用[][]比List<List<>>更容易。

11、ArrayList和LinkedList有何区别?

  • 1. 是否保证线程安全: ArrayListLinkedList 都是不同步的,也就是不保证线程安全;

  • 2. 底层数据结构: Arraylist 底层使用的是 Object 数组LinkedList 底层使用的是 双向链表 数据结构(JDK1.6之前为循环链表,JDK1.7取消了循环。注意双向链表和双向循环链表的区别)

  • 3. 插入和删除是否受元素位置的影响:ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。 比如:执行add(E e)方法的时候, ArrayList 会默认在将指定的元素追加到此列表的末尾,这种情况时间复杂度就是O(1)。但是如果要在指定位置 i 插入和删除元素的话(add(int index, E element))时间复杂度就为 O(n-i)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位/向前移一位的操作。 ② LinkedList 采用链表存储,所以对于add(E e)方法的插入,删除元素时间复杂度不受元素位置的影响,近似 O(1),如果是要在指定位置i插入和删除元素的话((add(int index, E element)) 时间复杂度近似为o(n))因为需要先移动到指定位置再插入。

  • 4. 是否支持快速随机访问: LinkedList 不支持高效的随机元素访问,而 ArrayList 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于get(int index)方法)。

  • 5. 内存空间占用: ArrayList的空 间浪费主要体现在在list列表的结尾会预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗比ArrayList更多的空间(因为要存放直接后继和直接前驱以及数据)。

12、哪些集合类提供对元素的随机访问?

ArrayList、HashMap、TreeMap和HashTable类提供对元素的随机访问。

13、Collections类是什么?

Java.util.Collections是一个工具类仅包含静态方法,它们操作或返回集合。

它包含操作集合的多态算法,返回一个由指定集合支持的新集合和其它一些内容。这个类包含集合框架算法的方法,比如折半搜索、排序、混编和逆序等。

14、Comparable和Comparator接口有何区别?

Comparable和Comparator接口被用来对对象集合或者数组进行排序。Comparable接口被用来提供对象的自然排序,我们可以使用它来提供基于单个逻辑的排序。

Comparator接口被用来提供不同的排序算法,我们可以选择需要使用的Comparator来对给定的对象集合进行排序。

15、HashMap 和 HashSet区别

如果你看过 HashSet 源码的话就应该知道:HashSet 底层就是基于 HashMap 实现的。(HashSet 的源码非常非常少,因为除了 clone()writeObject()readObject()是 HashSet 自己不得不实现之外,其他方法都是直接调用 HashMap 中的方法。

HashMap HashSet
实现了Map接口 实现Set接口
存储键值对 仅存储对象
调用 put()向map中添加元素 调用 add()方法向Set中添加元素
HashMap使用键(Key)计算Hashcode HashSet使用成员对象来计算hashcode值,对于两个对象来说hashcode可能相同,所以equals()方法用来判断对象的相等性,

三、IO模型

1、什么是线程?

线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。
程序员可以通过它进行多处理器编程,你可以使用多线程对 运算密集型任务提速。比如,如果一个线程完成一个任务要100毫秒,那么用十个线程完成改任务只需10毫秒。Java在语言层面对多线程提供了卓越的支 持,它也是一个很好的卖点。

2、线程和进程有什么区别?

线程是进程的子集,一个进程可以有很多线程,每条线程并行执行不同的任务。不同的进程使用不同的内存空间,而所有的线程共享一片相同的内存空间。别把它和栈内存搞混,每个线程都拥有单独的栈内存用来存储本地数据。

3、在Java中实现现场有哪些方式呢?

  • 继承Thread类创建线程类。
  • 通过Runnable接口类创建线程类。
  • 通过Callable和Future创建线程。

采用实现Runnable、Callable接口的方式创见多线程时,优势是:
线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。
在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
劣势是:
编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。
使用继承Thread类的方式创建多线程时优势是:
编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。
劣势是:
线程类已经继承了Thread类,所以不能再继承其他父类。

4、Thread 类中的start() 和 run() 方法有什么区别?

这个问题经常被问到,但还是能从此区分出面试者对Java线程模型的理解程度。

start()方法被用来启动新创建的线程,而且start()内部 调用了run()方法,这和直接调用run()方法的效果不一样。当你调用run()方法的时候,只会是在原来的线程中调用,没有新的线程启 动,start()方法才会启动新线程。

5、Java中Runnable和Callable有什么不同?

  1. Runnable提供run方法,不会抛出异常,只能在run方法内部处理异常。Callable提供call方法,直接抛出Exception异常,也就是你不会因为call方法内部出现检查型异常而不知所措,完全可以抛出即可。
  2. Runnable的run方法无返回值,Callable的call方法提供返回值用来表示任务运行的结果
  3. Runnable可以作为Thread构造器的参数,通过开启新的线程来执行,也可以通过线程池来执行。而Callable只能通过线程池执行。

6、什么是线程安全?Vector是一个线程安全类吗?

如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量 的值也和预期的是一样的,就是线程安全的。一个线程安全的计数器类的同一个实例对象在被多个线程使用的情况下也不会出现计算失误。很显然你可以将集合类分 成两组,线程安全和非线程安全的。Vector 是用同步方法来实现线程安全的, 而和它相似的ArrayList不是线程安全的。

7、Java中如何停止一个线程?

Java提供了很丰富的API但没有为停止线程提供API。JDK 1.0本来有一些像stop(), suspend() 和 resume()的控制方法但是由于潜在的死锁威胁因此在后续的JDK版本中他们被弃用了,之后Java API的设计者就没有提供一个兼容且线程安全的方法来停止一个线程。当run() 或者 call() 方法执行完的时候线程会自动结束,如果要手动结束一个线程,你可以用volatile 布尔变量来退出run()方法的循环或者是取消任务来中断线程。

8、 一个线程运行时发生异常会怎样?

这是我在一次面试中遇到的一个很刁钻的Java面试题, 简单的说,如果异常没有被捕获该线程将会停止执行。Thread.UncaughtExceptionHandler是用于处理未捕获异常造成线程突然中 断情况的一个内嵌接口。当一个未捕获异常将造成线程中断的时候JVM会使用Thread.getUncaughtExceptionHandler()来 查询线程的UncaughtExceptionHandler并将线程和异常作为参数传递给handler的uncaughtException()方法 进行处理。

9、如何在两个线程间共享数据?

你可以通过共享对象来实现这个目的,或者是使用像阻塞队列这样并发的数据结构。这篇教程《Java线程间通信》(涉及到在两个线程间共享对象)用wait和notify方法实现了生产者消费者模型。

10、Java中notify 和 notifyAll有什么区别?

这又是一个刁钻的问题,因为多线程可以等待单监控锁,Java API 的设计人员提供了一些方法当等待条件改变的时候通知它们,但是这些方法没有完全实现。notify()方法不能唤醒某个具体的线程,所以只有一个线程在等 待的时候它才有用武之地。而notifyAll()唤醒所有线程并允许他们争夺锁确保了至少有一个线程能继续运行。

11、为什么wait, notify 和 notifyAll这些方法不在thread类里面?

这是个设计相关的问题,它考察的是面试者对现有系统和一些普遍存在但看起来不合理的事物的看法。回答这些问题的时候,你要说明为什么把这些方法放在 Object类里是有意义的,还有不把它放在Thread类里的原因。一个很明显的原因是JAVA提供的锁是对象级的而不是线程级的,每个对象都有锁,通 过线程获得。如果线程需要等待某些锁那么调用对象中的wait()方法就有意义了。如果wait()方法定义在Thread类中,线程正在等待的是哪个锁 就不明显了。简单的说,由于wait,notify和notifyAll都是锁级别的操作,所以把他们定义在Object类中因为锁属于对象。

12、Java中interrupted 和 isInterruptedd方法的区别?

interrupted() 和 isInterrupted()的主要区别是前者会将中断状态清除而后者不会。Java多线程的中断机制是用内部标识来实现的,调用Thread.interrupt()来中断一个线程就会设置中断标识为true。当中断线程调用静态方法Thread.interrupted()来 检查中断状态时,中断状态会被清零。而非静态方法isInterrupted()用来查询其它线程的中断状态且不会改变中断状态标识。简单的说就是任何抛 出InterruptedException异常的方法都会将中断状态清零。无论如何,一个线程的中断状态有有可能被其它线程调用中断来改变。

13、如何避免死锁?

Java多线程中的死锁
死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。这是一个严重的问题,因为死锁会让你的程序挂起无法完成任务,死锁的发生必须满足以下四个条件:

  • 互斥条件:一个资源每次只能被一个进程使用。
  • 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
  • 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
  • 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

避免死锁最简单的方法就是阻止循环等待条件,将系统中所有的资源设置标志位、排序,规定所有的进程申请资源必须以一定的顺序(升序或降序)做操作来避免死锁。

14、线程有哪些状态?

  • NEW: 新建状态,线程对象已经创建,但尚未启动
  • RUNNABLE:就绪状态,可运行状态,调用了线程的start方法,已经在java虚拟机中执行,- 等待获取操作系统资源如CPU,操作系统调度运行。
  • Running:就绪状态的线程获取到了CPU
  • BLOCKED:堵塞状态。线程等待锁的状态,等待获取锁进入同步块/方法或调用wait后重新进入需要竞争锁
  • WAITING:等待状态。等待另一个线程以执行特定的操作。调用以下方法进入等待状态。 - Object.wait(), Thread.join(),LockSupport.park
  • TIMED_WAITING: 线程等待一段时间。调用带参数的Thread.sleep, objct.wait,Thread.join,LockSupport.parkNanos,LockSupport.parkUntil
  • TERMINATED:进程结束状态。

15、sleep() 和 wait() 有什么区别?

  • 每个对象都有一个锁来控制同步访问,Synchronized关键字可以和对象的锁交互,来实现同步方法或同步块。sleep()方法正在执行的线程主动让出CPU,在sleep指定时间后CPU再回到该线程继续往下执行(注意:sleep方法只让出了CPU,而并不会释放同步资源锁!!!);wait()方法则是指当前线程让自己暂时退让出同步资源锁,以便其他正在等待该资源的线程得到该资源进而运行,只有调用了notify()方法,之前调用wait()的线程才会解除wait状态,可以去参与竞争同步资源锁,进而得到执行。(注意:notify的作用相当于叫醒睡着的人,而并不会给他分配任务,就是说notify只是让之前调用wait的线程有权利重新参与线程的调度);
  • sleep()方法可以在任何地方使用;wait()方法则只能在同步方法或同步块中使用;
  • sleep()是线程线程类(Thread)的方法,调用会暂停此线程指定的时间,但监控依然保持,不会释放对象锁,到时间自动恢复;wait()是Object的方法,调用会放弃对象锁,进入等待队列,待调用notify()/notifyAll()唤醒指定的线程或者所有线程,才会进入锁池,再次获得对象锁才会进入运行状态;
  • sleep()方法必须捕获异常,而wait()、notify()、notifyAll()不需要捕获异常。

作者:蜡笔没了小新_e8c0
链接:https://www.jianshu.com/p/0e42fb4334d5
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

16、多线程锁的升级原理是什么?

在这里插入图片描述

锁降级确实是会发生的,当JVM进入安全点(SafePoint)的时候,会检查是否有闲置的Monitor,然后试图进行降级。

17、

18、

19、

20、

21、

22、

23、

24、

25、

26、

27、

28、

四、多线程

1、

2、

3、

4、

5、

6、

7、

8、

9、

10、

11、

12、

13、

14、

15、

16、

17、

18、

19、

20、

21、

22、

23、

24、

25、

26、

27、

28、

五、反射

1、

2、

3、

4、

5、

6、

7、

8、

9、

10、

11、

12、

13、

六、拷贝对象

1、

2、

3、

4、

5、

6、

7、

8、

9、

10、

11、

12、

13、

七、异常

1、

2、

3、

4、

5、

6、

7、

8、

9、

10、

11、

12、

13、

八、网络

1、

2、

3、

4、

5、

6、

7、

8、

9、

10、

11、

12、

13、

发布了72 篇原创文章 · 获赞 78 · 访问量 21万+

猜你喜欢

转载自blog.csdn.net/Dreamhai/article/details/104456782