java面试笔试大全

面试题

java基础

java语言特点

  • 面向对象(贴近人类思维模式,模拟现实世界,解决现实问题)

  • 简单性(自动内存管理机制,不易造成内存溢出,简化流程处理,语义清晰)

  • 健壮性(Java语言的强类型机制、异常处理、垃圾的自动收集等)

  • 跨平台(操作系统,服务器,数据库)

  • 支持网络编程并且很方便(Java语言诞生本身就是为简化网络编程设计的)

  • 支持多线程(多线程机制使应用程序在同一时间并行执行多项任)

  • 安全性好

java的执行机制

先编译,在解释
将源文件编译成字节码文件(平台中立文件.class),再将字节码文件进行解释执行

命名规范

包名小写,类名大驼峰,方法名小驼峰

JDK 和 JRE 有什么区别?

JDK:Java Development Kit 的简称,java 开发工具包,提供了 java 的开发环境和运行环境。
包含了jre+类库+开发工具包(编译器+调试工具)

JRE:Java Runtime Environment 的简称,java 运行环境,为 java 的运行提供了所需环境。
包含jvm和解释器,完整的java运行环境

JVM虚拟机
使用软件在不同的操作系统中,模拟相同的环境

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

在这里插入图片描述
说明:

三者关系: JDK > JRE > JVM

什么是跨平台性?原理是什么

所谓跨平台性,是指java语言编写的程序,一次编译后,可以在多个系统平台上运行。实现原理:Java程序是通过java虚拟机在系统平台上运行的,只要该系统可以安装相应的java虚拟机,该系统就可以运行java程序

什么是字节码?采用字节码的最大好处是什么

字节码:Java源代码经过虚拟机编译器编译后产生的文件(即扩展为.class的文件),它不面向任
何特定的处理器,只面向虚拟机。

采用字节码的好处:
Java语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留
了解释型语言可移植的特点。所以Java程序运行时比较高效,而且,由于字节码并不专对一种特定
的机器,因此,Java程序无须重新编译便可在多种不同的计算机上运行

java中的编译器和解释器:

Java中引入了虚拟机的概念,即在机器和编译程序之间加入了一层抽象的虚拟机器。这台虚拟的机
器在任何平台上都提供给编译程序一个的共同的接口。编译程序只需要面向虚拟机,生成虚拟机能
够理解的代码,然后由解释器来将虚拟机代码转换为特定系统的机器码执行。在Java中,这种供虚
拟机理解的代码叫做字节码(即扩展为.class的文件),它不面向任何特定的处理器,只面向虚拟
机。每一种平台的解释器是不同的,但是实现的虚拟机是相同的。Java源程序经过编译器编译后变
成字节码,字节码由虚拟机解释执行,虚拟机将每一条要执行的字节码送给解释器,解释器将其翻
译成特定机器上的机器码,然后在特定的机器上运行,这就是上面提到的Java的特点的编译与解释
并存的解释。

Java源代码---->编译器---->jvm可执行的Java字节码(即虚拟指令)---->jvm---->jvm中解释器----->机
器可执行的二进制机器码---->程序运行

一个".java"源文件中是否可以包括多个类(不是内部类)?有什么限制?

可以。但最多只有一个类名声明为public,与文件名相同

程序文件名必须与公共外部类的名称完全一致(包括大小写)(对)

Java语言采用何种编码方案?有何特点?

Java语言采用Unicode编码标准,Unicode(标准码),它为每个字符制订了一个唯一的数值,因此在任何的语言,
平台,程序都可以放心的使用

语句:char foo=‘中’,是否正确?(假设源文件以GB2312编码存储,并且以javac – encoding GB2312命令编译)

正确
采用GB2312或GBK编码方式时,一个中文字符占2个字节;而采用UTF-8编码方式时,一个中文字符会占3个字节,在java中char类型,是16位,2个字节

/* * /中可以嵌套//注释,也能嵌套//注释(错误)。

JDK常用的包

java.lang: 这个是系统的基础类,比如String、Math、Integer、System和Thread, 提供常用功能。
 java.io: 这里面是所有输入输出有关的类,比如文件操作等
 java.net: 这里面是与网络有关的类,比如URL,URLConnection等。
 java.util : 这个是系统辅助类,特别是集合类Collection,List,Map等。
 java.sql: 这个是数据库操作的类,Connection, Statememt,ResultSet等

== 和 equals 的区别是什么?


==:它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象。
基本类型:比较的是值是否相同;
引用类型:比较的是引用(内存地址)是否相同;

 equals 默认情况下是引用比较,只是很多类重写了 equals 方法,比如 String等把它变成了值比较
equals() : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况:
1.类没有覆盖 equals() 方法。则通过 equals() 比较该类的两个对象时,等价于通过“==”比较这两个对象
2.类覆盖了 equals() 方法。一般,我们都覆盖 equals() 方法来两个对象的内容相等;若它们的内容相等,则返回
 true (即,认为这两个对象相等)

Java 中应该使用什么数据类型来代表价格?

java中使用BigDecimal来表示价格是比较好的

float f=3.4;是否正确

不正确。3.4 是双精度数,将双精度型(double)赋值给浮点型(float)属于下转型)会造成精度损失,因此需要强制类型转换float f =(float)3.4; 或者写成 floatf =3.4F

3*0.1 == 0.3 将会返回什么?true 还是 false?

false,因为有些浮点数不能完全精确的表示出来

		 System.out.println(3 * 0.1);//0.30000000000000004
        System.out.println(4 * 0.1);//0.4
		System.out.println(13 * 0.1);//1.3
        System.out.println(3 * 0.1 == 0.3);//false
        System.out.println(13 * 0.1 == 1.3);//true
        System.out.println(9 * 0.1 == 0.9);//true
        System.out.println(3 * 0.1 / 3);//0.10000000000000002

short s1 = 1; s1 = s1 + 1;有什么错? short s1 =1; s1 +=1;有什么错?

1.对于 short s1=1;s1=s1+1 来说,在 s1+1 运算时会自动提升表达式的类型为 int,那么将 int 赋予给 short 类型的变量 s1 会出现类型转换错误。,因此 s1+1 运算结果也是 int型,需要强制转换类型才能赋值给 short 型。
2.对于 short s1=1;s1+=1 来说 +=是 java 语言规定的运算符,可以正确编译,因为 s1+= 1;相当于 s1 = (short(s1 + 1);其中有隐含的强制类型转换

a = a + b 与 a += b 的区别

两者写法上主要在于是否能进行数据类型自动转换,事实上就是类型与精度上的差异,当两个操作数 数据类型一致时两种形式的运算结果没有差别 但数据类型不同时 此时两种形式就有区别了,a=a+b有可能因为类型不匹配报错,a+=b不会出现问题,因为它会自动强制类型转换

下面程序运行结果

public static void main(String[] args) {
    
    
        int i = 1;
        i = i++;
        int j = i++;
        int k = i + ++i * i++;
        System.out.println("i=" + i);
        System.out.println("j=" + j);
        System.out.println("k=" + k);
    }
i=4
j=1
k=11

分析:i=i++时 因为是i++,i确实后面变成2了,但是前面表达式的值为1,又赋值给i所以i又从2变成了1,给j赋值i++先赋值再++,所以j的值是1,i此时变成了2,给k赋值时 该表达式相当于 2+3*3 并且i又进行了加1 所以 i最后的值是4,k的值是11

java 中有可能出现 i + 1 < i 的情况吗?为什么

这个和java中的数值表示有关系,带符号的数都有最大值,到了最大值之后就变成负数了,可以看看java中负数的表示方法。原理讲了,下面给个例子:

int i = Integer.MAX_VALUE;
int j = i+1;
System.out.println(j<i);

溢出了,所以加1之后可能小于原来

⽤最有效率的⽅法算出2乘以8

2 << 3
因为将⼀个数左移n位,就相当于乘以了2的n次⽅,那么,⼀个数乘以8只要将其左移3位即
可,⽽位运算cpu直接⽀持的,效率最⾼,所以,2乘以8最效率的⽅法是2 << 3。

下面代码输出结果是?©

int i = 5;
int j = 10;
System.out.println(i + ~j);

A.Compilation error because”~”doesn’t operate on integers
B.-5
C.-6
D.15

~代表按位取反
负数的补码 = 原码取反 + 1,即:
-n = ~n + 1 → ~n = -n -1
j:10 = - 10 - 1 = -11
i + j = 5 + -11 = -6

下列代码的输出结果是

class Foo {
    
    
    final int i;
    int j;
    public void doSomething() {
    
    
        System.out.println(++j + i);
    }
}

不能执行,因为编译有错
final作为对象成员存在时,必须初始化;但是,如果不初始化,也可以在类的构造函数中初始
因为java允许将数据成员声明为final,却不赋初值。但是,blank finals必须在使用之前初始化,且必须在构造函数中初始化

break 和 continue,return 的区别?

breakcontinue 都是用来控制循环的语句。
break 用于完全结束一个循环,跳出循环体执行循环后面的语句。
continue 用于跳过本次循环,执行下次循环。

return 程序返回,不再执行下面的代码(结束当前的方法 直接返回)

下面的方法,当输入为2的时候返回值是多少

public static int getValue(int i) {
    
    
        int result = 0;
        switch (i) {
    
    
        case 1:
            result = result + i;
        case 2:
            result = result + i * 2;
        case 3:
            result = result + i * 3;
        }
        return result;
    }

10

case2 result=4 没有break,走入case3 result=4+6

java支持的基本数据类型有那些,什么是自动拆箱

Java支持的数据类型包括两种:基本数据类型、引用类型

1)基本数据类型有8种:
byteshortintlongfloatdoublebooleanchar
2)引用类型:
如String、包装类等类,数组,接口等

java 基本类型与引用类型的区别

基本类型保存原始值,引用类型保存的是引用值(引用值就是指对象在堆中所处的位置/地址)

自动装箱就是将基本数据类型自动转换为对应的包装类,即int转化为Integer,自动拆箱。就是将包装类自动转换为
基本数据类型

所以自动装箱和拆箱就是基本类型和引用类型之间的转换

包装类的作用:

1.一个实现基本类型之间的转换
2.是便于函数传值
3.是在一些地方要用到Object的时候方便将基本数据类型装换,例如集合中不能存放基本类型数据,如果要存放数字,应该通过包装类将基本类型数据包装起来,从而间接处理基本类型数据

int和integer的区别

1.Integer是int的包装类,int则是java的一种基本数据类型
2、Integer变量必须实例化后才能使用,而int变量不需要
3、Integer实际是对象的引用,当new一个Integer时,实际上是生成一个指针指向此对象;而int则是直接存储数据值
4、Integer的默认值是null,int的默认值是0

两个new生成的Integer变量的对比

由于Integer变量实际上是对一个Integer对象的引用,所以两个通过new生成的Integer变量永远是不相等的(因为new生成的是两个对象,其内存地址不同)。

Integer i = new Integer(10000);
Integer j = new Integer(10000);
System.out.print(i == j); //false

Integer变量和int变量的对比

Integer变量和int变量比较时,只要两个变量的值是向等的,则结果为true(因为包装类Integer和基本数据类型int比较时,java会自动拆包装为int,然后进行比较,实际上就变为两个int变量的比较)

    int a = 10000;
    Integer b = new Integer(10000);
    Integer c=10000;
    System.out.println(a == b); // true
    System.out.println(a == c); // true

非new生成的Integer变量和new Integer()生成变量的对比

非new生成的Integer变量和new Integer()生成的变量比较时,结果为false。(因为非new生成的Integer变量指向的是java常量池中的对象,而new Integer()生成的变量指向堆中新建的对象,两者在内存中的地址不同)

    Integer b = new Integer(10000);
    Integer c=10000;
    System.out.println(b == c); // false

两个非new生成的Integer对象的对比

对于两个非new生成的Integer对象,进行比较时,如果两个变量的值在区间-128到127之间,则比较结果为true,如果两个变量的值不在此区间,则比较结果为false

Integer i = 100;
Integer j = 100;
System.out.print(i == j); //true

Integer i = 128;
Integer j = 128;
System.out.print(i == j); //false

当值在 -128 ~ 127之间时,java会进行自动装箱,然后会对值进行缓存,如果下次再有相同的值,会直接在缓存中取出使用。缓存是通过Integer的内部类IntegerCache来完成的。当值超出此范围,会在堆中new出一个对象来存储。

java对于-128到127之间的数,会进行缓存,Integer i = 127时,会将127进行缓存,下次再写Integer j = 127时,就会直接从缓存中取,就不会new了

在不是new出来的情况下在自动装箱时对于值从–128127之间的值,它们被装箱为Integer对象后,会存在内存中被重用,始终只存在一个对
象而如果超过了从–128127之间的值,被装箱后的Integer对象并不会被重用,即相当于每次装箱时都新建一个 
Integer对象

当包装对象的数值进行比较时,不能简单的使用==来进行判断,当该对象的数值在-128127之间时可以进行比较,超
出范围时应该使用equals进行比较
public static  void main(String[] args){
    
    
        Integer a=1;
        Integer b=1;
        System.out.println(a+"----"+b);
        System.out.println(a==b);
        System.out.println(a.equals(b));
        Integer c=200;
        Integer d=200;
        System.out.println(c+"----"+d);
        System.out.println(c==d);
        System.out.println(c.equals(d));
    }   

结果:
在这里插入图片描述

如下两个题目输出结果相同吗?各是什么

Integer i = new Integer(1);
		Integer j = new Integer(1);
		System.out.println(i == j);
		Integer m = 1;
		Integer n = 1;
		System.out.println(m == n);//
		Integer x = 128;
		Integer y = 128;
		System.out.println(x == y);//
false
true
false
Object o1 = true ? new Integer(1) : new Double(2.0);
		System.out.println(o1);//1.0

三元运算符后面两个的类型需要一致,所有int会提升为double

&与&&的区别

&是按位与
&&是逻辑与(短路运算)
二者都要求运算符左右两端的布尔值都是true整个表达式的值才是true,&&之所以称为短路运算是因为,如果&&左边的表达式的值是false,右边的表达式会被直接短路掉,不会进行运算

switch 是否能作用在 byte 上,是否能作用在 long 上,是否能作用在 String上

Java5 以前 switch(expr)中,expr 只能是 byte、short、char、int。从 Java 5 开始,Java 中引入了枚举类型,
expr 也可以是 enum 类型。
从 Java 7 开始,expr 还可以是字符串(String),但是长整型(long)在目前所有的版本中都是不可以的

数组有没有 length() 方法?String 有没有 length() 方法

数组没有 length()方法,而是有 length 的属性。String 有 length()方法。JavaScript 中,获得字符串的长度是
通过 length 属性得到的,这一点容易和 Java 混淆

怎么将 byte 转换为 String?

可以使用 String 接收 byte[] 参数的构造器来进行转换,需要注意的点是要使用 的正确的编码,否则会使用平台默认编码,这个编码可能跟原来的编码相同,也可能不同。

我们能将 int 强制转换为 byte 类型的变量吗?如果该值大于 byte 类型的范围,将会出现什么现象?

可以做强制转换,但是 Java 中 int 是 32 位的,而 byte 是 8 位的,byte 类型的范围是从 -128 到 127所以,如果强制转化是超出了范围,int 类型的高 24 位将会被丢弃,

下列哪些语句关于内存回收的说明是正确的? (B )

A.程序员必须创建一个线程来释放内存
B. 内存回收程序负责释放无用内存
C. 内存回收程序允许程序员直接释放内存
D.内存回收程序可以在指定的时间释放内存对象

A、JVM一旦启动,就会创建一个守护线程来监测是否需要有对象内存被释放。
C、无法直接释放。
D、不可以指定时间,System.gc(),只是提醒JVM可以进行一次Full GC,但是什么时候真正执行,还是不知道的。

关于访问权限说法正确 的是 ? ( B)

A.外部类前面可以修饰public,protected和private
B.成员内部类前面可以修饰public,protected和private
C.局部内部类前面可以修饰public,protected和private
D.以上说法都不正确

( 1 )对于外部类而言,它也可以使用访问控制符修饰,但外部类只能有两种访问控制级别: public 和默认。因为外部类没有处于任何类的内部,也就没有其所在类的内部、所在类的子类两个范围,因此 private 和 protected 访问控制符对外部类没有意义。
( 2 )内部类的上一级程序单元是外部类,它具有 4 个作用域:同一个类( private )、同一个包(protected )和任何位置( public )。
( 3 ) 因为局部成员的作用域是所在方法,其他程序单元永远不可能访问另一个方法中的局部变量,所以所有的局部成员都不能使用访问控制修饰符修饰

数组


下列说法正确的有( AA.数组是一种对象
B.数组属于一种原生类
C.int number=[]{
    
    31,23,33,43,35,63}
D.数组的大小可以任意改变

Java中的那些基本类型属于原生类,而数组是引用类型,不属于原生类,可以看成是一种对象。

而C中的数组声明和初始化的格式不对

数组的大小一旦指定,就不可以进行改变

java语言中的数组元素下标总是从0开始,下标可以是整数或整型表达式。(对)

java语言的下面几种数组复制方法中,哪个效率最高?(B)

A.for 循环逐一复制
B.System.arraycopy
C.Array.copyOf
D.使用clone方法

面向对象

什么是面向对象,面向对象理解

面向对象就是分工与协作,面向过程更注重事情的每一个步骤及顺序,面向对象更注重事情有哪些参与者,以及各自需
要做什么

例如:洗衣机洗衣服
面向过程将会将任务拆解成一系列的步骤
1.打开洗衣机---2.放衣服---3.放洗衣粉---4.清洗---5.烘干

面向对象会拆成人和洗衣机两个对象:
人:打开洗衣机,放衣服,放洗衣粉
洗衣机:清洗,烘干

面向过程比较直接高效,面向对象易于复用维护和扩展

java创建对象的四种方法

  1. 使用new关键字来生成对象只是最常用的方式;
  2. 使用反射创建对象,调用java.lang.Class或者java.lang.reflect.Constructor类的new Instance()实例方法;
  3. 调用对象的clone()方法(要拷贝的对象需要实现Cloneable接口,并重写clone()方法);
  4. 使用反序列化方式,通过让类实现Serializable接口,然后使用new ObjectInputStream().readObject()来创建对象

构造方法的特点

构造方法的命名必须和类名完全相同
构造方法没有返回值
在创建新对象时,系统自动的调用该类的构造方法
构造方法可以重载, 不能重写
构造方法不能被static,final,abstract,synchronized,native等修饰符修饰
系统会为每个类默认添加一个无参构造,一旦为类编写构造方法,默认的构造方法将会被覆盖

构造器Constructor是否可被override

构造器Constructor不能被继承,因此不能重写Override,但可以被重载Overload。

封装

封装的意义,在于明确表示出允许外部使用的所有成员函数和数据项,内部细节对外部调用透明,外部调用无需修改或者关心内部实现
1.javabean的属性私有,提供getset对外访问,因为属性的赋值或者获取逻辑只能由javabean本身决定,而不能由外部胡乱修改

private string name;
public void setName(String name){
    
    
  this.name="物联网"+name;
}
//该name有自己的命名规则,明显不能由外部直接赋值

2.orm框架
操作数据库,我们不需要关心链接是如何建立的,sql是如何执行的,只需要引入mybatis,调用方法即可

继承

继承基类的方法,并做出自己的改变和扩展
子类共性的方法或者属性直接使用父类的,而不需要自己再定义,只需扩展自己个性化的,把共性的抽到父类,达到代码复用

继承特点

继承鼓励类的重用
继承可以多层继承
一个类只能继承一个父类,java只支持单继承
父类中private,default修饰的不能被继承
构造方法不能被继承,只能调用

继承带来的好处与弊端

继承的好处:
继承可以让类与类之间产生关系,子父类关系,产生子父类后,子类则可以使用父类中非私有的成员。

提高了代码的复用性(多个类相同的成员可以放到同一个类中) 提高了代码的维护性(如果方法的代码需要修改,修改一
处即可)

继承弊端:
继承让类与类之间产生了关系,类的耦合性增强了,当父类发生变化时子类实现也不得不跟着变化,削 弱了子类的独
立性

继承的应用场景: 使用继承,需要考虑类与类之间是否存在is..a的关系,不能盲目使用继承 is..a的关系:谁是谁
的一种,例如:老师和学生是人的一种,那人就是父类,学生和老师就是子类

继承中变量的访问特点

在子类方法中访问一个变量,采用的是就近原则。
1. 子类局部范围找 
2.  子类成员范围找 
3.  父类成员范围找 
4.  如果都没有就报错(不考虑父亲的父亲…)

继承中构造方法的访问特点

注意:子类中所有的构造方法默认都会访问父类中无参的构造方法
 
子类会继承父类中的数据,可能还会使用父类的数据。所以,子类初始化之前,一定要先完成父类数据的初始化, 
原因在于,每一个子类构造方法的第一条语句默认都是:super()
 
问题:如果父类中没有无参构造方法,只有带参构造方法,该怎么办呢?

1. 通过使用super关键字去显示的调用父类的带参构造方法 

2. 在父类中自己提供一个无参构造方法

继承中成员方法的访问特点

1.通过子类对象访问一个方法
3. 子类成员范围找 
4. 父类成员范围找 
5. 如果都没有就报错(不考虑父亲的父亲…)

继承的注意事项

 Java中类只支持单继承,不支持多继承(接口支持多继承)
 
 错误范例:class A extends B, C {
    
     }
 Java中类支持多层继承

java为什么是单继承

java中为什么要单继承,多实现,总结如下:

若为多继承,那么当多个父类中有重复的属性或者方法时,子类的调用结果会含糊不清,因此用了单继承。这样也会使程序更具安全性

为什么是多实现呢?

通过实现接口拓展了类的功能,若实现的多个接口中有重复的方法也没关系,因为实现类中必须重写接口中的方法,所以调用时还是调用的实现类中重写的方法。

如果一个接口Glass有个方法setColor(),有个类BlueGlass实现接口Glass,则在类BlueGlass中正确的是? (C )

A,protected void setColor() { …}
B.void setColor() { …}
C.public void setColor() { …}
D.以上语句都可以用在类BlueGlass中

接口中属性为public static final。方法为public abstract。子类的权限不能比父类更低

多态

基于对象所属类的不同,外部对同一个方法的调用,实际执行的逻辑不同
概念:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果,这就是多态性。简单的说:就是用父类的引用指向子类的对象
多态的三个条件:继承,方法重写,父类引用指向子类对象

父类类型 变量名=new 子类对象;
变量名.方法名();

缺点:无法调用子类特有的功能

多态中的成员访问特点

成员访问特点:

成员变量
  编译看父类,运行看父类
  或者编译看左边,运行也看左边
  
成员方法
  编译看父类,运行看子类
  或者编译看左边,运行看右边(因为方法有重写)

多态的好处和弊端

好处
  提高程序的扩展性。定义方法时候,使用父类型作为参数,在使用的时候,使用具体的子类型参与操作
  
弊端
  不能使用子类的特有成员 

根据下面这个程序的内容,判断哪些描述是正确的:( )

public class Test {
    
    
    public static void main(String args[]) {
    
    
        String s = "tommy";
        Object o = s;
        sayHello(o); //语句1
        sayHello(s); //语句2
    }
    public static void sayHello(String to) {
    
    
        System.out.println(String.format("Hello, %s", to));
    }
    public static void sayHello(Object to) {
    
    
        System.out.println(String.format("Welcome, %s", to));
    }
}

语句2输出为:Hello, tommy
语句1输出为:Welcome, tommy
如果是重载方法之间的选择,则是使用静态类型。
如果是父类与子类之间的重写方法的选择,则是使用动态类型。
如A a = new B(); 会使用类型B去查找重写的方法,使用类型A去查找重载的方法

final 在 java 中有什么作用

1.修饰变量时,定义时必须赋值,被修饰的变量不可变,一旦赋了储值就不能重新赋值
对于 基本类型 来说,不可改变指的是变量当中的数据不可改变,但是对于 引用类型 来说,不可改变的指的是变量当
中的地址值不可改变,数据是可以变的

2.修饰方法 该方法不能被子类重写但是可以被重载

3.修饰类,该类不能被继承,比如math,string类
对于类、方法来说,abstract关键字和final关键字不能同时使用,因为作用相互矛盾

fianl关键字的作用 final代表最终的意思,可以修饰成员方法,
成员变量,类 final修饰类、方法、变量的效果 fianl
修饰类:该类不能被继承(不能有子类,但是可以有父类) 
final修饰方法:该方法不能被重写
final修饰变量:表明该变量是一个常量,不能再次赋值 

final、finally、finalize的区别?

final 用于修饰变量、方法和类。

  • final 变量:被修饰的变量不可变,不可变分为引用不可变对象不可变,final 指的是引用不可变,final 修饰的变量必须初始化,通常称被修饰的变量为常量
  • final 方法:被修饰的方法不允许任何子类重写,子类可以使用该方法。
  • final 类:被修饰的类不能被继承,所有方法不能被重写。

finally 作为异常处理的一部分,它只能在 try/catch 语句中,并且附带一个语句块表示这段语句最终一定被执行(无论是否抛出异常),经常被用在需要释放资源的情况下,System.exit (0) 可以阻断 finally 执行。

finalize 是在 java.lang.Object 里定义的方法,也就是说每一个对象都有这么个方法,这个方法在 gc 启动,该对象被回收的时候被调用。

一个对象的 finalize 方法只会被调用一次,finalize 被调用不一定会立即回收该对象,所以有可能调用 finalize 后,该对象又不需要被回收了,然后到了真正要被回收的时候,因为前面调用过一次,所以不会再次调用 finalize 了,进而产生问题,因此不推荐使用 finalize 方法

static的访问特点

非静态的成员方法
能访问静态的成员变量 
能访问非静态的成员变量 
能访问静态的成员方法 
能访问非静态的成员方法
静态的成员方法
能访问静态的成员变量 
能访问静态的成员方法

静态成员方法只能访问静态成员

静态变量的特点与区别

静态变量全局唯一,为所有的对象共用,修改它的值,其他对象使用该变量时,值也会改变

非静态变量,每个对象持有一份,是独立的,修改对象的值不会影响其他对象的该值。

抽象类(abstract class)和接口(interface)有什么异同

抽象类:
1.抽象类中可以定义构造器
2.可以有抽象方法和具体方法
3.抽象类中可以定义成员变量,抽象类中的成员可以是 private、默认、protected、public
4.有抽象方法的类必须被声明为抽象类,而抽象类未必要有抽象方法
5.抽象类中可以包含静态方法
6.一个类只能继承一个抽象类

接口:
1.接口中不能定义构造器
2.方法全部都是抽象方法
3.接口中的成员全都是 public 的
4.接口中定义的成员变量实际上都是常量
5.接口中不能有静态方法
6.一个类可以实现多个接口
相同:
1.不能够实例化
2.可以将抽象类和接口类型作为引用类型
3.一个类如果继承了某个抽象类或者实现了某个接口都需要对其中的抽象方法全部进行实现,否则该类仍然需要
被声明为抽象类

抽象选择题

下列哪一种叙述是正确的( DA.abstract修饰符可修饰字段、方法和类
B.抽象方法的body部分必须用一对大括号{
    
     }包住
C.声明抽象方法,大括号可有可无
D.声明抽象方法不可写出大括号

A:abstract修饰方法和类

B、C:抽象方法没有方法体,有没有方法体看有没有大括号

接⼝是否可继承接⼝? 抽象类是否可实现(implements)接⼝? 抽象类是否可继承具体类? 抽象类中是否可以有静态的main⽅法?

接⼝可以继承接⼝。抽象类可以实现(implements)接⼝,抽象类可继承具体类。抽象类中可以有静态的main⽅法

只有记住抽象类与普通类的唯⼀区别就是不能创建实例对象和允许有abstract⽅法

抽象类方法的访问权限默认都是public。( 错 )

关于抽象类
JDK 1.8以前,抽象类的方法默认访问权限为protected
JDK 1.8时,抽象类的方法默认访问权限变为default

关于接口
JDK 1.8以前,接口中的方法必须是public的
JDK 1.8时,接口中的方法可以是public的,也可以是default的
JDK 1.9时,接口中的方法可以是private的

说说overload和override的区别

方法重载:在同一个类中,方法名相同,参数列表不同,和返回值无关
方法重写:在子父类中,子类有一个和父类方法名相同,参数列表相同,返回值类型也相同的方法。这个就叫方法的重写,要实现继承关系,子类方法修饰符要大于父类	

参数列表不同可以是1.参数类型不同,2.参数个数不同,3.参数顺序不同

Java 中是否可以覆盖(override)一个 private 或者是 static 的方法?

Javastatic 方法不能被覆盖,因为方法覆盖是基于运行时动态绑定的,而 static 方法是编译时静态绑定的。static 
方法跟类的任何实例都不相关,所以概念上不适用。java 中也不可以覆盖 private 的方法,因为 private 修饰的变量
和方法只能在当前类中使用,如果是其他的类继承当前类是不能访问到 private 变量或方法的,当然也不能覆盖

为什么函数不能根据返回类型来区分重载?

因为调用时不能指定类型信息,编译器不知道你要调用哪个函数

1.float max(int a, int b);
2.int max(int a, int b);
当调用 max(1, 2);时无法确定调用的是哪个,单从这一点上来说,仅返回值类型不同的重载是不应该允许的。

函数的返回值只是作为函数运行之后的一个“状态”,他是保持方法的调用者与被调用者进行通信的关键。并不能
作为某个方法的“标识”

构造器(constructor)是否可被重写(override)?

构造器不能被继承,因此不能被重写,但可以被重载。每一个类必须有自己的构造函数,负责构造自己这部分的构造。子类不会覆盖父类的构造函数,相反必须一开始调用父类的构造函数。

说出下面运行结果

char[] arr = new char[] {
    
     'a', 'b', 'c' };
			System.out.println(arr);//
			int[] arr1 = new int[] {
    
     1, 2, 3 };
			System.out.println(arr1);//
			double[] arr2 = new double[] {
    
     1.1, 2.2, 3.3 };
			System.out.println(arr2);//
abc
[I@24d46ca6
[D@4517d9a3

代码块

java静态变量、代码块、和静态方法的执行顺序是什么?

基本上代码块分为三种:Static静态代码块、构造代码块、普通代码块

代码块执行顺序静态代码块——> 构造代码块 ——> 构造函数——> 普通代码块

继承中代码块执行顺序:父类静态块——>子类静态块——>父类代码块——>父类构造器——>子类代码块——>子类构造器

说出下面运行结果

class HelloA {
    
    

	public HelloA() {
    
    
		System.out.println("HelloA");
	}

	{
    
     System.out.println("I'm A class"); }

	static {
    
     System.out.println("static A"); }

}

public class HelloB extends HelloA {
    
    
	public HelloB() {
    
    
		System.out.println("HelloB");
	}

	{
    
     System.out.println("I'm B class"); }

	static {
    
     System.out.println("static B"); }

	public static void main(String[] args) {
    
     
		new HelloB(); 
	}
}
static A
static B
I'm A class
HelloA
I'm B class
HelloB

父类–静态代码块
子类–静态代码块
父类–非静态代码块
父类–构造函数
子类–非静态代码块
子类–构造函数

说出下面运行结果

父类

package ms;

public class Father {
    
    
    private int i=test();
    private static int j=method();

    //静态代码块
    static {
    
    
        System.out.print("(1)");
    }
   //构造方法
    Father(){
    
    
        System.out.print("(2)");
    }
    //成员代码块
    {
    
    
        System.out.print("(3)");
    }

    public int test(){
    
    
        System.out.print("(4)");
        return 1;
    }

    public static int method(){
    
    
        System.out.print("(5)");
        return 1;
    }
}

子类

package ms;

public class Son extends Father{
    
    
    private int i=test();
    private static int j=method();

    //静态代码块
    static {
    
    
        System.out.print("(6)");
    }
    //构造方法
    Son (){
    
    
        System.out.print("(7)");
    }
    //成员代码块
    {
    
    
        System.out.print("(8)");
    }

    @Override
    public int test(){
    
    
        System.out.print("(9)");
        return 1;
    }

    public static int method(){
    
    
        System.out.print("(10)");
        return 1;
    }

    public static void main(String[] args) {
    
    
        Son s1=new Son();
        System.out.println();
        Son s2=new Son();
    }
}

顺序为

(5)(1)(10)(6)(9)(3)(2)(9)(8)(7)
(9)(3)(2)(9)(8)(7)

解析:

首先得知道类的实例化顺序
1.父类静态变量
2.父类静态代码块
3.子类静态变量
4.子类静态代码块
5.父类非静态变量(父类成员变量)
6.父类成员态代码块
7.父类构造函数
8.子类非静态变量
9.子类成员代码块
10.子类构造函数
这一题除了要考虑执行顺序以外还要考虑方法的重写,test方法被子类重写了执行的子类的方法
method方法不被重写,因为final,static,private的方法都不可以被重写

运行代码,输出的结果是()

public class P {
    
    
    public static int abc = 123;
    static{
    
    
        System.out.println("P is init");
    }
}
public class S extends P {
    
    
    static{
    
    
        System.out.println("S is init");
    }
}
public class Test {
    
    
    public static void main(String[] args) {
    
    
        System.out.println(S.abc);
    }
}

A. P is init
   123
B. S is init
P is init
   123
C. P is init
S is init
   123
D. S is init
   123

不会初始化子类的几种
调用的是父类的static方法或者字段
调用的是父类的final方法或者字段
通过数组来引用

常用类

math类

Math.round(11.5) 等于多少?Math.round(-11.5)等于多少

Math.round(11.5)的返回值是 12,Math.round(-11.5)的返回值是-11。四舍五入的原理是在参数上加 0.5 然后进行下取整。

String类

String 是基本数据类型吗?

String 是引用类型,底层用 char 数组实现的

字符串如何转基本数据类型

调用基本数据类型对应的包装类中的方法 parseXXX(String)或 valueOf(String)即可返回相应基本类型

基本数据类型如何转字符串?

一种方法是将基本数据类型与空字符串(“”)连接(+)即可获得其所对应的字符串;另一种方法是调用 String
类中的 valueOf()方法返回相应字符串

String不变性理解

  • String类是被final进行修饰的,不能被继承
  • 在用+号拼接字符串时会创建新的字符串
  • String s1=“abc”,"abc被放到常量池去了,String s2=“abc”,只会将引用指向原来那个常量
  • String s1=new String(“abc”) 可能创建两个对象也可能创建一个对象,如果静态区有“abc”字符串常量的话,则仅仅在堆中创建一个对象,如果静态区中没有,则堆和静态区中都需要创建对象
  • java使用+拼接字符串,最好使用StringBuilder的append(来实现)

什么是不可变对象?好处是什么?

不可变对象指对象一旦被创建,状态就不能再改变,任何修改都会创建一个新的对象,如 String、Integer及其它包装类.不可变对象最大的好处是线程安全.

能否创建一个包含可变对象的不可变对象?

当然可以,比如final Person[] persons = new Persion[]{}. persons是不可变对象的引用,但其数组中的Person实例却是可变的.这种情况下需要特别谨慎,不要共享可变对象的引用.这种情况下,如果数据需要变化时,就返回原对象的一个拷贝.

java 中操作字符串都有哪些类?它们之间有什么区别?

StringStringBufferStringBuilder。
都是final修饰的类,都不允许被继承

String是不可变的,StringBufferStringBuilder是可变的

String类利用了final修饰的char类型数组存储字符,源码如下:

private final char value[];

StringBuilderStringBuffer都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数
组保存字符串,这两种对象都是可变的。

源码如下:

char[] value;

StringBuffer是线程安全的,StringBuilder不是线程安全的,但它们两个所有方法是相同的,StringBufferStringBuilder的方法上添加了synchronized修饰,保证线程安全

StringStringBufferStringBuilder 的区别在于 String 声明的是不可变的对象,在修改时不会改变自身;
若修改,等于重新生成新的字符串对象,然后将指针指向新的 String 对象,而 StringBufferStringBuilder 可
以在原有对象的基础上进行操作,在修改时会改变对象自身,每次操作都是对 对象本身进行修改,不是生成新的对象
所以在经常改变字符串内容的情况下最好不要使用 StringStringBuilderStringBuffer 有公共父类 AbstractStringBuilder(抽象类)StringBufferStringBuilder 
最大的区别在于,StringBuffer 是线程安全的,而 StringBuilder 是非线程安全的,但 StringBuilder 的性能却
高于StringBuffer,所以在单线程环境下推荐使用 StringBuilder,多线程环境下推荐 使用 StringBuffer

String为什么要设计成不可变的?

1.便于实现字符串池(String pool)

在Java中,由于会大量的使用String常量,如果每一次声明一个String都创建一个String对象,那将会造成极大的空间资源的浪费。Java提出了String pool的概念,在堆中开辟一块存储空间String pool,当初始化一个String变量时,如果该字符串已经存在了,就不会去创建一个新的字符串变量,而是会返回已经存在了的字符串的引用。

String a = "Hello world!";
String b = "Hello world!";

如果字符串是可变的,某一个字符串变量改变了其值,那么其指向的变量的值也会改变,String pool将不能够实现!

2.使多线程安全

在并发场景下,多个线程同时读一个资源,是安全的,不会引发竞争,但对资源进行写操作时是不安全的,不可变对象不能被写,所以保证了多线程的安全。

3.避免安全问题

在网络连接和数据库连接中字符串常常作为参数,例如,网络连接地址URL,文件路径path,反射机制所需要的String参数。其不可变性可以保证连接的安全性。如果字符串是可变的,黑客就有可能改变字符串指向对象的值,那么会引起很严重的安全问题。

4.加快字符串处理速度

由于String是不可变的,保证了hashcode的唯一性,于是在创建对象时其hashcode就可以放心的缓存了,不需要重新计算。这也就是Map喜欢将String作为Key的原因,处理速度要快过其它的键对象。所以HashMap中的键往往都使用String。

总体来说,String不可变的原因要包括 设计考虑,效率优化,以及安全性这三大方面。

什么是字符串常量池?

jvm为了提升性能和减少内存开销,避免字符的重复创建,其维护了一块特殊的内存空间,即字符串池,当需要使用字符串时,先去字符串池中查看该字符串是否已经存在,如果存在,则可以直接使用,如果不存在,初始化,并将该字符串放入字符串常量池中。

字符串常量池的位置也是随着jdk版本的不同而位置不同。在jdk6中,常量池的位置在永久代(方法区)中,此时常量池中存储的是对象。在jdk7中,常量池的位置在堆中,此时,常量池存储的就是引用了。在jdk8中,永久代(方法区)被元空间取代了。

String 转出 int 型,判断能不能转?如何转

可以转,得处理异常 Integer.parseInt(s) 主要为 NumberFormatException:当你输入为字母时,也就是内容不是数字时,如 abcd,
当你输入为空时 )当你输入超出int 上限时 Long.parseLong("123")转换为 long

String s = “Hello”;s = s + " world!";这两行代码执行后,原始的 String 对象中的内容到底变了没有?

没有。因为 String 被设计成不可变(immutable)类,所以它的所有对象都是不可变对象。在这段代码中,s 原先指
向一个 String 对象,内容是 “Hello”,然后我们对 s 进行了“+”操作,那么 s 所指向的那个对象是否发生了改变呢?
答案是没有。这时,s 不指向原来那个对象了,而指向了另一个 String 对象,内容为"Hello world!",原来那个对象还存在于内存之中,只是 s 这个引用变量不再指向它了。

请说出下面程序的输出

		String s1 = "Programming";
		String s2 = new String("Programming");
		String s3 = "Program";
		String s4 = "ming";
		String s5 = "Program" + "ming";
		String s6 = s3 + s4;
		System.out.println(s1 == s2); //false
		System.out.println(s1 == s5); //true
		System.out.println(s1 == s6); //false
		System.out.println(s1 == s6.intern()); //true
		System.out.println(s2 == s2.intern()); //false

String 对象的 intern()方法会得到字符串对象在常量池中对应的版本的引用(如果常量池中有一个字符串与String 对象的 equals 结果是 true),如果常量池中没有对应的字符串,则该字符串将被添加到常量池中,然后返回常量池中字符串的引用

下面程序的运行结果是

String str1 = "hello";
String str2 = "he" + new String("llo");
System.err.println(str1 == str2);//flase

String str1 = “hello”;这里的str1指的是方法区的字符串常量池中的“hello”,编译时期就知道的; String str2 = “he” + new String(“llo”);这里的str2必须在运行时才知道str2是什么,所以它是指向的是堆里定义的字符串“hello”,所以这两个引用是不一样的。

如果用str1.equal(str2),那么返回的是True;因为两个字符串的内容一样

下面程序运行结果

public class Example {
    
    
    String str=new String("good");
    char[] ch={
    
    'a','b','c'};

    public static void main(String[] args) {
    
    
        Example example = new Example();
        example.change(example.str,example.ch);
        System.out.print(example.str+"and");
        System.out.print(example.ch);
    }

    public void change(String str,char ch[]){
    
    
        str="test ok";
        ch[0]='g';
    }
}
goodandgbc

首先说下String确实是个不可变对象,这个不可变是JDK特有的,写JAVA的人特意针对的 但是这与本题无关,题目中的形参str只是原引用ex.str的一个引用副本,传的是一个副本地址值,这个值与ex.str地址值是不一样的,但是它们同时指向了堆中的对象new String(“good”),当你在函数中改变形参也就是地址的副本值也就是这句str=“test ok"只是将副本地址指向常量"test ok”,并没有改变原ex.str的指向方向,它还是指向对象new String(“good”)的 char数组与String一样传的也是地址的副本,但是关键是形参ch它没有新的指向 ch[0]只是ch在指向原对象时改变了对象的内部结构, 所以在ex.ch指向与它是同一个对象的情况下当然也会随之变化

public class test1 {
    
    
    public static void main(String[] args) {
    
    
        int i=1;
        String str="hello";
        Integer num=2;
        int[] arr={
    
    1,2,3,4,5};
        mydata mydata=new mydata();

        change(i,str,num,arr,mydata);
        System.out.println("i ="+i);
        System.out.println("str ="+str);
        System.out.println("num ="+num);
        System.out.println("arr ="+ Arrays.toString(arr));
        System.out.println("mydata.a ="+mydata.a);

    }


    public static void change(int j,String s,Integer n,int [] a,mydata m){
    
    
        j+=1;
        s+="world";
        n+=1;
        a[0]+=1;
        m.a+=1;
    }
}

class mydata{
    
    
    int a=10;
}
i =1
str =hello
num =2
arr =[2, 2, 3, 4, 5]
mydata.a =11

包装类和string类都是新建了一个对象

集合

ArrayList的源码分析:

jdk 7情况下

ArrayList list = new ArrayList();//底层创建了长度是10的Object[]数组elementData
 list.add(123);//elementData[0] = new Integer(123);
 list.add(11);//如果此次的添加导致底层elementData数组容量不够,则扩容。

默认情况下,扩容为原来的容量的1.5倍,同时需要将原有数组中的数据复制到新的数组中。

结论:建议开发中使用带参的构造器:ArrayList list = new ArrayList(int capacity)

jdk 8中ArrayList的变化

ArrayList list = new ArrayList();//底层Object[] elementData初始化为{}.并没有创建长度为10的数组
list.add(123);//第一次调用add()时,底层才创建了长度10的数组,并将数据123添加到elementData[0]
后续的添加和扩容操作与jdk 7 无异

小结:jdk7中的ArrayList的对象的创建类似于单例的饿汉式,而jdk8中的ArrayList的对象的创建类似于单例的懒汉式,延迟了数组的创建,节省内存

ArrayList 和 Vector,LinkedList 的区别

这两个类都实现了 List 接口(List 接口继承了 Collection 接口),他们都是有序集合,即存储在这两个集合中
的元素的位置都是有顺序的,相当于一种动态的数组,我们以后可以按位置索引号取出某个元素,并且其中的数据是允
许重复的,这是HashSet 之类的集合的最大不同处,HashSet 之类的集合不可以按索引号去检索其中的元素,也不允
许有重复的元素

Vector 是线程安全的,也就是说是它的方法之间是线程同步的,而 ArrayList 是线程序不安全的,它的方法之间是
线程不同步的

ArrayList 与 Vector 都有一个初始的容量大小,当存储进它们里面的元素的个数超过了容量时,就需要增加 
ArrayList 与 Vector 的存储空间,每次要增加存储空间时,不是只增加一个存储单元,而是增加多个存储单元,每
次增加的存储单元的个数在内存空间利用与程序效率之间要取得一定的平衡。Vector 默认增长为原来两倍,

ArrayList 与 Vector 都可以设置初始的空间大小,Vector 还可以设置增长的空间大小,而 ArrayList 没有提供
设置增长空间的方法

总结:即 Vector 增长原来的一倍,ArrayList 增加原来的 0.5 倍。

ArrayList 和 Vector 都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,它们都
允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢,Vector 由
于使用了synchronized 方法(线程安全)。通常性能上较 ArrayList 差,而 LinkedList 使用双向链表实现存
储,按序号索引数据需要进行前向或后向遍历,但是插入数据时只需要记录本项的前后项即可,所以插入速度较快 。


ArrayList 在查找时速度快,LinkedList 在插入与删除时更具优势

说⼀下ArrayList和LinkedList区别

  1. ⾸先,他们的底层数据结构不同,ArrayList底层是基于数组实现的,LinkedList底层是基于链表实现的
  2. 由于底层数据结构不同,他们所适⽤的场景也不同,ArrayList更适合随机查找,LinkedList更适合删除和添加
  3. 二者都线程不安全,相对线程安全的Vector,执行效率高
  4. 另外ArrayList和LinkedList都实现了List接⼝,但是LinkedList还额外实现了Deque接⼝,所以LinkedList还可以当做队列来使⽤

ArrayList和Vector的区别

Vector和ArrayList几乎是完全相同的,唯一的区别在于Vector是同步类(synchronized),属于强同步类。因此开销就比ArrayList要大,访问要慢。正常情况下,大多数的Java程序员使用ArrayList而不是Vector,因为同步完全可以由程序员自己来控制。Vector每次扩容请求其大小的2倍空间,而ArrayList是1.5倍。Vector还有一个子类Stack。

数组(Array)和列表(ArrayList)有什么区别?什么时候应该使⽤Array⽽不是ArrayList?

Array可以包含基本类型和对象类型,ArrayList只能包含对象类型。
Array⼤⼩是固定的,ArrayList的⼤⼩是动态变化的。
ArrayList处理固定⼤⼩的基本数据类型的时候,这种⽅式相对⽐较慢

Iterator和ListIterator的区别是什么?

Iterator可⽤来遍历Set和List集合,但是ListIterator只能⽤来遍历List。
Iterator对集合只能是前向遍历,ListIterator既可以前向也可以后向。
ListIterator实现了Iterator接⼝,并包含其他的功能,⽐如:增加元素,替换元素,获取前⼀个和后⼀个元素的索引,等等

为什么重写 equals 方法必须重写 hashcode 方法 ?

判断的时候先根据hashcode进行的判断,相同的情况下再根据equals()方法进行判断。如果只重写了equals方法,而不重写hashcode的方法,会造成hashcode的值不同,而equals()方法判断出来的结果为true。

在Java中的一些容器中,不允许有两个完全相同的对象,插入的时候,如果判断相同则会进行覆盖。这时候如果只重写了equals()的方法,而不重写hashcode的方法,Object中hashcode是根据对象的存储地址转换而形成的一个哈希值。这时候就有可能因为没有重写hashcode方法,造成相同的对象散列到不同的位置而造成对象的不能覆盖的问题。

hashCode()与equals()

  • 如果两个对象相等,则hashcode一定也是相同的;
  • 两个对象相等,对两个对象分别调用equals方法都返回true;
  • 两个对象有相同的hashcode值,它们也不一定是相等的;

hashset底层原理

在这里插入图片描述

我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,
此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置(即为:索引位置),判断数组此位置上是否已经有元素:
7. 如果此位置上没有其他元素,则元素a添加成功。 —>情况1
8. 如果此位置上有其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的hash值:
1. 如果hash值不相同,则元素a添加成功。—>情况2
2. 如果hash值相同,进而需要调用元素a所在类的equals()方法:
equals()返回true,元素a添加失败
equals()返回false,则元素a添加成功。—>情况2

对于添加成功的情况2和情况3而言:元素a 与已经存在指定索引位置上数据以链表的方式存储。
jdk 7 :元素a放到数组中,指向原来的元素。
jdk 8 :原来的元素在数组中,指向元素a
总结:七上八下

HashSet底层:数组+链表的结构

在List内去除重复数字值,要求尽量简单

public static List duplicateList(List list) {
    
    
        HashSet set = new HashSet();
        set.addAll(list);
        return new ArrayList(set);
    }
    public static void main(String[] args) {
    
    
        List list = new ArrayList();
        list.add(new Integer(1));
        list.add(new Integer(2));
        list.add(new Integer(2));
        list.add(new Integer(4));
        list.add(new Integer(4));
        List list2 = duplicateList(list);
        for (Object integer : list2) {
    
    
            System.out.println(integer);
        }
    }

说出下面结果

其中Person类中重写了hashCode()和equal()方法

public static void main(String[] args) {
    
    
        HashSet set = new HashSet();
        Person p1 = new Person(1001,"AA");
        Person p2 = new Person(1002,"BB");
        set.add(p1);
        set.add(p2);
        p1.name = "CC";
        set.remove(p1);
        System.out.println(set);//[Person{id=1002, name='BB'}, Person{id=1001, name='CC'}]
        set.add(new Person(1001,"CC"));
        System.out.println(set);//[Person{id=1002, name='BB'}, Person{id=1001, name='CC'}, Person{id=1001, name='CC'}]
        set.add(new Person(1001,"AA"));//[Person{id=1002, name='BB'}, Person{id=1001, name='CC'}, Person{id=1001, name='CC'}, Person{id=1001, name='AA'}]
        System.out.println(set);
    }

在这里插入图片描述

说⼀下HashMap的Put⽅法

先说HashMap的Put⽅法的⼤体流程:

  1. 根据Key通过哈希算法与与运算得出数组下标
  2. 如果数组下标位置元素为空,则将key和value封装为Entry对象(JDK1.7中是Entry对象,JDK1.8中是Node对象)并放⼊该位置
  3. 如果数组下标位置元素不为空,则要分情况讨论
    a. 如果是JDK1.7,则先判断是否需要扩容,如果要扩容就进⾏扩容,如果不⽤扩容就⽣成Entry对
    象,并使⽤头插法添加到当前位置的链表中
    b. 如果是JDK1.8,则会先判断当前位置上的Node的类型,看是红⿊树Node,还是链表Node
    i. 如果是红⿊树Node,则将key和value封装为⼀个红⿊树节点并添加到红⿊树中去,在这个过程中会判断红⿊树中是否存在当前key,如果存在则更新value
    ii. 如果此位置上的Node对象是链表节点,则将key和value封装为⼀个链表Node并通过尾插法插
    ⼊到链表的最后位置去,因为是尾插法,所以需要遍历链表,在遍历链表的过程中会判断是否存在当key,如果存在则更新value,当遍历完链表后,将新链表Node插⼊到链表中,插⼊到链表后,会看当前链表的节点个数,如果⼤于等于8,那么则会将该链表转成红⿊树
    iii. 将key和value封装为Node插⼊到链表或红⿊树中后,再判断是否需要进⾏扩容,如果需要就
    扩容,如果不需要就结束PUT⽅法

HashMap装填因子,负载因子,加载因子为什么是0.75

装填因子设置为1:空间利用率得到了很大的满足,很容易碰撞,产生链表,导致查询效率低
装填因子设置为0.5: 碰撞的概率低,扩容,产生链表的几率低,查询效率高,空间利用率低

HashMap的长度为什么必须为2^n

  1. h&(length-1)等效 h%length 操作,等效的前提是:length必须是2的整数倍
  2. 防止哈希冲突,位置冲突

HashMap和Hashtable的区别

  • Hashtable 是线程安全的,HashMap 不是线程安全的。
  • Hashtable 所有的元素操作都是 synchronized 修饰的,而 HashMap 并没有。
    既然 Hashtable 是线程安全的,每个方法都要阻塞其他线程,所以 Hashtable 性能较差,HashMap 性能较好,使用更广。如果要线程安全又要保证性能,建议使用 JUC 包下的 ConcurrentHashMap
  • Hashtable 是不允许键或值为 null 的,HashMap 的键值则都可以为 null。
  • 两者继承的类不一样,Hashtable 继承了 Dictionary类,而 HashMap 继承的是 AbstractMap类。
  • HashMap 的初始容量为:16,Hashtable 初始容量为:11,两者的负载因子默认都是:0.75。
  • HashMap扩容为原来的两倍,Hashtable扩容为原来两倍+1
    在这里插入图片描述

HashSet底层原理

HashSet底层就是利用HashMap来完成的,每次添加其实就是调用的HashMap的put方法只是value是个固定值罢了

private transient HashMap<E,object> map;
private static final Object PRESENT = new Object();
public HashSet() {
    
    
        map = new HashMap<>(); //HashSet底层就是利用HashMap来完成的
    }
public boolean add(E e) {
    
    
        return map.put(e, PRESENT)==null;
    }

下面程序的运行结果

public static void main(String[] args) {
    
    
        List list = new ArrayList();
        list.add(1);
        list.add(2);
        list.add(3);
        updateList(list);
        System.out.println(list);//
    }

    private static void updateList(List list) {
    
    
        list.remove(2);
    }
[1, 2]

解析:remove,一种是根据下标删除元素,一种是根据内容删除元素,当直接写list.remove(2),会直接当作下标删除,省去了自动装箱的过程如果想要删除的是元素2,而不是下标2那么就要如下,

public static void main(String[] args) {
    
    
        List list = new ArrayList();
        list.add(1);
        list.add(2);
        list.add(3);
        updateList(list);
        System.out.println(list);//
    }

    private static void updateList(List list) {
    
    
        list.remove(new Integer(2));
    }
[1, 3]

在使用 HashMap 的时候,用 String 做 key 有什么好处?

HashMap 内部实现是通过 key 的 hashcode 来确定 value 的存储位置,因为字符串是不可变的,所以当创建字符串时,它的 hashcode 被缓存下来,不需要再次计算,所以相比于其他对象更快。

Comparable和Comparator接⼝是⼲什么的?列出它们的区别。

Comparable

Comparable可以认为是一个内比较器,实现了Comparable接口的类有一个特点,就是这些类是可以和自己比较的,至于具体和另一个实现了Comparable接口的类如何比较,则依赖compareTo方法的实现,compareTo方法也被称为自然比较方法。如果开发者add进入一个Collection的对象想要Collections的sort方法帮你自动进行排序的话,那么这个对象必须实现Comparable接口。compareTo方法的返回值是int,有三种情况:

1、比较者大于被比较者(也就是compareTo方法里面的对象),那么返回正整数

2、比较者等于被比较者,那么返回0

3、比较者小于被比较者,那么返回负整数

Comparator

Comparator可以认为是是一个外比较器,个人认为有两种情况可以使用实现Comparator接口的方式:

1、一个对象不支持自己和自己比较(没有实现Comparable接口),但是又想对两个对象进行比较
2、一个对象实现了Comparable接口,但是开发者认为compareTo方法中的比较方式并不是自己想要的那种比较方式

Comparator接口里面有一个compare方法,方法有两个参数T o1和T o2,是泛型的表示方式,分别表示待比较的两个对象,方法返回值和Comparable接口一样是int,有三种情况:

1、o1大于o2,返回正整数
2、o1等于o2,返回0
3、o1小于o3,返回负整数

1、如果实现类没有实现Comparable接口,又想对两个类进行比较(或者实现类实现了Comparable接口,但是对compareTo方法内的比较算法不满意),那么可以实现Comparator接口,自定义一个比较器,写比较算法

2、实现Comparable接口的方式比实现Comparator接口的耦合性 要强一些,如果要修改比较算法,要修改Comparable接口的实现类,而实现Comparator的类是在外部进行比较的,不需要对实现类有任何修 改。从这个角度说,其实有些不太好,尤其在我们将实现类的.class文件打成一个.jar文件提供给开发者使用的时候。实际上实现Comparator 接口的方式后面会写到就是一种典型的策略模

Java集合类框架的最佳实践有哪些

  • 假如元素的⼤⼩是固 定的,⽽且能事先知道,我们就应该⽤Array⽽不是ArrayList。
  • 有些集合类允许指定初始容量。因此,如果我们能估计出存储的元素的数⽬,我们可以设
    置 初始容量来避免重新计算hash值或者是扩容。
  • 为了类型安全,可读性和健壮性的原因总是要使⽤泛型。同时,使⽤泛型还可以避免运⾏
    时的ClassCastException。
  • 使⽤JDK提供的不变类(immutable class)作为Map的键可以避免为我们⾃⼰的类实现hashCode()和equals()⽅法。
  • 编程的时候接⼝优于实现。
  • 底层的集合实际上是空的情况下,返回⻓度是0的集合或者是数组,不要返回null

Collection和Collections的区别

collection是集合类的上级接⼝,继承与它的接⼝主要是set和list。
collections类是针对集合类的⼀个帮助类.它提供⼀系列的静态⽅法对各种集合的搜索,排序,线程安全化等操作

IO流

字节字符区别

字节是存储容量的基本单位,字符是数字,字母,汉子以及其他语言的各种符号。
1 字节=8 个二进制单位:一个字符由一个字节或多个字节的二进制单位组成

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

C语言中,char类型占1一个字节,而汉子占2个字节,所以不能存储。

在Java中,char类型占2个字节,而且Java默认采用Unicode编码,一个Unicode码是16位,所以一个Unicode码占两个字
节,Java中无论汉子还是英文字母都是用Unicode编码来表示的。所以,在Java中,char类型变量可以存储一个中文汉字

序列化 ID作用

它决定着是否能够成功反序列化,简单来说,java的序列化机制是通过在运行时判断类serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地实体类中的serialVersionUID进行比较,如果相同则认为是一致的,便可以进行反序列化,否则就会报序列化版本不一致的异常

多线程

线程和进程有什么区别?

进程是运行中的程序,线程是进程的内部的一个执行序列
一个程序至少有一个进程,一个进程至少有一个线程.
进程是资源分配最小单位,线程是系统调度的最小单位
进程间不会相互影响,一个线程挂掉将导致整个进程挂掉
同一线程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的
进程间切换代价大,线程间切换代价小
同一进程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的

同步代码块能发生CPU的切换吗 (能)

创建线程的方式有哪些

run()和start()区别

run( ):只是调⽤普通run⽅法
start( ):启动了线程,由Jvm调⽤run⽅法

调用 start() 方法是用来启动线程的,轮到该线程执行时,会自动调用 run();直接调用 run() 方法,无法达到启动多线程的目的,相当于主线程线性执行 Thread 对象的 run() 方法。
一个线程的 start() 方法只能调用一次,多次调用会抛出 java.lang.IllegalThreadStateException 异常;run() 方法没有限制

以下哪个事件会导致线程销毁?(D)

A. 调用方法sleep()
B. 调用方法wait()
C. start()方法的执行结束
D.run()方法的执行结束

说⼀下ThreadLocal

  1. ThreadLocal是Java中所提供的线程本地存储机制,可以利⽤该机制将数据缓存在某个线程内部,该线程可以在任意时刻、任意⽅法中获取缓存的数据
  2. ThreadLocal底层是通过ThreadLocalMap来实现的,每个Thread对象(注意不是ThreadLocal
    对象)中都存在⼀个ThreadLocalMap,Map的key为ThreadLocal对象,Map的value为需要缓
    存的值
  3. 如果在线程池中使⽤ThreadLocal会造成内存泄漏,因为当ThreadLocal对象使⽤完之后,应该
    要把设置的key,value,也就是Entry对象进⾏回收,但线程池中的线程不会回收,⽽线程对象是通过强引⽤指向ThreadLocalMap,ThreadLocalMap也是通过强引⽤指向Entry对象,线程不被回收,Entry对象也就不会被回收,从⽽出现内存泄漏,解决办法是,在使⽤了ThreadLocal对象之后,⼿动调⽤ThreadLocal的remove⽅法,⼿动清除Entry对象
  4. ThreadLocal经典的应⽤场景就是连接管理(⼀个线程持有⼀个连接,该连接对象可以在不同的⽅法之间进⾏传递,线程之间不共享同⼀个连接)

java8中,下面哪个类用到了解决哈希冲突的开放定址法©

A.LinkedHashSet
B.HashMap
C.ThreadLocal
D.TreeMap

threadlocalmap使用开放定址法解决haah冲突,hashmap使用链地址法解决hash冲突

下列程序的运行结果

public static void main(String args[]) {
    
    
   Thread t = new Thread() {
    
    
            public void run() {
    
    
                pong();
            }
        };
        t.run();
        System.out.print("ping");
    }
    static void pong() {
    
    
        System.out.print("pong");
    }
pongping

这里需要注意Thread的start和run方法

用start方法才能真正启动线程,此时线程会处于就绪状态,一旦得到时间片,则会调用线程的run方法进入运行状态。

而run方法只是普通方法,如果直接调用run方法,程序只会按照顺序执行主线程这一个线程

当编译并运行下面程序时会发生什么结果( D)

public class Bground extends Thread{
    
    
    public static void main(String argv[]){
    
    
        Bground b = new Bground();
        b.run();
    }
    public void start(){
    
    
        for(int i=0;i<10;i++){
    
    
            System.out.println("Value of i = "+i);
        }
    }
}

A.编译错误,指明run方法没有定义
B.运行错误,只鞥呢run方法没有定义
C.编译通过并输出0到9
D.编译通过,但无输出

对于线程而言,start是让线程从new变成runnable。run方法才是执行体的入口。
但是在Thread中,run方法是个空方法,没有具体实现。
Bground继承了Thread,但是没有重写run方法,那么调用run方法肯定是无输出

下列代码执行结果为()

public static void main(String args[])throws InterruptedException{
    
    
	    	Thread t=new Thread(new Runnable() {
    
    
				public void run() {
    
    
					try {
    
    
						Thread.sleep(2000);
					} catch (InterruptedException e) {
    
    
						throw new RuntimeException(e);
					}
					System.out.print("2");
				}
			});
	    	t.start();
	    	
	    	t.join();
	    	System.out.print("1");
	    }

A.21
B.12
C.可能为12,也可能为21
D.以上答案都不对

因为子线程的休眠时间太长,因此主线程很有可能在子线程之前结束也就是输出结果是12,但是子线程用了join函数,因此主线程必须等待子线程执行完毕才结束因此输出结果只能是21

Executor,ExecutorService和Executors的区别

  • Executor 接口对象能执行我们的线程任务;
  • Executors 工具类的不同方法按照我们的需求创建了不同的线程池,来满足业务的需求。
  • ExecutorService 接口继承了Executor接口并进行了扩展,提供了更多的方法,我们能够获得任务执行的状态并且可以获取任务的返回值

在java中守护线程和用户线程的区别

java中的线程分为两种守护线程(Daemon)和用户线程(user)
守护线程通过调用Thread.setDaemon(true)设置
一般程序使用的都是用户线程
守护线程我们一般用不上,比如垃圾回收线程就是守护线程(Daemon)
使用守护线程的注意点

  • Thread.setDaemon()必须在Thread.start()之前调用,否则运行时会抛出异常
  • 守护线程是为其他线程提供服务的,如果全部的用户线程已结束,守护线程没有可服务的线程,JVM关闭

什么是多线程中的上下文切换

CPU时间片:CPU时间片是CPU分配给每个线程执行的时间段,一般为几十毫秒
上下文切换:当前任务执行一个时间片后会切换到下一个任务。但是,在切换前会保存上一个任务的状态,以便下次切换回这个任务时,可以再次加载这个任务的状态,从任务保存到再加载的过程就是一次上下文切换。
上下文:在这种切入切出的过程中,操作系统需要保存和恢复响应的进度信息,这个进度信息就是上下文

什么是死锁?死锁的危害

所谓死锁,是指多个进程在运行过程中因争夺资源而造成的一种互相等待的僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进。 因此我们举个例子来描述,如果此时有一个线程A,按照先锁a再获得锁b的的顺序获得锁,而在此同时又有另外一个线程B,按照先锁b再锁a的顺序获得锁。如下图所示:
在这里插入图片描述
也可以用形象的理解在一个狭窄的道路上(只能由一辆车过去),两辆车都不倒退,退一步,就一直卡在哪里都想前进。

危害

  • 死锁会使进程得不到正确的结果
  • 死锁会使资源的利用率降低
  • 死锁还会导致产生新的死锁

死锁产生的4个必要条件

产生死锁的必要条件:

  • 互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。
  • 请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
  • 不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
  • 环路等待条件:在发生死锁时,必然存在一个进程–资源的环形链。

异常

final、finally、finalize 的区别?

  • final:用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,被其修饰的类不可继承
  • finally:异常处理语句结构的一部分,表示一定会执行
  • finalize:Object 类的一个方法,在垃圾回收器执行的时候会调用被回收对象的此方法,可以覆盖此方法
    提供垃圾收集时的其他资源回收,例如关闭文件等。该方法更像是一个对象生命周期的临终方法,当该方法
    被系统调用则代表该对象即将“死亡”,但是需要注意的是,我们主动行为上去调用该方法并不会导致该对
    象“死亡”,这是一个被动的方法(其实就是回调方法),不需要我们调用

getCustomerInfo()方法如下,try中可以捕获三种类型的异常,如果在该方法运行中产生了一个IOException,将会输出什么结果()

    public void getCustomerInfo() {
    
    

        try {
    
    

            // do something that may cause an Exception

        } catch (java.io.FileNotFoundException ex) {
    
    

            System.out.print("FileNotFoundException!");

        } catch (java.io.IOException ex) {
    
    

            System.out.print("IOException!");

        } catch (java.lang.Exception ex) {
    
    

            System.out.print("Exception!");

        }

    }

A IOException!

B IOException!Exception!

C FileNotFoundException!IOException!

D FileNotFoundException!IOException!Exception!

正确答案是 A

考察多个catch语句块的执行顺序。当用多个catch语句时,catch语句块在次序上有先后之分。从最前面的catch语句
块依次先后进行异常类型匹配,这样如果父异常在子异常类之前,那么首先匹配的将是父异常类,子异常类将不会获
得匹配的机会,也即子异常类型所在的catch语句块将是不可到达的语句。所以,一般将父类异常类即Exception老大
放在catch语句块的最后一个

Throw 和 throws 的区别

  1. throws 用在函数上,后面跟的是异常类,可以跟多个;而 throw 用在函数内,后面跟的是异常对象。
  2. throws 表示出现异常的一种可能性,并不一定会发生这些异常;throw 则是抛出了异常,
    执行 throw 则一定抛出了某种异常对象
  3. 两者都是消极处理异常的方式,只是抛出或者可能抛出异常,但是不会由函数去处理异
    常,真正的处理异常由函数的上层调用处理

AccessViolationException异常触发后,下列程序的输出结果为( A )

static void Main(string[] args)  
{
    
      
    try  
    {
    
      
        throw new AccessViolationException();  
        Console.WriteLine("error1");  
    }  
    catch (Exception e)  
    {
    
      
        Console.WriteLine("error2");  
    }  
    Console.WriteLine("error3");  
} 

A. error2 error3
B. error3
C.error2
D.error1

try里面如果抛出的异常被catch捕获,依旧可以执行trycatch外面的语句,并且抛出异常后面的代码不会执行,直接执行catch里面,如果有finally{}代码无论有没有出现异常都会执行

以下代码执行的结果显示是多少()?

public class Demo{
    
    
    public static void main(String[] args){
    
    
        System.out.print(getNumber(0));
        System.out.print(getNumber(1));
        System.out.print(getNumber(2));
        System.out.print(getNumber(4));
    }

    public static int getNumber(int num){
    
    
        try{
    
    
            int result = 2 / num;
            return result;
        }catch (Exception exception){
    
    
            return 0;
        }finally{
    
    
            if(num == 0){
    
    
                return -1;
            }
            if(num == 1){
    
    
                return 1;
            }
        }
    }    
}

A. 0110
B.-1110
C.0211
D.-1211

finally一定会在return之前执行,但是如果finally使用了return或者throw语句,将会使trycatch中的return或者throw失效

给定以下JAVA代码,这段代码运行后输出的结果是()

public class Test
{
    
      
    public static int aMethod(int i)throws Exception
    {
    
    
        try{
    
    
            return i/10;
        }
        catch (Exception ex)
        {
    
    
            throw new Exception("exception in a aMethod");
        }finally{
    
    
      System.out.printf("finally");
        }
} 
    public static void main(String[] args){
    
    
        try
        {
    
    
            aMethod(0);
        }
        catch (Exception ex)
        {
    
    
            System.out.printf("exception in main");
        }
        System.out.printf("finished");
    }
}

finallyfinished
1、先进入main函数,进入try块调用aMethod(0)方法;
2、执行aMethod()方法的try块,i/10可以正确执行,故并未抛出异常,catch块不执行,而需要执行finally(该块任何时候都要执行),故打印finally;
3、回到main函数,由于aMethod()调用成功,因此main函数的catch块同样不执行,顺序执行finally块,打印finished
因此,最终的输出结果就是:finally finished

try括号里有return语句, finally执行顺序

return前执行

反射

什么是反射

Java中的反射机制是指在运行状态中,对于任意一个类,能够动态获取这个类中的属性和方法;对于任意一个对象,都能够任意调用它的属性和方法。这种动态获取类的信息以及动态调用对象方法的功能称为Java的反射机制。总结就是:反射可以实现运行时知道任意一个类的属性和方法。

我们.java文件在编译后会变成.class文件,这就像是个镜面,本身是.java,在镜中是.class,他们其实是一样的;那么同理,我们看到镜子的反射是.class,就能通过反编译,了解到.java文件的本来面目。即为反射。

反射是否破坏了封装性

封装性是指对外隐藏对象的属性和实现细节,仅对外提供公共的访问方式。反射是通过对象找到类,既然找到类了,那么我们就可以得到这个类的成员结构了,例如这个类的属性和方法,即使是private的也能得到,你想,现在这个类我都得到了,那么这个类中的所以东西我肯定是都得到了,我现在只是得到了这个类的成员,并没有说是在外部访问这个类的private的东西。这并没有破坏面向对象的封装性

打个比喻,核武器不是用来炸的,而是用来吓人的,但是当对象封装不合理的的时候,迫不得已还是要用的,就想上面所说,如果封装性不好的话,就可以用反射的来直接访问私有变量,比如类的私有变量没有相应的set方法,就可以暴力一下

JVM

常用命令

1、jps:查看本机java进程信息。

2、jstack:打印线程的栈信息,制作线程dump文件。

3、jmap:打印内存映射,制作堆dump文件

4、jstat:性能监控工具

5、jhat:内存分析工具

6、jconsole:简易的可视化控制台

7、ClassLoader如何加载classjvisualvm:功能强大的控制台

ClassLoader如何加载class

JVM⾥有多个类加载,每个类加载可以负责加载特定位置的类,例如,bootstrap类加载负责加载jre/lib/rt.jar中的类, 我们平时⽤的jdk中的类都位于rt.jar中。extclassloader负责加载jar/lib/ext/*.jar中的类,appclassloader负责classpath指定的⽬录或jar中的类。除了bootstrap之外,其他的类加载器本身也都是java类,它们的⽗类是ClassLoader。

JVM中可以运行多种语言吗

jvm不仅可以跨平台,还可以跨语言,只要遵循字节码规范,JVM只识别字节码,所以JVM其实是跟语言是解耦的,没有直接关联,像Scala、Groovy等语言都可以在JVM上运行

JVM有哪些内存区域

线程私有区可以分为:虚拟机栈、本地方法栈和程序计数器
线程共享区分化为方法区和堆

虚拟机栈:在JVM运行过程中存储当前线程运行方法所需要的数据、指令和返回地址
本地方法栈:他服务的对象是native方法,调用的是本地库,c/c++编写的
程序计数器:主要用来记录各个线程执行的字节码地址
方法区:JDK1.7及以前“永久代”,JDK1.8及以后“元空间”,存放类信息、常量池,方法数据、方法代码
:堆是JVM上最大的内存区域,几乎所有的对象都在堆中存储,jvm调优一般就是调堆

JVM在创建对象是采用了哪些并发安全机制

JVM创建对象的过程:类加载、检查加载、分配内存、内存空间初始化、设置、对象初始化
划分内存的方式:指针碰撞、空闲列表
解决并发安全:CAS+失败重试(使用乐观锁的机制,有一定的内存开销)、本地线程分配缓冲

本地线程分配缓冲

每个线程在java堆中预先分配一小块私有内存,也就是本地线程分配缓冲,这样没有线程都独立拥有一个buffer,如果需要分配内存,就在自己的buffer上分配,这样就不存在竞争的情况,可以大大提升分配效率
在这里插入图片描述

什么是对象头?对象头里面有哪些东西

在这里插入图片描述
对象填充必须是8字节的整数,如果对象头+实例数据没有达到8字节的数据,就要用对象填充到8字节

实例对象是怎样存储的

实例对象存放在堆区,对实例的引用存在线程栈上,而实例的元数据存在方法区或者元空间,如果实例对象没有发生线程逃逸行为,就会被存储在线程栈中

如何判断对象是否存活

引用计数法

原理:实际上是通过在对象头中分配一个空间来保存该对象被引用的次数。如果该对象被其它对象引用,则它的引用计数+1,如果删除对该对象的引用,那么它的引用计数就-1,当该对象的引用计数为0时,那么该对象就会被回收。

GC的时候会将计数器为0的对象C给销毁.

引用计数法无法解决循环引用的问题

循环依赖问题:
A a = new A()
B b = new B()
a.x=b
b.x=a
a=null
b=null
很难判断 然后 怎么去标记为0 去回收

根搜索算法

根搜索算法。它的处理方式就是,设立若干种根对象,当任何一个根对象到某一个对象均不可达时,则认为这个对象是可以被回收的。

在这里插入图片描述
ObjectD和ObjectE是互相关联的,但是由于GC roots到这两个对象不可达,所以最终D和E还是会被当做GC的对象,上图若是采用引用计数法,则A-E五个对象都不会被回收。

说到GC roots(GC根),在JAVA语言中,可以当做GC roots的对象有以下几种:

     1、虚拟机栈中的引用的对象。
     2、方法区中的类静态属性引用的对象。
     3、方法区中的常量引用的对象。
     4、本地方法栈中JNI的引用的对象。
     第一和第四种都是指的方法的本地变量表,第二种表达的意思比较清晰,第三种主要指的是声明为final的常量值。

JVM中哪些内存区域会发生内存溢出(OOM)

  • 栈溢出
  • 堆溢出
  • 方法区溢出
  • 本机直接内存溢出
    唯一不会发生OOM的就是程序计数器

为什么不要使用Finalize方法

一个对象要被回收,需要经过两次过程,一次是没有找到GCRoots的引用链,它将被第一次标记,随后进行一次筛选(如果对象覆盖了finalize),我们可以在finalize方法中去拯救(变为存活对象)

finalize方法执行的优先级很低(需要等待),往往调用了也可能不成功,可以采用线程休眠的方式让finalize方法生效
finalize方法只执行一次

复制算法

GC 复制算法是利用 From 空间进行分配的。当 From 空间被完全占满时,GC 会将存活对象全部复制到 To 空间,并且年龄加一。当复制完成后,该算法会把 From 空间和 To 空间互换,GC 也就结束了。From 空间和 To 空间大小必须一致。这是为了保证能把 From 空间中的所有活动对象都收纳到 To 空间里

在这里插入图片描述
在这里插入图片描述

  • 不适用于存活对象较多的场合,如老年代(复制算法适合做新生代的GC)

  • 幸存区from和幸存区to中谁空谁是to,我们会将to中的数据复制到from中保持to中数据为空;

  • from和to区实际上为逻辑上的概念,保证to区一直空;

  • 默认对象经过15次GC后还没有被销毁就会进入养老区

流程:

将Eden区进行GC存活对象放入空的to区,将from区存活的放到空的to区

此时from区为空变成了to区,to区有数据变为from区

经过15次GCfrom区还存活的对象会被移动到养老区

好处:没有内存碎片,效率高
坏处:浪费内存空间,多了一半to空间永远是空的。
复制算法最佳使用场景:对象存活度较低的时候 -> 新生区 (如果存活度较高,则from区空间全部被占满导致会将全部内容复制到to区)

标记清除算法

标记-清除算法将垃圾回收分为两个阶段:标记阶段和清除阶段。一种可行的实现是,在标记阶段,首先通过根节点,标记所有从根节点开始的可达对象。因此,未被标记的对象就是未被引用的垃圾对象;然后,在清除阶段,清除所有未被标记的对象。

它的做法是当堆中的有效内存空间(available memory)被耗尽的时候,就会停止整个程序(也被成为stop the world),然后进行两项工作,第一项则是标记,第二项则是清除。

需要两次扫描,第一次扫描标记存活对象,第二次扫描清除没有被标记的对象
在这里插入图片描述
优点:不需要额外的空间

缺点:两次扫描严重浪费时间,并且还会产生内存碎片,(内存碎片会导致明明有空间,但是无法存储大对象)

标记-整理

标记整理算法适合用于存活对象较多的场合,如老年代。它在标记-清除算法的基础上做了一些优化。和标记-清除算法一样,标记-压缩算法也首先需要从根节点开始,对所有可达对象做一次标记;但之后,它并不简单的清理未标记的对象,而是将所有的存活对象压缩到内存的一端;之后,清理边界外所有的空间。

在这里插入图片描述
优点 不会产生内存碎片
缺点 效率低

为什么扩容新生代可以提高GC的效率

在这里插入图片描述

简述一下CMS垃圾回收器,他有哪些问题

在这里插入图片描述
采用多线程和标记清除算法,在初始标记和重新标记会发生stw

  • 初始标记: 暂停所有的其他线程(STW),并记录gc roots直接能引⽤的对象。在这个过程中,虽然会触发STW机制,但是时间会非常的短
  • 并发标记:从GC Roots的直接关联对象开始遍历整个对象图的过程, 这个过程耗时较⻓但是不需要STW,可以与垃圾收集线程⼀起并发运⾏。这个过程中,⽤户线程和GC线程并发,可能会有导致已经标记过的对象状态发⽣改变。
  • 重新标记:为了修正并发标记期间因为⽤户程序继续运⾏⽽导致标记产⽣变动的那⼀部分对象的标记记录,这个阶段的停顿时间⼀般会⽐初始标记阶段的时间稍⻓,远远⽐并发标记阶段时间短。主要⽤到三⾊标记⾥的算法做重新标记。
  • 并发清理:开启⽤户线程,同时GC线程开始对未标记的区域做清扫。这个阶段如果有新增对象会被标记为⿊⾊不做任何处理。
  • 并发重置:重置本次GC过程中的标记数据。

CMS需要注意的问题

  1. 消耗cpu资源多
  2. 无法处理浮动的垃圾,并发清除的时候,用户线程也在运行,还是会产生垃圾,这就是浮动垃圾,浮动垃圾只有在执行下一次垃圾回收的时候才会被真正回收掉
  3. 会产生大量的空间碎片CMS是基于标记-清除算法的,只会将标记为为存活的对象删除,并不会移动对象整理内存空间,会造成内存碎片(会发生垃圾回收器退化为serialOld 单线程的垃圾回收标记清除算法)

以下哪个区域不属于新生代?(C)

A.eden区
B.from区
C.元数据区
D.to区

java程序内存泄露的最直接表现是( C)

A.频繁FullGc
B.jvm崩溃
C.程序抛内存溢出的Exception
D.java进程异常消失

数据库

mysql

char和varchar的区别

区别一,定长和变长
char 表示定长,长度固定,varchar表示变长,即长度可变。char如果插入的长度小于定义长度时,则用空格填充;
varchar小于定义长度时,还是按实际长度存储,插入多长就存多长。

因为其长度固定,char的存取速度还是要比varchar要快得多,方便程序的存储与查找;但是char也为此付出的是空
间的代价,因为其长度固定,所以会占据多余的空间,可谓是以空间换取时间效率。varchar则刚好相反,以时间换
空间。

区别之二,存储的容量不同
对 char 来说,最多能存放的字符个数 255,和编码无关。
而 varchar 呢,最多能存放 65532 个字符。varchar的最大有效长度由最大行大小和使用的字符集确定。整体最大
长度是 65,532字节。

数据库的隔离级别有哪些?各自的含义是什么

脏读:指当一个事务正在访问数据,并且对数据进行了修改,而这种数据还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据还没有提交那么另外一个事务读取到的这个数据我们称之为脏数据。依据脏数据所做的操作肯能是不正确的。

简单来说就是:脏读是指一个事务在处理数据的过程中,读取到另一个为提交事务的数据
不可重复读:指在一个事务内,多次读同一数据。在这个事务还没有执行结束,另外一个事务也访问该同一数据,那么在第一个事务中的两次读取数据之间,由于第二个事务的修改第一个事务两次读到的数据可能是不一样的,这样就发生了在一个事物内两次连续读到的数据是不一样的,这种情况被称为是不可重复读。

简单来说就是:不可重复读是指对于数据库中的某个数据,一个事务范围内的多次查询却返回了不同的结果,这是由于在查询过程中,数据被另外一个事务修改并提交了

幻象读:一个事务先后读取一个范围的记录,但两次读取的纪录数不同,我们称之为幻象读(两次执行同一条 select 语句会出现不同的结果,第二次读会增加一数据行,并没有说这两次执行是在同一个事务中)

不可重复读和脏读的区别是,脏读读取到的是一个未提交的数据,而不可重复读读取到的是前一个事务提交的数据。

而不可重复读在一些情况也并不影响数据的正确性,比如需要多次查询的数据也是要以最后一次查询到的数据为主

事务管理(ACID)

原子性(Atomicity)

原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
一致性(Consistency)
事务前后数据的完整性必须保持一致。
隔离性(Isolation)
事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。
持久性(Durability)
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响

银行转账举例

原子性

针对同一个事务

这个过程包含两个步骤

A: 800 - 200 = 600
B: 200 + 200 = 400

原子性表示,这两个步骤一起成功,或者一起失败,不能只发生其中一个动作

一致性(Consistency)

针对一个事务操作前与操作后的状态一致
操作前A:800,B:200
操作后A:600,B:400

一致性表示事务完成后,符合逻辑运算

持久性(Durability)

表示事务结束后的数据不随着外界原因导致数据丢失

操作前A:800,B:200
操作后A:600,B:400
如果在操作前(事务还没有提交)服务器宕机或者断电,那么重启数据库以后,数据状态应该为
A:800,B:200
如果在操作后(事务已经提交)服务器宕机或者断电,那么重启数据库以后,数据状态应该为
A:600,B:400

事务的四个特性以及对应的子系统:

(1)原子性(A):安全性管理子系统;
(2)一致性(C):完整性管理子系统;
(3)隔离性(I):并发控制子系统;
(4)持久性(D):恢复管理子系统;

javaweb

get请求和post请求的区别

1,GET在浏览器回退是无害的,而POST会再次提交请求
2,GET请求只能进行url编码,而post支持多种编码形式
3,get产生一个tcp数据包,post产生两个tcp数据包
4,get请求参数会被完整保留在浏览器历史记录里,而post中的参数不会被保留
5,get请求在url中传递的参数是有长度限制的不超过4k,而post没有
6,对参数的数据类型,get只接受ASCII类型,而post没有限制
7,get比post更不安全,因为参数直接暴露在url上,所以不能用传递敏感信息
8,get参数通过url传递,post放在request body报文体中
9,在进行文件上传时只能使用post而不能是get

转发和重定向

转发是服务器行为
转发浏览器只做了一次访问请求
转发浏览器的地址栏不发生变化
转发两次跳转之间的传输信息不会丢失,所以可以通过request进行数据的传递
转发只能将请求转发给同一个Web应用的组件

重定向是客户端行为
重定向是浏览器至少做了两次的访问请求
重定向浏览器地址改变
重定向两次跳转之间传输的信息会丢失(不能使用request范围)
重定向可以指向任何的资源,包括当前应用程序中的其他资源,同一个站点上的其他应用程序中的资源,其他站点资源

Cookie和Session的区别

  1. 存储位置不同
  • cookie的数据信息存放在客户端浏览器上。
  • session的数据信息存放在服务器上。
  1. 存储容量大小不同
  • 单个cookie保存的数据<=4KB,一个站点最多保存20个Cookie。
  • 对于session来说并没有上限,但出于对服务器端的性能考虑,session内不要存放过多的东西,并且设置session删除机制
  1. 存储方式不同
  • cookie中只能保管ASCII字符串,并需要通过编码方式存储为Unicode字符或者二进制数据。
  • session中能够存储任何类型的数据,包括且不限于string,integer,list,map等。
  1. 隐私策略不同
  • cookie对客户端是可见的,别有用心的人可以分析存放在本地的cookie并进行cookie欺骗,所以它是不安全的。
  • session存储在服务器上,对客户端是透明对,不存在敏感信息泄漏的风险。
  1. 跨域支持上不同
  • cookie支持跨域名访问。
  • session不支持跨域名访问。

Servlet什么时候被创建?

1.默认情况下,第一次被访问时,Servlet被创建


2.可以配置执行Servlet的创建时机。
在<servlet>标签下配置
(1)第一次被访问时,创建
   <load-on-startup>的值为负数或不写
(2)在服务器启动时,创建
  <load-on-startup>的值为正整数

下列有关Servlet的生命周期,说法不正确的是?(A)

A.在创建自己的Servlet时候,应该在初始化方法init()方法中创建Servlet实例
B.在Servlet生命周期的服务阶段,执行service()方法,根据用户请求的方法,执行相应的doGet()或是doPost()方法
C.在销毁阶段,执行destroy()方法后会释放Servlet 占用的资源
D. destroy()方法仅执行一次,即在服务器停止且卸载Servlet时执行该方法

Servlet的生命周期分为5个阶段:加载、创建、初始化、处理客户请求、卸载。
(1)加载:容器通过类加载器使用servlet类对应的文件加载servlet
(2)创建:通过调用servlet构造函数创建一个servlet对象
(3)初始化:调用init方法初始化
(4)处理客户请求:每当有一个客户请求,容器会创建一个线程来处理客户请求
(5)卸载:调用destroy方法让servlet自己释放其占用的资源

创建Servlet的实例是由Servlet容器来完成的,且创建Servlet实例是在初始化方法init()之前

spring

srping是什么?

spring是一个轻量级开源的容器框架,通常spring指的是spring franework,spring也是一个生态,可以构建java应用所需的一切基础设施
spring是一个ioc和aop的容器框架
ioc 控制反转
aop 面向切面
容器:包含并管理应用对象的生命周期
spring是为了解决企业级应用开发的业务逻辑层和其他层对象和对象直接耦合问题
通过控制反转IOC达到解耦的目的

包含管理对象的配置和生命周期,这个意义上是一个容器
将简单的组件配置,组合成为复杂的应用

spring优点

spring属于低侵入式设计,代码的污染极低;
spring的DI机制将对象之间的依赖关系交由框架处理,减低组件的耦合性;
Spring提供了AOP技术,支持将一些通用任务,如安全、事务、日志、权限等进行集中式管理,减少重复代码从而提供更好的复用。
spring对于主流的应用框架提供了集成支持

缺点是 简化开发,如果想要深入底层去了解就非常困难,(上层使用越简单,顶层封装的就越复杂)
源码缺点,由于spring大而全,代码非常庞大,一百多万对深入学习源码带了了一定困难

Spring 中的设计模式

什么是IOC、DI 及其两者的优点 、 有哪几种注入方式

IOC:控制反转,把创建对象的控制权利由代码转移到spring容器由工厂统一推送。并由Spring根据配置文件去创建实例和管理各个实例之间的依赖关系,以前创建对象的主动权和时机都是由自己把控的,IOC让对象的创建不用去new了,可以由spring工厂自动生产,使用java的反射机制,根据配置文件在运行时动态的去创建对象以及管理对象,并调用对象的方法的。

通俗讲,UserService service=new UserService();//耦合度太高,维护不方便
引入ioc,就将创建对象的控制权交给spring容器,以前由程序员自己控制对象的创建,现在交给spring的ioc去创建,在运行时动态的去创建对象以及管理对象,要使用对象只需要通过依赖注入即可

DI:依赖注入,在程序运行期间,由外部容器动态地将依赖对象注入到组件中。简单定义就是当一个对象需要另一个对象时,可以把另一个对象注入到对象中去。依赖注入是实现IOC的方法,就是由IOC容器在运行期间,动态的将某种依赖关系注入到对象中

优点就是把应用的代码量降到最低,达到松散耦合度。集中管理对象,方便维护

注入的方式

构造注入
Set注入
自动注入

ioc的实现机制是什么

工厂模式+反射

AOP的理解

aop将程序中的交叉业务逻辑(比如安全,日志,事务等)封装成一个切面,然后注入到目标对象(具体的业务逻辑)中去,aop可以对某个对象或某些对象的功能进行增强,比如对象中的某些方法进行增强,可以在某些方法之前额外做一些事情,在某个方法执行之后额外的做一些事情

Spring 中的设计模式

  • 单例模式——spring 中两种代理方式,
    单例模式——在 spring 的配置文件中设置 bean 默认为单例模式
  • 模板方式模式——用来解决代码重复的问题
    比如:RestTemplate、JdbcTemplate
  • 工厂模式——在工厂模式中
    我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用同一个接口来指向新创建的对象。Spring 中使用 beanFactory 来创建对象的实例
  • 代理模式
    aop的实现,若目标对象实现了若干接口,spring 使用 jdk 的 java.lang.reflect.Proxy类代理。若目标兑现没有实现任何接口,spring 使用 CGLIB 库生成目标类的子类
  • 观察者模式
    spring的事件驱动模型使用的是 观察者模式 ,Spring中Observer模式常用的地方是listener的实现,监听
  • 包装器设计模式
    我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源
  • 适配器模式
    Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配Controller

SpringMVC

SpringMVC 的工作原理

在这里插入图片描述

  • 1.用户向服务器发送请求,请求被 springMVC 前端控制器 DispatchServlet 捕获
  • 2.DispatcherServle 对请求 URL 进行解析,得到请求资源标识符(URL),去查找处理器(Handler)
  • 3.然后根据该 URL 调用 HandlerMapping将请求映射到处理器 HandlerExcutionChain
  • 4.DispatchServlet 根据获得 Handler 选择一个合适的 HandlerAdapter 适配器处理
  • 5.处理器适配器去执行Handler
  • 6… Handler 对数据处理完成以后将返回一个 ModelAndView()对象给 适配器
  • 7.处理器适配器向前端控制器DisPatchServlet;返回ModelAndView
  • 8.Handler 返回的 ModelAndView()只是一个逻辑视图并不是一个正式的视图,DispatcherSevlet 通过ViewResolver 试图解析器将逻辑视图转化为真正的视图 View;
  • 9.DispatcherServlet 通过 model 解析出 ModelAndView()中的参数进行解析最终展现出完整的 view 并返回给客
  • 10.前端控制器对视图进行渲染
  • 11.前端控制器向用户响应结果

SpringMVC 常用注解都有哪些?

@RequestMapping
用于映射请求地址,类上跟方法上都可以使用此注解,当同时使用的时候,url匹配需要同时组合匹配。

@RequestParam
用于将指定的请求参数赋值给方法中的形参,当提交的请求参数中的key跟方法中的形参命名一致的话,则不需要使用此注解,会自动将请求中的参数封装到形参中。如果2者命名不一致的话,则需要通过RequestParam来表示映射关系

@ModelAttribute
最主要的作用是将数据添加到模型对象中,用于视图页面展示时使用

@SessionAttributes
可以将Model中的属性同步到session当中

RequestBody
用于接收请求体中的json串(如ajax请求的data参数)可在直接接收并封装到bean中。

ResponseBody
注解实现将 controller 方法返回对象转化为 json 响应给客户

@PathVariable
接收请求路径中占位符的值,实现rest风格的路径收参

如何开启注解处理器和适配器

我们在项目中一般会在 springmvc.xml 中通过开启 mvc:annotation-driven来实现注解处理器和适配器的开启

springboot

描述一下SpringBoot的作用

Spring Boot有哪些优点

1. 减少开发时间,通过提供默认值快速开发
2. 使用javaConfig有利于避免使用XML,无代码生成,开箱即用
3. 避免大量的Maven导入和各种版本冲突
4. 不需要单独的服务器,SpringBOOT工程在部署时,采用的是jar包的方式,内部自动依赖Tomcat容器,提供了多环境配置
5. SpringBOOT中默认整合第三方框架时,只需要导入响应的starter依赖包,就会自动整合了

Linux

统计一个文件中"java"出现的行数?

// grep 要查找的单词  文件名 | wc -l
grep "java" abc.txt | wc -l

wc -l # 统计行数
wc -w # 统计单词数量

内存磁盘相关命令

du(disk usage) 命令功能说明:统计目录(或文件)所占磁盘空间的大小
df(disk file) 命令功能说明: 用于显示文件系统的磁盘使用情况
free 命令功能说明: 可以显示当前系统未使用的和已使用的内存数目,还可以显示被内核使用的内存缓冲区
vmstat 命令功能说明: 命令报告关于内核线程、虚拟内存、磁盘、陷阱和 CPU 活动的统计信息

vim编辑器可以分为三种模式

1.命令模式:控制屏幕光标的移动,进行文本的删除,复制等文字编辑工作,不使用【del】和【backspace】键,以及进入插入模式或者回到底行模式;
2.插入模式:只有在插入模式下,才可以输入文字,按【esc】可以回到命令模式,vim编辑器一打开是不可以输入的,因为刚打开时候处于命令模式;
3.底行模式:保存文件或者退出vim,也可以设置编辑环境和一些编译工作

在使用VI编辑器的时候,查找内容的方法有两种:

1、“/”:这个查找是自上而下
2、“?”:这个查找是自下而上
针对“/”:使用“n”查找下一个,使用“N”查找上一个

实现文件去重并排序:

sort demo.txt|uniq

压缩相关命令

tar是操作.tar的命令
gzip是压缩.gz压缩包的命令
compress:压缩.Z文件
uncompress:解压缩.Z文件

猜你喜欢

转载自blog.csdn.net/qq_44866153/article/details/115101533