java面试

java面试

01_谈谈对面向对象思想的理解

这个问题,通常会让很多人有点不知所措,感觉我一直在编码,但是说到思想很难去阐述。

下面,我说说自己的想法,

首先,谈谈“面向过程”vs“面向对象”

我觉得这两者是思考角度的差异,面向过程更多是以“执行者”的角度来思考问题,而面向对象更多是以“组织者”的角度来思考问题,举个例子,比如我要产生一个0-10之间的随机数,如果以“面向过程”的思维,那我更多是关注如何去设计一个算法,然后保证比较均衡产生0-10的随机数,而面向对象的思维会更多关注,我找谁来帮我们做这件事,比如Random类,调用其中提供的方法即可。

所以,面向对象的思维更多的是考虑如何去选择合适的工具,然后组织到一起干一件事。

好比一个导演,要拍一场电影,那么首先要有男猪脚和女猪脚,然后还有其他等等,最后把这些资源组织起来,拍成一场电影。

再说回我们的程序世界,这个组织者的思维无处不在,比如,我们要开发项目,以三层架构的模式来开发,那么这个时候,我们不需要重复造轮子,只需要选择市面上主流的框架即可,比如SpringMVC,Spring,MyBatis,这些都是各层的主流框架。

JDK,JRE,JVM有什么区别?

JDK:Java Development Kit,Java开发工具包,提供了Java的开发环境和运行环境。包含了编译Java源文件的编译器Javac,还有调试和分析的工具。

JRE:Java Runtime Environment,Java运行环境,包含Java虚拟机及一些基础类库

JVM:Java Virtual Machine,Java虚拟机,提供执行字节码文件的能力

所以,如果只是运行Java程序,只需要安装JRE即可。

另外注意,JVM是实现Java跨平台的核心,但JVM本身并不是跨平台的,

不同的平台需要安装不同的JVM

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h8TBD4aT-1631606888922)(…/images/v2-199da3b71771a2ac8a6abca871d2bca2_720w.jpg)]

浅谈Java常量池

一、概述
  • 常量池:编译期被确定,*.class文件中的一部分,包含字面量(Literal)和符号引用(Symbolic Reference)。
  • 字面量:文本字符串、声明为final的常量值(int / long / double…)等。
  • 符号引用:类和接口的完全限定名(Fully Qualified Name)、字段的名称和描述符(Descriptor)、方法的名称和描述符。
  • 运行时常量池:方法区的一部分,jvm在完成类装载操作后,将class文件中的常量池载入内存并保存在方法区中。
  • JDK1.6之前字符串常量池位于方法区。
    JDK1.7字符串常量池已经被移至堆。
    JDK1.8字符串常量池位移至元空间。
二、字符串常量池

字符串常量池属于运行时常量池的一部分

String s1 = "Hello";
String s2 = "Hello";
String s3 = "Hel" + "lo";
String s4 = "Hel" + new String("lo");
String s5 = new String("Hello");
String s6 = s5.intern();
String s7 = "H";
String s8 = "ello";
String s9 = s7 + s8;
          
System.out.println(s1 == s2);  // true
System.out.println(s1 == s3);  // true
System.out.println(s1 == s4);  // false
System.out.println(s1 == s9);  // false
System.out.println(s4 == s5);  // false
System.out.println(s1 == s6);  // true

java中==比较的是内存地址

  1. s1 == s2 (true),s1、s2赋值时均使用的字符串字面量"Hello",在编译期间,这种字面量会直接放入class文件的常量池中,载入运行时常量池后,s1、s2指向的是同一个内存地址。

  2. s1 == s3 (true),s3虽然是动态拼接出来的字符串,但所有参与拼接的部分都是已知的字面量,在编译期间,这种拼接会被优化,因此String s3 = “Hel” + “lo”;在class文件中被优化成String s3 = “Hello”;,所以s1 == s3成立。

  3. s1 == s4 (false),s4虽然也是拼接出来的,但new String(“lo”)这部分不是已知字面量,编译器不会优化,必须等到运行时才可以确定结果,所以s4指向堆中某个地址

  4. s1 == s9 (false),s9是s7、s8两个变量拼接,都是不可预料的,编译器不作优化,运行时拼接成新字符串存于堆中某个地址。

  5. s4 == s5 (false),二者都在堆中,但地址不同。

  6. s1 == s6 (true),这两个相等完全归功于intern()方法,s5在堆中,内容为"Hello" ,intern方法会尝试将"Hello"字符串添加到常量池中,并返回其在常量池中的地址,因为常量池中已经有了"Hello"字符串,所以intern方法直接返回地址;而s1在编译期就已经指向常量池了,因此s1和s6指向同一地址,相等。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jzwAKopO-1631606888925)(…/images/9643870-b42fcf0c4e6b4918.jpg)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O4ItggZa-1631606888926)(…/images/9643870-d05c492b6e0d506d.png)]

==和equals的区别

== 是判断两个变量或实例是不是指向同一个内存空间,equals() 是判断两个变量或实例所指向的内存空间的值是不是相同

== 是指对内存地址进行比较 , equals() 是对字符串的内容进行比较

== 指引用是否相同, equals() 指的是值是否相同

==.

比较基本的数据类型:比较的是数值.

比较引用类型:比较引用指向的值(地址).

equals

默认比较也是地址,因为这个方法的最初定义在Object上,默认的实现就是比较地址

自定义的类,如果需要比较的是内容,那么就要学String,重写equals方法

代码案例:测试以下的每道题,你是否能够正确得到答案?

String s1 = new String("zs");
String s2 = new String("zs");
System.out.println(s1 == s2);//false
String s3 = "zs";
String s4 = "zs";
System.out.println(s3 == s4);//ture
System.out.println(s3 == s1);//false
String s5 = "zszs";
String s6 = s3+s4;
System.out.println(s5 == s6);//false 因为字符串是不可变的对象,拼接字符串相当于new了一个对象了
final String s7 = "zs";  //使用final了说明是常量,都在常量池
final String s8 = "zs";
String s9 = s7+s8;  //使用final了说明是常量,常量和常量拼接,不会new一个对象
System.out.println(s5 == s9);//ture
final String s10 = s3+s4; //s3+s4依然是new的,s10只能赋值一次
System.out.println(s5 == s10);//false

final的作用

final修饰类,表示类不可变,不可继承: 比如,String,不可变性

final修饰方法,表示该方法不可重写:比如模板方法,可以固定我们的算法

final修饰变量,这个变量就是常量

注意:

修饰的是基本数据类型,这个值本身不能修改

修饰的是引用类型,引用的指向不能修改

比如下面的代码是可以的

final Student student = new Student(1,"Andy");
student.setAge(18);//注意,这个是可以的!

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-csxubhfK-1631606888928)(…/images/image-20210325093939077.png)]

String s = "java"与String s = new String(“java”)

String s = “java”;

String s = new String(“java”);

这两者的内存分配方式是不一样的。

第一种方式,JVM会将其分配到常量池,而第二种方式是分配到堆内存.

String s = new String(“abc”)到底产生几个对象?

​ 栈只是对堆中或常量池中对象的引用,new String(“abc”) 的过程中,会检查常量池中是否有abc,如果没有,在常量池中进行创建,并且在堆中new 一个 string为abc的对象,如果有,只是在堆中new 一个 string为abc的对象。所以可能是一个,也可能是两个.

String,StringBuffer,StringBuilder区别.

什么是线程安全?
一个对象是线程安全的,当通过多线程去访问这个对象的时候,你不需要做额外的操作(额外的同步控制),他依然能表现出正确的数据,就叫做线程安全。

String 跟其他两个类的区别是

String是final类型,每次声明的都是不可变的对象,
所以每次操作都会产生新的String对象,然后将指针指向新的String对象。

StringBuffer,StringBuilder都是在原有对象上进行操作

所以,如果需要经常改变字符串内容,则建议采 用这两者。

StringBuffer vs StringBuilder

前者是线程安全的,后者是线程不安全的。
线程不安全性能更高,所以在开发中,优先采用StringBuilder.
StringBuilder > StringBuffer > String(性能排序)

什么时候。我们会考虑线程安全的问题?

当单个线程去访问资源的时候,不需要去考虑线程安全

只有当多个线程同时访问这个资源的时候,才需要考虑线程安全。(因为会出现边界的问题)(多线程访问同一个资源才需要考虑)

开发中,你用StringBuilder来解决什么问题?

用来解决字符拼接的问题,在方法内写 StringBuilder.append(" ");

StringBuilder sb = new StringBuilder();
sb.append(" ");

每个线程访问一个StringBuilder,不会出现线程安全问题
局部变量每个线程都独享。根本不存在线程安全的问题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r8xPY51b-1631606888930)(…/images/image-20210325101003101.png)]

我们常说的CURD指的是什么?

CRUD是指在做计算处理时的增加(Create)、更新(Update)、读取(Retrieve)和删除(Delete)几个单词的首字母简写,CRUD主要被用在描述软件系统中数据库或者持久层的基本操作功能。

接口和抽象类的区别

这个问题,要分JDK版本来区分回答:

  • JDK1.8之前:

  • 语法:

  • 抽象类:方法可以有抽象的,也可以有非抽象, 有构造器

    接口:方法都是抽象,属性都是常量,默认有public static final修饰.

  • 设计:

  • 抽象类:同一类事物的抽取,比如针对Dao层操作的封装,如,BaseDao,BaseServiceImpl

    接口:通常更像是一种标准的制定,定制系统之间对接的标准

    • 例子:

      1,单体项目,分层开发,interface作为各层之间的纽带,在controller中注入IUserService,在Service注入IUserDao

      2,分布式项目,面向服务的开发,抽取服务service,这个时候,就会产生服务的提供者和服务的消费者两个角色

      这两个角色之间的纽带,依然是接口

  • JDK1.8之后:

  • 接口里面可以有实现的方法,注意要在方法的声明上加上default或者static

最后区分几个概念:

  • 多继承,多重继承,多实现

  • 多重继承:A->B->C(爷孙三代的关系)

  • 多实现:Person implements IRunable,IEatable(符合多项国际化标准)

  • 多继承:接口可以多继承,类只支持单继承

抽象类
定义:
  在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。简单来说,使用关键字 abstract 修饰的类就叫做抽象类。
使用:

abstract class AbstractAnimal {
    public AbstractAnimal() {
        System.out.println("Init AbstractAnimal.");
    }
    static String name = "AbstractAnimal";
    public abstract void eat();
    public void run() {
        System.out.println("AbstractAnimal Run.");
    }
}
class Animal extends AbstractAnimal {
    public static void main(String[] args) {
        AbstractAnimal animal = new Animal();
        animal.run();
        System.out.println(animal.name);
        animal.eat();
    }
  // 必须重写抽象父类方法
    @Override
    public void eat() {
        System.out.println("Animal Eat.");
    }
}

执行的结果:

Init AbstractAnimal.
AbstractAnimal Run.
AbstractAnimal
Animal Eat.

抽象方法:
  使用 abstract 关键字修饰的方法叫做抽象方法,抽象方法仅有声明没有方法体。如下代码:

public abstract void m();

抽象类的特性:

抽象类不能被初始化
抽象类可以有构造方法
抽象类的子类如果为普通类,则必须重写抽象类中的所有抽象方法
抽象类中的方法可以是抽象方法或普通方法
一个类中如果包含了一个抽象方法,这个类必须是抽象类
子类中的抽象方法不能与父类中的抽象方法同名
抽象方法不能为 private、static、final 等关键字修饰
抽象类中可以包含普通成员变量,访问类型可以任意指定,也可以使用静态变量(static)

接口.
定义:
  接口(interface)是抽象类的延伸,它允许一个类可以实现多个接口,弥补了抽象类不能多继承的缺陷,接口是对类的描述,使用 interface 关键字来声明。
使用:

interface IAnimal {
    void run();
}
class AnimalImpl implements IAnimal {
    public static void main(String[] args) {
        IAnimal animal = new AnimalImpl();
        animal.run();
    }
    @Override
    public void run() {
        System.out.println("AnimalImpl Run.");
    }
}

相关面试题
1.抽象类中能不能包含方法体?
答:抽象类中可以包含方法体。抽象类的构成也可以完全是包含方法体的普通方法,只不过这样并不是抽象类最优的使用方式。

题目解析:包含了方法体的抽象类示例代码如下:

abstract class AbstractAnimal {
    public void run() {
        System.out.println("AbstractAnimal Run.");
    }
}
class Animal extends AbstractAnimal {
    public static void main(String[] args) {
        AbstractAnimal animal = new Animal();
        animal.run();
    }
}

执行结果:
AbstractAnimal Run.

2.抽象类能不能被实例化?为什么?

答:抽象类不能被实例化,因为抽象类和接口的设计就是用来规定子类行为特征的,就是让其他类来继承,是多态思想的一种设计体现,所以强制规定抽象类不能被实例化。

3.抽象方法可以被 private 修饰吗?为什么?

答:抽象方法不能使用 private 修饰,因为抽象方法就是要子类继承重写的,如果设置 private 则子类不能重写此抽象方法,这与抽象方法的设计理念相违背,所以不能被 private 修饰。

4.添加以下哪个选项不会引起编译器报错?

abstract class AbstractAnimal {
    static String animalName = "AbstractAnimal";
      // 添加代码处
}
Aprotected abstract void eat();
Bvoid eat();
Cabstract void eat(){};
D:animalName +=Cat;

答:A

题目解析:选项 B 普通方法必须有方法体;选项 C 抽象方法不能有方法体;选项 D 变量赋值操作必须在方法内。

5.以下关于抽象类和抽象方法说法正确的是
A:抽象类中的方法必须全部为抽象方法
B: 抽象类中必须包含一个抽象方法
C:抽象类中不能包含普通方法
D:抽象类中的方法可以全部为普通方法(包含方法体)

答:D

题目解析:抽象类中可以没有方法或者全部为普通方法,都是允许的,如下代码所示:

abstract class AbstractAnimal {
    public void run() {
        System.out.println("AbstractAnimal Run.");
    }
}
class Animal extends AbstractAnimal {
    public static void main(String[] args) {
        AbstractAnimal animal = new Animal();
        animal.run();
    }
}

程序执行的结果为:

AbstractAnimal Run.

6.接口和普通类有什么关系
答:在 Java 语言设计中,接口不是类,而是对类的一组需求描述,这些类必须要遵循接口描述的统一格式进行定义。

7.接口能不能有方法体
答:JDK 8 之前接口不能有方法体,JDK 8 之后新增了 static 方法和 default 方法,可以包含方法体。

8.执行以下代码会输出什么结果

interface IAnimal {
    static String animalName = "Animal Name";
}
class AnimalImpl implements IAnimal {
    static String animalName = new String("Animal Name");
    public static void main(String[] args) {
        System.out.println(IAnimal.animalName == animalName);
    }
}

答:执行的结果为 false。

题目解析:子类使用 new String… 重新创建了变量 animalName,又因为使用 == 进行内存地址比较,所以结果就是 false。

9.抽象类和接口有什么区别
答:抽象类和接口的区别,主要分为以下几个部分。

默认方法:
抽象类可以有默认方法的实现
JDK 8 之前接口不能有默认方法的实现,JDK 8 之后接口可以有默认方法的实现

继承方式
子类使用 extends 关键字来继承抽象类
子类使用 implements 关键字类实现接口

构造器
抽象类可以有构造器
接口不能有构造器

方法访问修饰符
抽象方法可以用 public / protected / default 等修饰符
接口默认是 public 访问修饰符,并且不能使用其他修饰符

多继承
一个子类只能继承一个抽象类
一个子类可以实现多个接口

10.以下抽象方法描述正确的是
A:抽象方法可以是静态(static)的
B:抽象方法可同时是本地方法(native)
C:抽象方法可以被 synchronized 修饰
D:以上都不是

答:D

题目解析:

抽象方法需要被子类重写,而静态方法是无法被重写的,因此抽象方法不能被静态(static)修饰;

本地方法是由本地代码实现的方法,而抽象方法没有实现,所以抽象方法不能同时是本地方法;

synchronized 和方法的实现细节有关,而抽象方法不涉及实现细节,因此抽象方法不能被 synchronized 修饰。

算法题-求N的阶乘

这道算法题一般考查的递归的编程技能,那么我们回顾下递归程序的特点:

1,什么是递归?

递归,就是方法内部调用方法自身
递归的注意事项:
找到规律,编写递归公式
找到出口(边界值),让递归有结束边界
注意:如果递归太多层,或者没有正确结束递归,则会出现“栈内存溢出Error”!
问题:为什么会出现栈内存溢出,而不是堆内存溢出?

2,这道题该怎么写?

规律:N!=(n-1)!*n;

出口:n1或n0 return 1;

public static int getResult(int n){
    if(n<0){
       throw new ValidateException("非法参数");
    }
    if(n==1 || n==0){
       return 1;
    }
    return getResult(n-1)*n;
}

算法题-求解斐波那切数列的第N个数是几?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KzOt0mZw-1631606888931)(…/images/v2-43a9a8e07aca193bc79500b36d8d0804_720w.jpg)]

    public static int getFeiBo(int n) {
        if (n < 0) {
            return -1;
        }
        if (n == 1 || n == 2) {
            return 1;
        } else {
            return getFeiBo(n - 1) + getFeiBo(n - 2);
        }
    }

Int和Integer的区别(重点)

	    Integer i1 = new Integer(12);
        Integer i2 = new Integer(12);
        System.out.println(i1==i2);//false

        Integer i3 = 126; //JDK1.5后  自动装箱的操作,查看源码可得,如果数据在(-128,127)的数据范围内,则不会new
        Integer i4 = 126;

        /*
        //反编译获取的代码
        Integer.valueOf(123);

        public static Integer valueOf(int i) {
            if (i >= IntegerCache.low && i <= IntegerCache.high)
                return IntegerCache.cache[i + (-IntegerCache.low)];
            return new Integer(i);
        }
        * */
        int i5=126;
        System.out.println(i3==i4); //true
        System.out.println(i3==i5); //true  自动拆箱,因为是数值,所以是相等的

        Integer i6 = 128; //根据源码可得,此处就等于,Integer i6 = new Integer(128);
        Integer i7 = 128;
        int i8=128;
        System.out.println(i6==i7);//false
        System.out.println(i6==i8); //true 自动拆箱,数值

以上这些输出的答案是什么?true or false? why?

你可以自己先思考,再看后面的答案分析。

答案揭晓

分情况来比较

  • 都定义为Integer的比较:

new:一旦new,就是开辟一块新内存,结果肯定是false
不new:
看范围
Integer做了缓存,-128至127,当你取值在这个范围的时候,会采用缓存的对象,所以会相等
当不在这个范围,内部创建新的对象,此时不相等

  • Integer和int的比较:

实际比较的是数值,Integer会做拆箱的动作,来跟基本数据类型做比较
此时跟是否在缓存范围内或是否new都没关系

源码分析:

当我们写Integer i = 126,实际上做了自动装箱:Integer i = Integer.valueOf(126);
分析这段源码

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}
//IntegerCache是Integer的内部类
private static class IntegerCache {
    static final int low = -128;
    static final int high;
    static final Integer cache[];
    static {
        // high value may be configured by property
        int h = 127;

自动装箱和自动拆箱

手动装箱过程:

Integer a = new Integer(66); //手动装箱过程:使用Integer包装类就相当于把数字装进箱子里一样

手动拆箱过程:

int i = a.intValue(); //手动拆箱过程:把Integer包装类型的数据转换成int类型的基本数据,intValue()就是将Integer转换为int

自动装箱过程:

Integer b = 88; //自动装箱过程,自动将数值转成Integer类型的数据。

自动拆箱过程:

int d = b; //自动拆箱过程,自动将数值转换成int类型的数据。
Integer i3 = Integer.valueOf(55); // Integer的valueOf()函数的功能就是把int类型转换为Integer。

java基本数据类型范围

整型:

byte:-2^7 ~ 2^7-1,即-128 ~ 127。1字节。Byte。末尾加B

short:-2^15 ~ 2^15-1,即-32768 ~ 32767。2字节。Short。末尾加S

有符号int:-2^31 ~ 2^31-1,即-2147483648 ~ 2147483647。4字节。Integer。

无符号int:0~2^32-1。

long:-2^63 ~ 2^63-1,即-9223372036854774808 ~ 9223372036854774807。8字节。Long。末尾加L。(也可以不加L)

浮点型:

float:4字节。Float。末尾加F。(也可以不加F)

double:8字节。Double。

字符型:

char:2字节。Character。

布尔型:

boolean:Boolean。

方法的重写和重载的区别

一般出现在(笔试题-选择题),下面我们说下重点

  • 重载:发生在一个类里面,方法名相同,参数列表不同(混淆点:跟返回类型没关系)

以下不构成重载
public double add(int a,int b)
public int add(int a,int b)​

  • 重写:发生在父类子类之间的,方法名相同,参数列表相同

List和Set的区别

这简直是一道送分题,简单到我都不好意思写出来,但居然有人会搞错,汗!

  • List(有序,可重复)【 ArrayList LinkList】
  • Set(无序,不可重复)【HashSet、TreeSet】

无序 !=可以排序

这里的无序:指的是,加进去的顺序,不一定等于输出的顺序

例子:

Collections 和 Collection 的区别?

在Java里面,工具类的命名,会+s结尾,Collections是工具类,Collection是接口

谈谈ArrayList和LinkedList的区别

1,底层数据结构的差异

ArrayList,数组,连续一块内存空间
LinkedList,双向链表,不是连续的内存空间

2,一个常规的结论

虽然不严谨,但也可以应付很多面试了

ArrayList,查找快,因为是连续的内存空间,方便寻址,但删除,插入慢,因为需要发生数据迁移
LinkedList,查找慢,因为需要通过指针一个个寻找,但删除,插入块,因为只要改变前后节点的指针指向即可。

思考后的答案:

查找第2个元素
ArrayList连续的内存空间,可计算的偏移量LinkedList不连续,无法计算,一个个往下找ArrayList>LinkedList

查找b在哪?
遍历只能一个个比较ArrayList = LinkedList

3,ArrayList细节分析

1,增加

  • 添加到末尾,正常不需要做特别的处理,除非现有的数组空间不够了,需要扩容

    • 数组初始化容量多大?10,当你知道需要存储多少数据时,建议在创建的时候,直接设置初始化大小

      怎么扩容?

      • 当发现容量不够之后,就进行扩容
        • 按原先数组容量的1.5倍进行扩容,位运算,下面是关键的源码
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
  • 再将原先数组的元素复制到新数组,Arrays
elementData = Arrays.copyOf(elementData, newCapacity)
  • 添加到其他位置,这个时候需要做整体的搬迁

  • 2,删除

  • 删除末尾,并不需要迁移

    • 删除其他的位置,这个时候也需要搬迁
  • 3,修改

  • 修改之前,必须先定位

    定位-查找-ArrayList(数组是一段连续的内存空间,定位会特别快)

  • 4,查找

  • 如上所述

4,LinkedList细节分析

1,提供了的两个引用(first,last)

2,增加

添加到末尾,创建一个新的节点,将之前的last节点设置为新节点的pre,新节点设置为last

我们看下源码:

void linkLast(E e) {
    //获取到最后一个节点​
    final Node<E> l = last;
    //构建一个新节点,将当前的last作为这个新节点的pre​
    final Node<E> newNode = new Node<>(l, e, null);
    //把last指向新节点​
    last = newNode;
    //如果原先没有最后一个节点​
    if (l == null)
        //将first指向新节点​
        first = newNode;
    else
        //否则,将原先的last的next指向新节点​
        l.next = newNode;
    size++;
    modCount++;
}
Node节点的定义:内部类
private static class Node<E> {
    E item;
    Node<E> next;
    Node<E> prev;
    Node(Node<E> prev, E element, Node<E> next) {
        this.item = element;
        this.next = next;
        this.prev = prev;
    }
}

添加到其他位置,这个时候,就需要调整前后节点的引用指向

3,如何去定义一个双向链表的节点,如上述的源码所示

4,修改

修改最后一个节点或者第一个节点,那么就很快(first,last)

修改其他位置,如果是按坐标来定位节点,则会按照二分查找法,源码如下:

if (index < (size >> 1)) {
    Node<E> x = first;
    for (int i = 0; i < index; i++)
        x = x.next;
    return x;
} else {
    Node<E> x = last;
    for (int i = size - 1; i > index; i--)
        x = x.prev;
    return x;
}

5,一个思考题,假如我们可以确定要存储1000个元素,那么采用ArrayList和LinkedList,

哪个更耗内存,为什么?

LinkedList,要实现在A和B之间插入C,该如何实现,编写伪代码即可

ArrayList
底层是数组:连续的内存的空间,长度固定

如果ArrayLiist容量不够,可以扩容
具体如何扩容
1,创建一个新数组,新数组的长度是原数组的1.5倍(通过位运算扩容)

2、将原数组的数据再迁移到新数组

ArrayList排序问题(3种方法)

1. 使用Collections.sort()方法进行排序

需要调用 Collections.sort()方法。

2. 使用Comparable排序

让 类实现 Comparable 接口并重写 compareTo()方法

3. 使用 Comparator 排序

如何在双向链表A和B之间插入C?

可以使用伪代码的方式来实现,你的答案是什么?

假设我们定位到了A节点,那么A.next就是B节点,这个是前提。

你的答案是?可以思考过后,再看答案

C.pre = A;

C.next = A.next;

A.next.pre = C;

A.next = C;

谈谈HashSet的存储原理

HashSet的存储原理或者工作原理,主要是从如何保证唯一性来说起。

这里面主要有3个问题,需要回答?

第一,为什么要采用Hash算法?有什么优势,解决了什么问题?

第二,所谓哈希表是一张什么表?

第三,HashSet如何保证保存对象的唯一性?会经历一个什么样的运算过程?

首先,我们要明确一点,HashSet底层采用的是HashMap来实现存储,其值作为HashMapkey

public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

具体关于hashmap的细节再说

第一,为什么要采用Hash算法?有什么优势,解决了什么问题

解决的问题是唯一性

存储数据,底层采用的是数组.

当我们往数组放数据的时候,你如何判断是否唯一?

可以采用遍历的方式,逐个比较,但是这种效率低,尤其是数据很多的情况下

所以,为了解决这个效率低的问题,我们采用新的方式

采用hash算法,通过计算存储对象的hashcode然后再跟数组长度-1做位运算,得到我们要存储在数组的哪个下标下,如果此时计算的位置没有其他元素,直接存储,不用比较。

此处,我们只会用到hashCode

但是随着元素的不断添加,就可能出现“哈希冲突”,不同的对象计算出来的hash值是相同的,这个时候,我们就需要比较,才需要用到equals方法

如果equals相同,则不插入,不相等,则形成链表使用头插法,新节点替换原来的节点)。

第二,所谓哈希表是一张什么表

本质是一个数组,而且数组的元素是链表

JDK1.7的版本实现

JDK1.8做了优化

随着元素不断添加,链表可能会越来越长,会优化为红黑树因为树的查找会比链表查找要快,因为树是二分查找的方式)。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q45qDNuA-1631606888932)(…/images/image-20210325225809583.png)]

ArrayList VS Vector

说句实话,对这种古老的Vector,之所以你在笔试题会遇到,我感觉是面试官偷懒了。

来吧,我们看看

ArrayList:线程不安全,效率高,常用
Vector:线程安全的,效率低
我们看Vector的源码:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0z2kTPMq-1631606888933)(…/images/v2-64837bba450a2a0cdcda96ae50bae445_720w.jpg)]

定义一个节点类:

class Node<T>{
    Node pre;
    Node next;
    T data;
}

开发一个自己的栈,你会怎么写?

答案如下:我们分析下JDK里面的Stack源码,会发现其实非常简单

首先,栈的特点是FILO(First In Last Out)

其次,底层的数据结构我们采用数组的方式

来,看几个关键的源码,一目了然

存:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4gkAnl9D-1631606888933)(…/images/v2-b4e97b3a0ae3c0b5fcf7d0ee4cffb1a7_720w.jpg)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ie1Wd4Ta-1631606888934)(…/images/v2-4c7a884cf36dc1e2299457de80436082_720w.jpg)]

取:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eWIyOfSi-1631606888935)(…/images/v2-7358b2a3ff8ecaea6c75f3263618893f_720w.jpg)]

谈谈IO流的分类及选择

缓冲区:(本质是数组)来的时候是一个一个来,还是一批一批来。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ddat6jXY-1631606888935)(…/images/image-20210330104750541.png)]

1,分类

按方向分:输入流,输出流

(注意,是站在程序的角度来看方向),输入流用于读文件,输出流用于写文件

按读取的单位分:字节流,字符流

按处理的方式分:节点流,处理流

比如,FileInputStream和BufferedInputStream(后者带有缓存区功能-byte[])

IO流的4大基类:InputStream,OutputStream,Reader,Writer

2,选择

字节流可以读取任何文件
读取文本文件的时候:选择字符流(假如有解析文件的内容的需求,比如逐行处理,则采用字符流,比如txt文件)
读取二进制文件的时候,选择字节流(视频,音频,doc,ppt)****

serialVersionUID的作用是什么

当执行序列化时,我们写对象到磁盘中,会根据当前这个类的结构生成一个版本号ID

当反序列化时,程序会比较磁盘中的序列化版本号ID跟当前的类结构生成的版本号ID是否一致,如果一致则反序列化成功,否则,反序列化失败

加上版本号,有助于当我们的类结构发生了变化,依然可以之前已经序列化的对象反序列化成功

新版本兼容老版本

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9Iqenleh-1631606888936)(…/images/image-20210330113224626.png)]

请描述下Java的异常体系

健壮性:Java通过异常体系来保证健壮性

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cSougdzC-1631606888937)(…/images/image-20210330152349860.png)]

异常体系如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-df4ASkQs-1631606888938)(…/images/v2-36ae97b8f5cfda5820465fc54c3530e9_720w.jpg)]

Error是虚拟机内部错误

栈内存溢出错误:StackOverflowError(递归,递归层次太多或递归没有结束)
堆内存溢出错误:OutOfMemoryError(堆创建了很多对象)

Exception是我们编写的程序错误

RuntimeException:也称为LogicException
为什么编译器不会要求你去try catch处理?
本质是逻辑错误,比如空指针异常,这种问题是编程逻辑不严谨造成的
应该通过完善我们的代码编程逻辑,来解决问题

非RuntimeException:

编译器会要求我们try catch或者throws处理
本质是客观因素造成的问题,比如FileNotFoundException
写了一个程序,自动阅卷,需要读取答案的路径(用户录入),用户可能录入是一个错误的路径,所以我们要提前预案,写好发生异常之后的处理方式,这也是java程序健壮性的一种体现

罗列常见的5个运行时异常(空-类-算-数)

运行时异常:又叫逻辑异常

此类异常,编译时没有提示做异常处理,因此通常此类异常的正确理解应该是“逻辑错误

空指针,
类型转换异常,
算数异常,
数组越界,
NumberFormateException(数字格式异常,转换失败,比如“a12”就会转换失败)

罗列常见的5个非运行时异常

IOException,

SQLException,

FileNotFoundException,

NoSuchFileException,

NoSuchMethodException

throw跟throws的区别

throw,作用于方法内,用于主动抛出异常
throws, 作用于方法声明上,声明该方法有可能会抛出某些异常

针对项目中,异常的处理方式,我们一般采用层层往上抛,最终通过异常处理机制统一处理(展示异常页面,或返回统一的json信息),``自定义 异常一般继承RunntimeException,我们去看看Hibernate等框架,他们的异常体系都是最终继承自RunntimeException

一道关于try catch finally返回值的问题

以下这道题,在实际开发中,并不会这么写。

这个是面试官为了考察大家对finally的认识,而苦思冥想出来,我猜的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nZdxdebv-1631606888939)(…/images/v2-3e8867ea1ca71f0aefa2c48680a2288e_720w.jpg)]

答案是:2,因为finally是无论如何都会执行,除非JVM关闭了

创建线程的方式

我们常说的方式有以下三种:

继承Thread(重写它的run方法)
实现Runable接口(重写run函数)
实现Callable接口(重写call函数,可以获取线程执行之后的返回值)

但实际后两种,更准确的理解是创建了一个可执行的任务,要采用多线程的方式执行,

还需要通过创建Thread对象来执行,比如 new Thread(new Runnable(){}).start();这样的方式来执行。

在实际开发中,我们通常采用线程池的方式来完成Thread的创建,更好管理线程资源。

案例:如何正确启动线程

class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+":running.....");
    }
}

public static void main(String[] args){
        MyThread thread = new MyThread();
        //正确启动线程的方式
        //thread.run();//调用方法并非开启新线程
        thread.start();
} 

案例:实现runnable只是创建了一个可执行任务,并不是一个线程

class MyTask implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+":running....");
    }
}

public static void main(String[] args){
        MyTask task = new MyTask();
        //task.start(); //并不能直接以线程的方式来启动
        //它表达的是一个任务,需要启动一个线程来执行
        new Thread(task).start();
    }

案例三:runnable vs callable(callable有返回值)

class MyTask2 implements Callable<Boolean>{

    @Override
    public Boolean call() throws Exception {
        return null;
    }
}

请描述线程的生命周期

一图胜千言!

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P22gQWoK-1631606888940)(…/images/v2-3640b7f86a072bc188199aa8bb76c271_720w.jpg)]

新建、就绪、运行、阻塞、终止

上述的图有些简略,下面详细说明下,线程共有6种状态:

new,runnable,blocked,waiting,timed waiting,terminated

1,当进入synchronized同步代码块或同步方法时,且没有获取到锁,线程就进入了blocked状态,直到锁被释放,重新进入runnable状态

2,当线程调用wait()或者join(插队)时,线程都会进入到waiting状态,当调用notify或notifyAll时,或者join的线程执行结束后,会进入runnable状态

3,当线程调用sleep(time),或者wait(time)时,进入timed waiting状态,

当休眠时间结束后,或者调用notify或notifyAll时会重新runnable状态。

4,程序执行结束,线程进入terminated状态

案例篇

/**
 * @author huangguizhao
 * 测试线程的状态
 */
public class ThreadStateTest {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new Task());
        System.out.println(thread.getState());//NEW
        thread.start();
        System.out.println(thread.getState());//RUNNABLE
        //保险起见,让当前主线程休眠下
        Thread.sleep(10);
        System.out.println(thread.getState());//terminated
    }
}
class Task implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(i);
        }
    }
}
public class ThreadStateTest {
    public static void main(String[] args) throws InterruptedException {
        BlockTask task = new BlockTask();
        Thread t1 = new Thread(task);
        Thread t2 = new Thread(task);
        t1.start();
        t2.start();
        //从严谨的角度来说,t1线程不一定会先执行,此处是假设t1先执行
        System.out.println(t1.getState());//RUNNABLE
        System.out.println(t2.getState());//BLOCKED
        Thread.sleep(10);
        System.out.println(t1.getState());//TIMED_WAITING
        Thread.sleep(1000);
        System.out.println(t1.getState());//WAITING
    }
}

class BlockTask implements Runnable{

    @Override
    public void run() {
        synchronized (this){
            //另一个线程会进入block状态
            try {
                //目的是让线程进入waiting time状态
                Thread.sleep(1000);
                //进入waiting状态
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

注意:

blocked,waiting,timed waiting 我们都称为阻塞状态

上述的就绪状态和运行状态,都表现为runnable状态

谈谈你对线程安全的理解?

如果这个是面试官直接问你的问题,你会怎么回答?

一个专业的描述是,当多个线程访问一个对象时,如果不用进行额外的同步控制或其他的协调操作,调用这个对象的行为都可以获得正确的结果,我们就说这个对象是线程安全的

线程安全的类

加了synchronized关键字,所以是同步方法,所以是线程安全的类

StringBuffer
·线程安全,可变的字符序列
·从版本JDK5开始,被StringBuilder替代。通常应该使用StringBuilder类,因为它支持所有相同的操作,但它更快,因为它不执行同步

Vector
·从Java2平台v1.2开始,该类改进了List接口,使其成为Java Collections Framework的成员。与新的集合实现不同,Vector被同步。如 果不需要线程安全的实现,建议使用ArrayList代替Vector

Hashtable
·该类实现了一个哈希表,它将键映射到值。任何非null对象都可以用作键或者值
·从ava2平台v1.2开始,该类进行了改进,实现了Map接口,使其成为Java Collections Framework的成员。
与新的集合实现不同,Hashtable被同步。如果不需要线程安全的实现,建议使用HashMap代替Hashtable

那么我们如何做到线程安全?

实现线程安全的方式有多种,其中在源码中常见的方式是,采用synchronized关键字给代码块或方法加锁,比如StringBuffer
查看StringBuffer的源码,你会看到是这样的:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2KwCTQoG-1631606888940)(…/images/v2-4423b8f2855af708b62c1c167b432ca0_720w.jpg)]

那么,我们开发中,如果需要拼接字符串,使用StringBuilder还是StringBuffer?

场景一

如果是多个线程访问同一个资源,那么就需要上锁,才能保证数据的安全性。

这个时候如果使用的是非线程安全的对象,比如StringBuilder,那么就需要借助外力,给他加synchronized关键字。或者直接使用线程安全的对象StringBuffer

局部变量不用管。成员变量就需要考虑线程安全问题了。当有多个线程的时候

场景二

如果每个线程访问的是各自的资源,那么就不需要考虑线程安全的问题,所以这个时候,我们可以放心使用非线程安全的对象,比如StringBuilder

比如在方法中,创建对象,来实现字符串的拼接。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XA19s555-1631606888941)(…/images/image-20210422151857985.png)]

不同的调用,会在栈中产生不同的栈帧。每个方法独一的一块空间,也对应着堆中独立的一块空间。

看场景,如果我们是在方法中使用,那么建议在方法中创建StringBuilder,这时候相当是每个线程独立占有一个StringBuilder对象,不存在多线程共享一个资源的情况,所以我们可以安心使用,虽然StringBuilder本身不是线程安全的。

什么时候需要考虑线程安全

1,多个线程访问同一个资源

2,资源是有状态的,比如我们上述讲的字符串拼接,这个时候数据是会有变化的

无状态的:比如直接查看方法。

下面是面试视频问题:

Array和ArrayList之间的区别?

一、Array和ArrayList的区别
#1. Array类型的变量在声明的同时必须进行实例化(至少得初始化数组的大小),而ArrayList可以只是先声明。
如:

int[] array = new array[3];int[] array = {1,2,3};ArrayList myList = new ArrayList();

这些都是合法的,而直接使用 int[ ] array;是不行的。

#4 初始化大小

Array对象的初始化必须只定指定大小,且创建后的数组大小是固定的,而ArrayList的大小可以动态指定,其大小可以在初始化时指定,也可以不指定,也就是说该对象的空间可以任意增加。

1、Array类型的变量在声明的同时必须进行实例化(至少得初始化数组的大小),而ArrayList可以只是先声明;

2、Array始终是连续存放的;而ArrayList的存放不一定连续;

3、Array对象的初始化必须指定大小,且创建后的数组大小是固定的;而ArrayList的大小可以动态指定,空间大小可以任意增加;

4、Array不能随意添加、删除;而ArrayList可以在任意位置插入和删除

Redis 是什么?都有哪些使用场景?

1.首先我们需要知道一下什么是Redis

Redis使用C语言开发的一个开源的高性能键值对(key-value)数据库。他通过提供多种键值数据类型来适应不同场景下的存储需求。
一句话:由C语言实现的直接操作内存的高性能的数据库软件。

2.目前为止Redis支持的键值数据类型如下

1】字符串类型
2】散列类型
3】列表类型
4】集合类型
5】有序集合类型

3.Redis的应用场景

缓存、抢购、直播间人数增减、点赞数、播放数、数据过期处理(可以精确到毫秒)

1】缓存(数据查询、短连接、新闻内容、商品内容等等)   最多使用
2】聊天室的在线好友列表 (你的好友也是别人的好友,他的上线下线。)
3】任务队列。(秒杀、抢购、12306等等)
4】应用排行榜   (直播间人数增减)
5】网站访问统计
6】数据过期处理(可以精确到毫秒)
7】分布式集群架构中的session分离

这些场景都有一个特点:用普通关系型数据库做,要频繁的去访问操作。

什么是Spring? 什么是 Spring Boot ?

什么是Spring?

​ Spring框架为开发 Java应用程序提供了全面的基础架构支持。它包含一些很好的功能,如依赖注入和开箱即用的模块,如:SpringJDBC、SpringMVC、SpringSecurity、SpringAOP、SpringORM、SpringTest,这些模块缩短应用程序的开发时间,提高了应用开发的效率例如,在 JavaWeb开发的早期阶段,我们需要编写大量的代码来将记录插入到数据库中。但是通过使用 SpringJDBC模块的 JDBCTemplate,我们可以将操作简化为几行代码。

什么是 Spring Boot ?

基于Spring的,开箱即用的框架

SpringBoot 基本上是 Spring框架的扩展,它消除了设置 Spring应用程序所需的 XML配置,为更快,更高效的开发生态系统铺平了道路。

SpringBoot中的一些特征:
1、创建独立的 Spring应用。
2、嵌入式 Tomcat、 Jetty、Undertow容器(无需部署war文件)。
3、提供的 starters 简化构建配置
4、尽可能自动配置 spring应用。
5、提供生产指标,例如指标、健壮检查和外部化配置
6、完全没有代码生成和 XML配置要求

get 和post 请求有哪些区别?

GET和POST是HTTP请求的两种基本方法,最直观的区别就是GET把参数包含在URL中,POST通过request body传递参数。

Http定义了与服务器交互的不同方法,最基本的方法有4种,分别是GET,POST,PUT,DELETE。

1.url可见性
get,参数url可见
post,url参数不可见

get把请求的数据放在url上,即HTTP协议头上,其格式为:以?分割URL和传输数据,参数之间以&相连;post把数据放在HTTP的包体内(requrest body)

2.传输数据的大小
get一般传输数据大小不超过2k-4k
post请求传输数据的大小根据php.ini 配置文件设定,也可以无限大

**get提交的数据最大是2k(原则上url长度无限制,那么get提交的数据也没有限制咯?限制实际上取决于浏览器,浏览器通常都会限制url长度在2K个字节,即使(大多数)服务器最多处理64K大小的url,也没有卵用);
post理论上没有限制。实际上IIS4中最大量为80KB,IIS5中为100KB

3.数据传输上
get,通过拼接url进行传递参数
post,通过body体传输参数

**GET产生一个TCP数据包,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);
POST产生两个TCP数据包,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)

4.后退页面的反应
get请求页面后退时,不产生影响
post请求页面后退时,会重新提交请求

**GET在浏览器回退时是无影响的,POST会再次提交请求

5.缓存性
get请求是可以缓存的
post请求不可以缓存

**GET请求会被浏览器主动cache,而POST不会,除非手动设置

6.安全性
都不安全,原则上post肯定要比get安全,毕竟传输参数时url不可见,但也挡不住部分人闲的没事在那抓包玩,浏览器还会缓存get请求的数据。安全性个人觉得是没多大区别的,防君子不防小人就是这个道理。对传递的参数进行加密,其实都一样

7.GET请求只能进行url编码,而POST支持多种编码方式

8.GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留

9.GET只接受ASCII字符的参数的数据类型,而POST没有限制

那么,post那么好为什么还用get?get效率高!

解释一下什么是 ioc?
  • 什么是 IoC?
  • IoC 解决了什么问题?
  • IoC 和 DI 的区别?

什么是 IoC

IoC (Inversion of control )控制反转/反转控制。它是一种思想不是一个技术实现。描述的是:Java 开发领域对象的创建以及管理的问题。

例如:现有类 A 依赖于类 B

  • 传统的开发方式 :往往是在类 A 中手动通过 new 关键字来 new 一个 B 的对象出来
  • 使用 IoC 思想的开发方式 :不通过 new 关键字来创建对象,而是通过 IoC 容器(Spring 框架) 来帮助我们实例化对象。我们需要哪个对象,直接从 IoC 容器里面过去即可。

从以上两种开发方式的对比来看:我们 “丧失了一个权力” (创建、管理对象的权力),从而也得到了一个好处(不用再考虑对象的创建、管理等一系列的事情)

为什么叫控制反转

  • 控制 :指的是对象创建(实例化、管理)的权力
  • 反转 :控制权交给外部环境(Spring 框架、IoC 容器)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o477mA6h-1631606888942)(…/images/071607350022831006933.png)]

IoC 解决了什么问题

IoC 的思想就是两方之间不互相依赖,由第三方容器来管理相关资源。这样有什么好处呢?

  • 对象之间的耦合度或者说依赖程度降低;
  • 资源变得容易管理;比如你用 Spring 容器提供的话很容易就可以实现一个单例。

例如:现有一个针对 User 的操作,利用 Service 和 Dao 两层结构进行开发

在没有使用 IoC 思想的情况下,Service 层想要使用 Dao 层的具体实现的话,需要通过 new 关键字在UserServiceImpl 中手动 new 出 IUserDao 的具体实现类 UserDaoImpl(不能直接 new 接口类)。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NaW3oPZ7-1631606888942)(…/images/071607350068487062699.jpg)]

很完美,这种方式也是可以实现的,但是我们想象一下如下场景:

开发过程中突然接到一个新的需求,针对对IUserDao 接口开发出另一个具体实现类。因为 Server 层依赖了IUserDao的具体实现,所以我们需要修改UserServiceImpl中 new 的对象。如果只有一个类引用了IUserDao的具体实现,可能觉得还好,修改起来也不是很费力气,但是如果有许许多多的地方都引用了IUserDao的具体实现的话,一旦需要更换IUserDao 的实现方式,那修改起来将会非常的头疼。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b3tfqXIS-1631606888943)(…/images/071607350119128078067.jpg)]

使用 IoC 的思想,我们将对象的控制权(创建、管理)交由 IoC 容器去管理,我们在使用的时候直接向 IoC 容器 “要” 就可以了

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uhYtFhih-1631606888944)(…/images/071607350149190044338.png)]

解释一下什么是Spring aop?

Spring AOP(Aspect Oriented Programming,面向切面编程)是OOPs(面向对象编程)的补充,它也提供了模块化。在面向对象编程中,关键的单元是对象,AOP的关键单元是切面,或者说关注点(可以简单地理解为你程序中的独立模块)。一些切面可能有集中的代码,但是有些可能被分散或者混杂在一起,例如日志或者事务。这些分散的切面被称为横切关注点。一个横切关注点是一个可以影响到整个应用的关注点,而且应该被尽量地集中到代码的一个地方,例如事务管理、权限、日志、安全等。
AOP让你可以使用简单可插拔的配置,在实际逻辑执行之前、之后或周围动态添加横切关注点。这让代码在当下和将来都变得易于维护。如果你是使用XML来使用切面的话,要添加或删除关注点,你不用重新编译完整的源代码,而仅仅需要修改配置文件就可以了。
Spring AOP通过以下两种方式来使用。但是最广泛使用的方式是Spring AspectJ 注解风格(Spring AspectJ Annotation Style)

  • 使用AspectJ 注解风格
  • 使用Spring XML 配置风格

登录的拦截、保存、日志的处理。

说一下session的工作原理?

其实session是一个存在服务器上的类似于一个散列表格的文件。里面存有我们需要的信息,在我们需要用的时候可以从里面取出来。

类似于一个大号的map吧,里面的键存储的是用户的sessionid,用户向服务器发送请求的时候会带上这个sessionid。这时就可以从中取出对应的值了。

一、创建Session
当用户访问一个服务器,如果服务器启用Session,服务器就要为该用户创建一个SESSION。
  在创建这个SESSION的时候,服务器首先检查这个用户发来的请求里是否包含了一个SESSION ID,如果包含了一个SESSION ID则说明之前该用户已经登陆过并为此用户创建过SESSION,那服务器就按照这个SESSION ID把这个SESSION在服务器的内存中查找出来。
  如果客户端请求里不包含有SESSION ID,则为该客户端创建一个SESSION并生成一个与此SESSION相关的SESSION ID。这个SESSION ID是唯一的、不重复的、不容易找到规律的字符串,这个SESSION ID将会在本次响应返回到客户端,客户端通过COOKIE来保存SESSION ID,这样在交互过程中浏览器可以自动的按照规则把这个标识发送给服务器。

二、禁用cookie使用Session
浏览器可以将session存到localstorage中,然后附在约定的地方传给服务端

服务器端的重定向如需传递session,可以使用url重写技术。

cookie和session的比较?

首先来说一下什么是cookie:cookie是Web服务器保存在客户端的一系列文本信息;

cookie的作用大致有三点:对特定对象的追踪,统计网页浏览次数,简化登陆。

它的安全性能是比较差的,容易泄露信息。

其次说一下什么是会话:一个会话就是浏览器与服务器之间的一次通话,包含浏览器与服务器之间的多次请求、响应的过程。

为什么说到会话呢?

因为session对象就是用来存储有关用户会话的所有信息的。

session是 jsp 内置对象,与浏览器 一 一 对应,允许用户存储和提取会话状态的信息。

对比一下两者,有以下几点不同:

1.作用位置:cookie是在客户端保存用户信息,session实在服务器端保存用户信息;

2.保存内容:cookie保存的是字符串,session中保存的是对象;

3.作用时间:cookie可以长期保存在客户端,session随会话结束而关闭;

4.一般cookie保存不重要的用户信息,重要的信息由session保存。

并行和并发的区别?

并发:一个处理器可以同时处理多个任务。这是逻辑上的同时发生。
并行:多个处理器同时处理多个不同的任务。这是物理上的同时发生。
有一个清晰地比喻:
并发:一个人同时吃三个苹果。并行:三个人同时吃三个苹果。

图文并茂:
并发(concurrency):
指同一时刻只能够执行一条指令,但是多条指令被快速的进行切换,给人造成了它们同时执行的感觉。但在微观来说,并不同同时进行的,只是划分时间段,分别进行执行。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rcHXweur-1631606888944)(…/images/20190523181852896.png)]

并行(parallel):
在同一时刻,有多条指令在多个处理器上同时执行。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BoumO6S8-1631606888945)(…/images/20190523181839145.png)]

牛客网Java面经部分:

&和&&的区别?

&运算符有两种用法:(1)按位与;(2)逻辑与。&&运算符是短路与运算。逻辑与跟短路与的差别是非常巨大的,虽然二者都要求运算符左右两端的布尔值都是true整个表达式的值才是true。&&之所以称为短路运算是因为,如果&&左边的表达式的值是false,右边的表达式会被直接短路掉,不会进行运算。很多时候我们可能都需要用&&而不是&,例如在验证用户登录时判定用户名不是null而且不是空字符串,应当写为:username != null &&!username.equals(""),二者的顺序不能交换,更不能用&运算符,因为第一个条件如果不成立,根本不能进行字符串的equals比较,否则会产生NullPointerException异常。注意:逻辑或运算符(|)和短路或运算符(||)的差别也是如此。

本题涉及java知识点分析
逻辑与运算符&
&,双目运算符:将两个表达式的值按二进制位展开,对应的位(bit)按值进行“与”运算,结果保留在该位上。
比如170&204
对应二进制就是
10101010B & 11001100B =10001000B…
170&204=136…
与运算:该位只要有一个值为0结果为0,否则结果为1
如果两数位数不同,则较短数高位补零,再运算,比如char a=100;int b=260;

短路与运算符&&
&&:双目运算符,计算两个表达式同时成立的“真值”(同时成立真值为真否则为假)
逻辑真值有两种,1为真,0为假,但在运算时用非零表示真,0表示假
即:数值->逻辑真值--非0为真,0为假/逻辑真值->数值--真为1,假为0。
例如:char a=1,b=0,c=-1;那么a真b假c真。a&&b和c&&b为假值为0,a&&c为真值为1

int和Integer有什么区别?

Java是一个近乎纯洁的面向对象编程语言,但是为了编程的方便还是引入了基本数据类型,但是为了能够将这些基本数据类型当成对象操作,Java为每一个基本数据类型都引入了对应的包装类型(wrapper class),int的包装类就是Integer,从Java 5开始引入了自动装箱/拆箱机制,使得二者可以相互转换。
Java 为每个原始类型提供了包装类型:

- 原始类型: boolean,char,byte,short,int,long,float,double

- 包装类型:Boolean,Character,Byte,Short,Integer,Long,Float,Double

class AutoUnboxingTest {
    public static void main(String[] args) {
        Integer a = new Integer(3);
        Integer b = 3;                  // 将3自动装箱成Integer类型
        int c = 3;
        System.out.println(a == b);     // false 两个引用没有引用同一对象
        System.out.println(a == c);     // true a自动拆箱成int类型再和c比较
    }
}

String是最基本的数据类型吗?

基本数据类型包括byte、int、char、long、float、double、boolean和short。

java.lang.String类是final类型的,因此不可以继承这个类、不能修改这个类。为了提高效率节省空间,我们应该用StringBuffer类。

请你解释什么是值传递和引用传递?

值传递是对基本型变量而言的,传递的是该变量的一个副本,改变副本不影响原变量.

引用传递一般是对于对象型变量而言的,传递的是该对象地址的一个副本, 并不是原对象本身 。 所以对引用对象进行操作会同时改变原对象.

一般认为,java内的传递都是值传递.

请你讲讲Java支持的数据类型有哪些?什么是自动拆装箱?

Java语言支持的8种基本数据类型是:
byte
short
int
long
float
double
boolean
char

自动装箱是Java编译器在基本数据类型和对应的对象包装类型之间做的一个转化。比如:把int转化成Integer,double转化成Double,等等。反之就是自动拆箱。

请你解释为什么会出现4.0-3.6=0.40000001这种现象?

原因简单来说是这样:2进制的小数无法精确的表达10进制小数,计算机在计算10进制小数的过程中要先转换为2进制进行计算,这个过程中出现了误差。

请你讲讲一个十进制的数在内存中是怎么存的?

补码的形式。

请你说说Lamda表达式的优缺点。

Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。

Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。

使用 Lambda 表达式可以使代码变的更加简洁紧凑。

优点:1. 简洁。2. 非常容易并行计算。3. 可能代表未来的编程趋势。

缺点:1. 若不用并行计算,很多时候计算速度没有比传统的 for 循环快。(并行计算有时需要预热才显示出效率优势)2. 不容易调试。3. 若其他程序员没有学过 lambda 表达式,代码不容易让其他语言的程序员看懂。

你知道java8的新特性吗,请简单介绍一下

Lambda 表达式 − Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法中。

方法引用− 方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。

默认方法− 默认方法就是一个在接口里面有了一个实现的方法。

新工具− 新的编译工具,如:Nashorn引擎 jjs、 类依赖分析器jdeps。

Stream API −新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。

Date Time API − 加强对日期与时间的处理。

Optional 类 − Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。

Nashorn, JavaScript 引擎 − Java 8提供了一个新的Nashorn javascript引擎,它允许我们在JVM上运行特定的javascript应用。

请你说明符号“==”比较的是什么?

== 对比两个对象基于内存引用,如果两个对象的引用完全相同(指向同一个对象)时,==操作将返回true,否则返回false。

“==”如果两边是基本类型,就是比较数值是否相等。

请你解释Object若不重写hashCode()的话,hashCode()如何计算出来的?

Object 的 hashcode 方法是本地方法,也就是用 c 语言或 c++ 实现的,该方法直接返回对象的 内存地址。

请你解释为什么重写equals还要重写hashcode?

HashMap中,如果要比较key是否相等,要同时使用这两个函数!因为自定义的类的hashcode()方法继承于Object类,其hashcode码为默认的内存地址,这样即便有相同含义的两个对象,比较也是不相等的。HashMap中的比较key是这样的,先求出key的hashcode(),比较其值是否相等,若相等再比较equals(),若相等则认为他们是相等的。若equals()不相等则认为他们不相等。如果只重写hashcode()不重写equals()方法,当比较equals()时只是看他们是否为同一对象(即进行内存地址的比较),所以必定要两个方法一起重写。HashMap用来判断key是否相等的方法,其实是调用了HashSet判断加入元素 是否相等。重载hashCode()是为了对同一个key,能得到相同的Hash Code,这样HashMap就可以定位到我们指定的key上。重载equals()是为了向HashMap表明当前对象和key上所保存的对象是相等的,这样我们才真正地获得了这个key所对应的这个键值对。

请你介绍一下map的分类和常见的情况

java为数据结构中的映射定义了一个接口java.util.Map;它有四个实现类,分别是HashMap Hashtable LinkedHashMap 和TreeMap.

Map主要用于存储健值对,根据键得到值,因此不允许键重复(重复了覆盖了),但允许值重复。

Hashmap 是一个最常用的Map,它根据键的HashCode值存储数据,根据键可以直接获取它的值,具有很快的访问速度,遍历时,取得数据的顺序是完全随机的。 HashMap最多只允许一条记录的键为Null;允许多条记录的值为 Null;HashMap不支持线程的同步,即任一时刻可以有多个线程同时写HashMap;可能会导致数据的不一致。如果需要同步,可以用 Collections的synchronizedMap方法使HashMap具有同步的能力,或者使用ConcurrentHashMap。

Hashtable与 HashMap类似,它继承自Dictionary类,不同的是:它不允许记录的键或者值为空;它支持线程的同步,即任一时刻只有一个线程能写Hashtable,因此也导致了 Hashtable在写入时会比较慢。

LinkedHashMap 是HashMap的一个子类,保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的.也可以在构造时用带参数,按照应用次数排序。在遍历的时候会比HashMap慢,不过有种情况例外,当HashMap容量很大,实际数据较少时,遍历起来可能会比 LinkedHashMap慢,因为LinkedHashMap的遍历速度只和实际数据有关,和容量无关,而HashMap的遍历速度和他的容量有关。

TreeMap实现SortMap接口,能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器,当用Iterator 遍历TreeMap时,得到的记录是排过序的。

一般情况下,我们用的最多的是HashMap,在Map 中插入、删除和定位元素,HashMap 是最好的选择。但如果您要按自然顺序或自定义顺序遍历键,那么TreeMap会更好。如果需要输出的顺序和输入的相同,那么用LinkedHashMap 可以实现,它还可以按读取顺序来排列.

请你讲讲Java里面的final关键字是怎么用的?

当用final修饰一个类时,表明这个类不能被继承。也就是说,如果一个类你永远不会让他被继承,就可以用final进行修饰。final类中的成员变量可以根据需要设为final,但是要注意final类中的所有成员方法都会被隐式地指定为final方法。

“使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升。在最近的Java版本中,不需要使用final方法进行这些优化了。“

对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。

请你谈谈关于Synchronized和lock

synchronized是Java的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。JDK1.5以后引入了自旋锁、锁粗化、轻量级锁,偏向锁来有优化关键字的性能。

Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。

介绍一下Syncronized锁,如果用这个关键字修饰一个静态方法,锁住了什么?如果修饰成员方法,锁住了什么?

synchronized修饰静态方法以及同步代码块的synchronized (类.class)用法锁的是类,线程想要执行对应同步代码,需要获得类锁。
synchronized修饰成员方法,线程获取的是当前调用该方法的对象实例的对象锁。

若对一个类不重写,它的equals()方法是如何比较的?

equals():Object类中的一个方法,只适用于引用数据类型

Object类中的equals()的比较还是使用的“==”运算符;

在Date、String、File、包装类等都重写了equals()方法,重写以后,比较的不再是地址值,而是比较数据值;

请解释hashCode()和equals()方法有什么联系?

Java对象的eqauls方法和hashCode方法是这样规定的:

➀相同 的对象必须具有相等的哈希码(或者散列码)。

➁如果两个对象的hashCode相同,它们并不一定相同。

请解释Java中的概念,什么是构造函数?什么是构造函数重载?什么是复制构造函数?

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

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

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

请说明JAVA语言如何进行异常处理,关键字:throws,throw,try,catch,finally分别代表什么意义?在try块中可以抛出异常吗?

Java 通过面向对象的方法进行异常处理,把各种不同的异常进行分类,并提供了良好的接口。在Java中,每个异常都是一个对象,它是Throwable类或其它子类的实例。当一个方法出现异常后便抛出一个异常对象,该对象中包含有异常信息,调用这个对象的方法可以捕获到这个异常并进行处理。Java的异常处理是通过5个关键词来实现的:try、catch、throw、throws和finally。一般情况下是用try来执行一段程序,如果出现异常,系统会抛出(throws)一个异常,这时候你可以通过它的类型来捕捉(catch)它,或最后(finally)由缺省处理器来处理。用try来指定一块预防所有”异常”的程序。紧跟在try程序后面,应包含一个catch子句来指定你想要捕捉的”异常”的类型。throw语句用来明确地抛出一个”异常”。throws用来标明一个成员函数可能抛出的各种”异常”。Finally为确保一段代码不管发生什么”异常”都被执行一段代码。可以在一个成员函数调用的外面写一个try语句,在这个成员函数内部写另一个try语句保护其他代码。每当遇到一个try语句,”异常“的框架就放到堆栈上面,直到所有的try语句都完成。如果下一级的try语句没有对某种”异常”进行处理,堆栈就会展开,直到遇到有处理这种”异常”的try语句。

浅谈abstract class和interface有什么区别?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wJa9fUoT-1631606888946)(…/images/20200831154304100.png)]

接口中的类方法、默认方法、私有方法都可以有方法体的。

请说明一下final, finally, finalize的区别。

final 用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,类不可继承。

finally是异常处理语句结构的一部分,表示总是执行。

finalize是Object类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法,可以覆盖此方法提供垃圾收集时的其他资源

回收,例如关闭文件等。

请说明面向对象的特征有哪些方面

-抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么。

-继承:继承是从已有类得到继承信息创建新类的过程。提供继承的类叫父类(超类、基类)、得到继承的类叫子类(派生类)。

-封装:通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口。可以说,封装就是隐藏一切可隐藏的东西,只向外界提供最简单的编程接口(可以想想普通洗衣机和全自动洗衣机的差别,明显全自动洗衣机封装更好因此操作起来更简单;我们现在使用的智能手机也是封装得足够好的,因为几个按键就搞定了所有的事情)。

-多态性:多态性是指允许不同子类型的对象对同一消息作出不同的响应。简单的说就是用同样的对象引用调用同样的方法但是做了不同的事情。实现多态需要做两件事:

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

答案是:不可以,因为static变量是属于类的,在类加载的时候就被初始化了,这时候非静态变量并没有加载,故非静态变量不能访问。

请你讲讲什么是泛型?

“泛型” 意味着编写的代码可以被不同类型的对象所重用。

泛型是在JDK1.5之后出现的。

泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。

extends 关键字声明了类型的上界,表示参数化的类型可能是所指定的类型,或者是此类型的子类

super 关键字声明了类型的下界,表示参数化的类型可能是指定的类型,或者是此类型的父类

静态变量存在什么位置?

方法区

请你谈谈StringBuffer和StringBuilder有什么区别,底层实现上呢?

StringBuffer线程安全,StringBuilder线程不安全,底层实现上的话,StringBuffer其实就是比StringBuilder多了Synchronized修饰符。

请说明String是否能被继承?

不能,char数组用final修饰的。

jdk1.8及以前String使用的是char数组,jdk1.9及以后使用的是byte数组。都是被final修饰的。

请说明”static”关键字是什么意思?Java中是否可以覆盖(override)一个private或者是static的方法?

static关键字 所修饰的方法/变量可以直接通过类名访问,而不是先创建对象再对里面的静态方法/变量进行访问,当然这样也可以。

static所修饰的 变量 / 代码块 / 方法 在该类被加载时加载(这里特指静态代码块,并且只执行一次)。同时,被static修饰的变量在堆中只有一个副本,所有对象对该变量的操作都是在操作同一个变量,如果是实例变量的话,每个对象都有各自的副本,每个对象操作的都是各自的副本。

请说明类和对象的区别

1.类是对某一类事物的描述,是抽象的;而对象是一个实实在在的个体,是类的一个实例。

比如:“人”是一个类,而“教师”则是“人”的一个实例。

2.对象是函数、变量的集合体;而类是一组函数和变量的集合体,即类是一组具有相同属性的对象集合体。

请讲讲Java有哪些特性,并举一个和多态有关的例子。

封装、继承、多态。多态:指允许不同类的对象对同一消息做出响应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式。(发送消息就是函数调用)

请说明List、Map、Set三个接口存取元素时,各有什么特点?

存放时:

1.List以特定的索引(有顺序的存放)来存放元素,可以有重复的元素

2.Set存放元素是无序的,而且不可重复

3.Map保存键值对的映射,映射关系可以是一对一(键值)或者多对一,需要注意到的是:键无序不可重复,值可以重复

取出时:

(1)List取出元素for循环,foreach循环,Iterator迭代器迭代

(2)Set取出元素foreach循环,Iterator迭代器迭代

(3)Map取出元素需转换为Set,然后进行Iterator迭代器迭代,或转换为Entry对象进行Iterator迭代器迭代

请判断List、Set、Map是否继承自Collection接口?

List、Set 是,Map 不是。Map是键值对映射容器,与List和Set有明显的区别,而Set存储的零散的元素且不允许有重复元素(数学中的集合也是如此),List是线性结构的容器,适用于按数值索引访问元素的情形。

请讲讲你所知道的常用集合类以及主要方法?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qX1rk65T-1631606888947)(…/images/1201956-20190514103509341-711090510.png)]

java中有几种常用的数据结构,主要分为Collection和map两个主要接口(接口只提供方法,并不提供实现),而程序中最终使用的数据结构是继承自这些接口的数据结构类。

最常用的集合类是List 和 Map。

List 的具体实现包括 ArrayList 和 Vector,它们是可变大小的列表,比较适合构建、存储和操作任何类型对象的元素列表。List 适用于按数值索引访问元素的情形。

Map 提供了一个更通用的元素存储方法。 Map 集合类用于存储元素对(称作"键"和"值"),其中每个键映射到一个值。

Collection与Collections的根本区别是

1、Collection 是集合类的上级接口,继承与他的接口主要有Set 和List.。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Xxwwadjd-1631606888947)(…/images/f9198618367adab46cfaf50e86d4b31c8701e451)]

2、Collections 是一个包装类。它包含有各种有关集合操作的静态多态方法。此类不能实例化,就像一个工具类,服务于Java的Collection框架。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aUEH5lGy-1631606888948)(…/images/f3d3572c11dfa9ecc8e0b95a6fd0f703908fc1b5)]

请你说明HashMap和Hashtable的区别?

1.HashMap是Hashtable的轻量级实现(非线程安全的实现),他们都完成了Map接口,主要区别在于HashMap允许空(null)键值(key),由于非线程安全,在只有一个线程访问的情况下,效率要高于Hashtable。

2.HashMap允许将null作为一个entry的key或者value,而Hashtable不允许。

3.HashMap把Hashtable的contains方法去掉了,改成containsvalue和containsKey。因为contains方法容易让人引起误解。

4.Hashtable继承自Dictionary类,而HashMap是Java1.2引进的Map interface的一个实现。

5.最大的不同是,Hashtable的方法是Synchronize的,而HashMap不是,在多个线程访问Hashtable时,不需要自己为它的方法实现同步,而HashMap 就必须为之提供外同步。

6.Hashtable和HashMap采用的hash/rehash算法都大概一样,所以性能不会有很大的差异。

7.就HashMap与HashTable主要从三方面来说。 一.历史原因:Hashtable是基于陈旧的Dictionary类的,HashMap是Java 1.2引进的Map接口的一个实现 二.同步性:Hashtable是线程安全的,也就是说是同步的,而HashMap是线程序不安全的,不是同步的 三.值:只有HashMap可以让你将空值作为一个表的条目的key或value

请你说说Iterator和ListIterator的区别?

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

请简单说明一下什么是迭代器?

Iterator提供了统一遍历操作集合元素的统一接口, Collection接口实现Iterable接口,
每个集合都通过实现Iterable接口中iterator()方法返回Iterator接口的实例, 然后对集合的元素进行迭代操作.
有一点需要注意的是:在迭代元素的时候不能通过集合的方法删除元素, 否则会抛出ConcurrentModificationException 异常. 但是可以通过Iterator接口中的remove()方法进行删除.

请说明Java集合类框架的基本接口有哪些?

集合类接口指定了一组叫做元素的对象。集合类接口的每一种具体的实现类都可以选择以它自己的方式对元素进行保存和排序。有的集合类允许重复的键,有些不允许。
Java集合类提供了一套设计良好的支持对一组对象进行操作的接口和类。Java集合类里面最基本的接口有:
Collection:代表一组对象,每一个对象都是它的子元素。
Set:不包含重复元素的Collection。
List:有顺序的collection,并且可以包含重复元素。
Map:可以把键(key)映射到值(value)的对象,键不能重复。

请解释一下TreeMap?

TreeMap是一个有序的key-value集合,基于红黑树(Red-Black tree)的 NavigableMap实现。该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator进行排序,具体取决于使用的构造方法。

TreeMap 的实现就是红黑树数据结构,也就说是一棵自平衡的排序二叉树,这样就可以保证当需要快速检索指定节点。

红黑树的插入、删除、遍历时间复杂度都为O(lgN),所以性能上低于哈希表。但是哈希表无法提供键值对的有序输出,红黑树因为是排序插入的,可以按照键的值的大小有序输出。红黑树性质:

TreeMap的特性:

性质1:每个节点要么是红色,要么是黑色。

性质2:根节点永远是黑色的。

性质3:所有的叶节点都是空节点(即 null),并且是黑色的。

性质4:每个红色节点的两个子节点都是黑色。(从每个叶子到根的路径上不会有两个连续的红色节点)

性质5:从任一节点到其子树中每个叶子节点的路径都包含相同数量的黑色节点。

请说明ArrayList是否会越界?

ArrayList是实现了基于动态数组的数据结构,而LinkedList是基于链表的数据结构 . 对于随机访问get和set,ArrayList要优于LinkedList,因为LinkedList要移动指针;ArrayList并发add()可能出现数组下标越界异常。

如果hashMap的key是一个自定义的类,怎么办?

使用HashMap,如果key是自定义的类,就必须重写hashcode()和equals()。

如何保证线程安全?

通过合理的时间调度,避开共享资源的存取冲突。

另外,在并行任务设计上可以通过适当的策略,保证任务与任务之间不存在共享资源,设计一个规则来保证一个客户的计算工作和数据访问只会被一个线程或一台工作机完成,而不是把一个客户的计算工作分配给多个线程去完成。

请你简要说明一下线程的基本状态以及状态之间的关系?

Running表示运行状态,

Runnable表示就绪状态,万事俱备,只欠CPU;

Blocked 表示阻塞状态,阻塞状态又有多种情况,可能是因为调用wait()方法进入等待池,也可能是因为执行同步方法或同步代码块进入等锁池,或者是调用了sleep()方法或join()方法等待休眠或其他线程结束,或者是由于IO中断。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3UxPQIxQ-1631606888949)(…/images/93694598_1567901669525_2E1604194BEFDDB0EE43167B50B364AB)]

什么是线程池?

线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。

为什么要用线程池?

  • 降低资源消耗

    • 通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  • 提高响应速度

    • 当任务到达时,任务可以不需要等到线程创建就能立即执行。
  • 提高线程的可管理性

    • 线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。但是要做到合理的利用线程池,必须对其原理了如指掌。

java中举例说明同步和异步

如果系统中存在临界资源(资源数量少于竞争资源的线程数量的资源),例如正在写的数据以后可能被另一个线程读到,或者正在读的数据可能已经被另一个线程写过了,那么这些数据就必须进行同步存取(数据库操作中的排他锁就是最好的例子)。

当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回时,就应该使用异步编程,在很多情况下采用异步途径往往更有效率。

事实上,所谓的同步就是指阻塞式操作,而异步就是非阻塞式操作。

请介绍一下线程同步和线程调度的相关方法。

- wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁;

- sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理InterruptedException异常;

- notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且与优先级无关;

- notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态;

通过Lock接口提供了显式的锁机制(explicit lock),增强了灵活性以及对线程的协调。Lock接口中定义了加锁(lock())和解锁(unlock())的方法,同时还提供了new Condition()方法来产生用于线程之间通信的Condition对象;此外,Java 5还提供了信号量机制(semaphore),信号量可以用来限制对某个共享资源进行访问的线程的数量。在对资源进行访问之前,线程必须得到信号量的许可(调用Semaphore对象的acquire()方法);在完成对资源的访问后,线程必须向信号量归还许可(调用Semaphore对象的release()方法)。

请问当一个线程进入一个对象的synchronized方法A之后,其它线程是否可进入此对象的synchronized方法B?

不能。其它线程只能访问该对象的非同步方法,同步方法则不能进入。因为非静态方法上的synchronized修饰符要求执行方法时要获得对象的锁,如果已经进入A方法说明对象锁已经被取走,那么试图进入B方法的线程就只能在等锁池(注意不是等待池哦)中等待对象的锁。

请简述一下线程的sleep()方法和yield()方法有什么区别?

(1)sleep()方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会;yield()方法只会给相同优先级或更高优先级的线程以运行的机会;

(2)线程执行sleep()方法后转入阻塞(blocked)状态,而执行yield()方法后转入就绪(ready)状态;

(3)sleep()方法声明抛出InterruptedException,而yield()方法没有声明任何异常;

(4)sleep()方法比yield()方法(跟操作系统CPU调度相关)具有更好的可移植性。

请说出你所知道的线程同步的方法

wait():使一个线程处于等待状态,并且释放所持有的对象的lock。

sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉InterruptedException异常。

notify():唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级。

notifyAll():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争。

启动一个线程是用run()还是start()?

启动一个线程是调用start()方法,使线程所代表的虚拟处理机 处于可运行状态,这意味着它可以由JVM调度并执行。这并不意味着线程就会立即运行。run()方法可以产生必须退出的标志来停止一个线程。

请说明一下sleep() 和 wait() 有什么区别?

sleep是线程类(Thread)的方法,导致此线程暂停执行指定时间,把执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复。调用sleep不会释放对象锁。

wait是Object类的方法,对象调用wait方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出notify方法(或notifyAll)后本线程才进入对象锁定池准备获得对象锁进入运行状态。

请分析一下同步方法和同步代码块的区别是什么?

同步方法默认用this或者当前类class对象作为锁;

同步代码块可以选择以什么来加锁,比同步方法要更细颗粒度,我们可以选择只同步会发生同步问题的部分代码而不是整个方法;

同步方法使用关键字 synchronized修饰方法,而同步代码块主要是修饰需要进行同步的代码,用 synchronized(object){代码内容} 进行修饰;

请详细描述一下线程从创建到死亡的几种状态都有哪些?

新建( new ):新创建了一个线程对象。

可运行( runnable ):线程对象创建后,其他线程(比如 main 线程)调用了该对象的 start ()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取 cpu 的使用权 。

运行( running ):可运行状态( runnable )的线程获得了 cpu 时间片( timeslice ) ,执行程序代码。

阻塞( block ):阻塞状态是指线程因为某种原因放弃了 cpu 使用权,也即让出了 cpu 时间片timeslice ,暂时停止运行。直到线程进入可运行( runnable )状态,才有机会再次获得 cpu 时间片timeslice 转到运行( running )状态。阻塞的情况分三种: ①等待阻塞:运行( running )的线程执行 o . wait ()方法, JVM 会把该线程放入等待队列( waitting queue )中。 ②同步阻塞:运行( running )的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则 JVM 会把该线程放入锁池( lock pool )中。 ③其他阻塞:运行( running )的线程执行 Thread . sleep ( long ms )或 t . join ()方法,或者发出了 I / O 请求时, JVM 会把该线程置为阻塞状态。 当 sleep ()状态超时、 join ()等待线程终止或者超时、或者 I / O 处理完毕时,线程重新转入可运行( runnable )状态。

死亡( dead ):线程 run ()、 main () 方法执行结束,或者因异常退出了 run ()方法,则该线程结束生命周期。死亡的线程不可再次复生。

请解释一下Java多线程回调是什么意思?

所谓回调,就是客户程序C调用服务程序S中的某个方法A,然后S又在某个时候反过来调用C中的某个方法B,对于C来说,这个B便叫做回调方法。

线程,进程,然后线程创建有很大开销,怎么优化?

可以使用线程池。

请介绍一下什么是生产者消费者模式?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6kWbuKV5-1631606888950)(…/images/308572_1537880635592_7142B8354CA8A352B2B805F997C71549)]

生产者消费者问题是线程模型中的经典问题:生产者和消费者在同一时间段内共用同一存储空间,生产者向空间里生产数据,而消费者取走数据。

优点:支持并发、解耦。

请简述一下实现多线程同步的方法?

可以使用synchronized、lock、volatile和ThreadLocal来实现同步。

多线程中的i++线程安全吗?请简述一下原因?

不安全。i++不是原子性操作。i++分为读取i值,对i值加一,再赋值给i++,执行期中任何一步都是有可能被其他线程抢占的。

请问什么是死锁(deadlock)?

两个线程或两个以上线程都在等待对方执行完毕才能继续往下执行的时候就发生了死锁。结果就是这些线程都陷入了无限的等待中。

例如,如果线程1锁住了A,然后尝试对B进行加锁,同时线程2已经锁住了B,接着尝试对A进行加锁,这时死锁就发生了。线程1永远得不到B,线程2也永远得不到A,并且它们永远也不会知道发生了这样的事情。为了得到彼此的对象(A和B),它们将永远阻塞下去。这种情况就是一个死锁。

请说明一下锁和同步的区别。

用法上的不同:
synchronized既可以加在方法上,也可以加载特定代码块上,而lock需要显示地指定起始位置和终止位置。
synchronized是托管给JVM执行的,lock的锁定是通过代码实现的,它有比synchronized更精确的线程语义。
性能上的不同:
lock接口的实现类ReentrantLock,不仅具有和synchronized相同的并发性和内存语义,还多了超时的获取锁、定时锁、等候和中断锁等。
在竞争不是很激烈的情况下,synchronized的性能优于ReentrantLock,竞争激烈的情况下synchronized的性能会下降的非常快,而ReentrantLock则基本不变。
锁机制不同:
synchronized获取锁和释放锁的方式都是在块结构中,当获取多个锁时,必须以相反的顺序释放,并且是自动解锁。而Lock则需要开发人员手动释放,并且必须在finally中释放,否则会引起死锁。

请说明一下synchronized的可重入怎么实现。

每个锁关联一个线程持有者和一个计数器。当计数器为0时表示该锁没有被任何线程持有,那么任何线程都都可能获得该锁而调用相应方法。当一个线程请求成功后,JVM会记下持有锁的线程,并将计数器计为1。此时其他线程请求该锁,则必须等待。而该持有锁的线程如果再次请求这个锁,就可以再次拿到这个锁,同时计数器会递增。当线程退出一个synchronized方法/块时,计数器会递减,如果计数器为0则释放该锁。

请讲一下非公平锁和公平锁在ReentrantLock里的实现过程是怎样的。

如果一个锁是公平的,那么锁的获取顺序就应该符合请求的绝对时间顺序,FIFO。对于非公平锁,只要CAS设置同步状态成功,则表示当前线程获取了锁,而公平锁还需要判断当前节点是否有前驱节点,如果有,则表示有线程比当前线程更早请求获取锁,因此需要等待前驱线程获取并释放锁之后才能继续获取锁。

请问JDK和JRE的区别是什么?

Java运行时环境(JRE)是将要执行Java程序的Java虚拟机。它同时也包含了执行applet需要的浏览器插件。Java开发工具包(JDK)是完整的Java软件开发包,包含了JRE,编译器和其他的工具(比如:JavaDoc,Java调试器),可以让开发者开发、编译、执行Java应用程序。

什么是反射?

反射就是把Java类中的各个组成部分进行解剖,并映射成一个个的Java对象,拿到这些对象后可以做一些事情。

既然说反射是解剖Java类中的各个组成部分,所以说咱们得知道一个类中有哪些部分?

例如,一个类有:构造方法,方法,成员变量(字段),等信息,利用反射技术咱们可以把这些组成部分映射成一个个对象

反射能干什么?

一般来说反射是用来做框架的,或者说可以做一些抽象度比较高的底层代码,反射在日常的开发中用到的不多,但是咱们还必须搞懂它,因为搞懂了反射以后,可以帮助咱们理解框架的一些原理。所以说有一句很经典的话:反射是框架设计的灵魂。现在说完这个可能还不太能理解,不急,等下说完一个快速入门的例子后,应该会稍微有点感觉

请说明一下JAVA中反射的实现过程和作用分别是什么?

JAVA语言编译之后会生成一个.class文件,反射就是通过字节码文件找到某一个类、类中的方法以及属性等。反射的实现主要借助以下四个类:Class:类的对象,Constructor:类的构造方法,Field:类中的属性对象,Method:类中的方法对象。

作用:反射机制指的是程序在运行时能够获取自身的信息。在JAVA中,只要给定类的名字,那么就可以通过反射机制来获取类的所有信息。

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

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

请问运行时异常与受检异常有什么区别?

异常表示程序运行过程中可能出现的非正常状态,运行时异常表示虚拟机的通常操作中可能遇到的异常,是一种常见运行错误,只要程序设计得没有问题通常就不会发生。受检异常跟程序运行的上下文环境有关,即使程序设计无误,仍然可能因使用的问题而引发。Java编译器要求方法必须声明抛出可能发生的受检异常,但是并不要求必须声明抛出未被捕获的运行时异常。异常和继承一样,是面向对象程序设计中经常被滥用的东西,在Effective Java中对异常的使用给出了以下指导原则:
- 不要将异常处理用于正常的控制流(设计良好的API不应该强迫它的调用者为了正常的控制流而使用异常)
- 对可以恢复的情况使用受检异常,对编程错误使用运行时异常
- 避免不必要的使用受检异常(可以通过一些状态检测手段来避免异常的发生)
- 优先使用标准的异常
- 每个方法抛出的异常都要有文档
- 保持异常的原子性
- 不要在catch中忽略掉捕获到的异常

请问什么是java序列化?以及如何实现java序列化?

序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。序列化是为了解决在对对象流进行读写操作时所引发的问题。
序列化的实现:将需要被序列化的类实现Serializable接口,该接口没有需要实现的方法,implements Serializable只是为了标注该对象是可被序列化的,然后使用一个输出流(如:FileOutputStream)来构造一个 ObjectOutputStream(对象流)对象,接着,使用ObjectOutputStream对象的writeObject(Object obj)方法就可以将参数为obj的对象写出(即保存其状态),要恢复的话则用输入流。

请问java中有几种类型的流?JDK为每种类型的流提供了一些抽象类以供继承,请说出他们分别是哪些类?

字节流,字符流。字节流继承于InputStream OutputStream,字符流继承于InputStreamReader OutputStreamWriter。在java.io包中还有许多其他的流,主要是为了提高性能和使用方便。

请说明一下Java中的异常处理机制的原理以及如何应用。

当JAVA 程序违反了JAVA的语义规则时,JAVA虚拟机就会将发生的错误表示为一个异常。违反语义规则包括2种情况。一种是JAVA类库内置的语义检查。例如数组下标越界,会引发IndexOutOfBoundsException;访问null的对象时会引发NullPointerException。另一种情况就是JAVA允许程序员扩展这种语义检查,程序员可以创建自己的异常,并自由选择在何时用throw关键字引发异常。所有的异常都是 java.lang.Thowable的子类。

请问error和exception有什么区别?

error 表示恢复不是不可能但很困难的情况下的一种严重问题。比如说内存溢出。不可能指望程序能处理这样的情况。
exception 表示一种设计或实现问题。也就是说,它表示如果程序运行正常,从不会发生的情况。

请问运行时的异常与一般情况下出现的异常有什么相同点和不同点?

异常表示程序运行过程中可能出现的非正常状态,运行时异常表示虚拟机的通常操作中可能遇到的异常,是一种常见运行错误。java编译器要求方法必须声明抛出可能发生的非运行时异常,但是并不要求必须声明抛出未被捕获的运行时异常。

请谈一谈Spring中自动装配的方式有哪些?

- no:不进行自动装配,手动设置Bean的依赖关系。
- byName:根据Bean的名字进行自动装配。
- byType:根据Bean的类型进行自动装配。
- constructor:类似于byType,不过是应用于构造器的参数,如果正好有一个Bean与构造器的参数类型相同则可以自动装配,否则会导致错误。
- autodetect:如果有默认的构造器,则通过constructor的方式进行自动装配,否则使用byType的方式进行自动装配。

自动装配没有自定义装配方式那么精确,而且不能自动装配简单属性(基本类型、字符串等),在使用时应注意。

请问什么是IoC和DI?并且简要说明一下DI是如何实现的?

IoC叫控制反转,是Inversion of Control的缩写,DI(Dependency Injection)叫依赖注入,是对IoC更简单的诠释。控制反转是把传统上由程序代码直接操控的对象的调用权交给容器,通过容器来实现对象组件的装配和管理。所谓的"控制反转"就是对组件对象控制权的转移,从程序代码本身转移到了外部容器,由容器来创建对象并管理对象之间的依赖关系。IoC体现了好莱坞原则 - “Don’t call me, we will call you”。依赖注入的基本原则是应用组件不应该负责查找资源或者其他依赖的协作对象。配置对象的工作应该由容器负责,查找资源的逻辑应该从应用组件的代码中抽取出来,交给容器来完成。DI是对IoC更准确的描述,即组件之间的依赖关系由容器在运行期决定,形象的来说,即由容器动态的将某种依赖关系注入到组件之中。

一个类A需要用到接口B中的方法,那么就需要为类A和接口B建立关联或依赖关系,最原始的方法是在类A中创建一个接口B的实现类C的实例,但这种方法需要开发人员自行维护二者的依赖关系,也就是说当依赖关系发生变动的时候需要修改代码并重新构建整个系统。如果通过一个容器来管理这些对象以及对象的依赖关系,则只需要在类A中定义好用于关联接口B的方法(构造器或setter方法),将类A和接口B的实现类C放入容器中,通过对容器的配置来实现二者的关联。
依赖注入可以通过setter方法注入(设值注入)、构造器注入和接口注入三种方式来实现,Spring支持setter注入和构造器注入,通常使用构造器注入来注入必须的依赖关系,对于可选的依赖关系,则setter注入是更好的选择,setter注入需要类提供无参构造器或者无参的静态工厂方法来创建对象。

请说明一下springIOC原理是什么?如果你要实现IOC需要怎么做?请简单描述一下实现步骤?

①IoC(Inversion of Control,控制倒转)。这是spring的核心,贯穿始终。所谓IoC,对于spring框架来说,就是由spring来负责控制对象的生命周期和对象间的关系。

IoC的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象。这一点是通过DI(Dependency Injection,依赖注入)来实现的。比如对象A需要操作数据库,以前我们总是要在A中自己编写代码来获得一个Connection对象,有了 spring我们就只需要告诉spring,A中需要一个Connection,至于这个Connection怎么构造,何时构造,A不需要知道。在系统运行时,spring会在适当的时候制造一个Connection,然后像打针一样,注射到A当中,这样就完成了对各个对象之间关系的控制。A需要依赖 Connection才能正常运行,而这个Connection是由spring注入到A中的,依赖注入的名字就这么来的。那么DI是如何实现的呢? Java 1.3之后一个重要特征是反射(reflection),它允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性,spring就是通过反射来实现注入的。

举个简单的例子,我们找女朋友常见的情况是,我们到处去看哪里有长得漂亮身材又好的女孩子,然后打听她们的兴趣爱好、qq号、电话号、ip号、iq号………,想办法认识她们,投其所好送其所要,这个过程是复杂深奥的,我们必须自己设计和面对每个环节。传统的程序开发也是如此,在一个对象中,如果要使用另外的对象,就必须得到它(自己new一个,或者从JNDI中查询一个),使用完之后还要将对象销毁(比如Connection等),对象始终会和其他的接口或类藕合起来。

②实现IOC的步骤

定义用来描述bean的配置的Java类

解析bean的配置,將bean的配置信息转换为上面的BeanDefinition对象保存在内存中,spring中采用HashMap进行对象存储,其中会用到一些xml解析技术

遍历存放BeanDefinition的HashMap对象,逐条取出BeanDefinition对象,获取bean的配置信息,利用Java的反射机制实例化对象,將实例化后的对象保存在另外一个Map中即可。

请简单说明一下依赖注入的方式有哪几种?以及这些方法如何使用?

1、Set注入 2、构造器注入 3、接口注入

请介绍一下bean的生命周期

Spring生命周期流程图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IvkLGw4f-1631606888950)(…/images/308572_1537967995043_4D7CF33471A392D943F00167D1C86C10)]

你知道TCP协议、IP协议、HTTP协议分别在哪一层吗?

运输层,网络层,应用层。

网络七层模型:

物理层,数据链路层,网络层,运输层,会话层,表现层,应用层

网络五层模型:

物理层,数据链路层,网络层,运输层,应用层

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9brVzCEw-1631606888951)(…/images/980266035_1565787665824_1ABB2DC3D76311944FFDBE9980FBAADD)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qzqZjKxu-1631606888952)(…/images/544019648_1568018341007_3939B87411E98E256AE502E17B360C20)]

请你说明一下,TCP协议的4次握手。

由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这个原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。

TCP的连接的拆除需要发送四个包,因此称为四次挥手(four-way handshake)。客户端或服务器均可主动发起挥手动作,在socket编程中,任何一方执行close()操作即可产生挥手操作。

(1)客户端A发送一个FIN,用来关闭客户A到服务器B的数据传送。

(2)服务器B收到这个FIN,它发回一个ACK,确认序号为收到的序号加1。和SYN一样,一个FIN将占用一个序号。

(3)服务器B关闭与客户端A的连接,发送一个FIN给客户端A。

(4)客户端A发回ACK报文确认,并将确认序号设置为收到序号加1。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vWTQZK6C-1631606888952)(…/images/308572_1538028144543_FCAC824D9C1E4301A60CF7D48A85E1C1)]

谈一下,为什么tcp为什么要建立连接?

保证可靠传输。

请你解释一下TCP为什么可靠一些

三次握手,超时重传,滑动窗口,拥塞控制。

请说明一下哪种应用场景会使用TCP协议,使用它的意义

当对网络通讯质量有要求的时候,比如:整个数据要准确无误的传递给对方,这往往用于一些要求可靠的应用,比如HTTP、HTTPS、FTP等传输文件的协议,POP、SMTP等邮件传输的协议

请说明一下http和https的区别

1)https协议要申请证书到ca,需要一定经济成本;
2) http是明文传输,https是加密的安全传输;
3) 连接的端口不一样,http是80,https是443;
4)http连接很简单,没有状态;https是ssl加密的传输,身份认证的网络协议,相对http传输比较安全。

请讲一下浏览器从接收到一个URL,到最后展示出页面,经历了哪些过程。

1.DNS解析

2.TCP连接

3.发送HTTP请求

4.服务器处理请求并返回HTTP报文

5.浏览器解析渲染页面

64位和32位的区别?

操作系统只是硬件和应用软件中间的一个平台。32位操作系统针对的32位的CPU设计。64位操作系统针对的64位的CPU设计。

进程和线程的区别是什么?

进程是执行着的应用程序,而线程是进程内部的一个执行序列。一个进程可以有多个线程。线程又叫做轻量级进程。

setter注入需要类提供无参构造器或者无参的静态工厂方法来创建对象。

请说明一下springIOC原理是什么?如果你要实现IOC需要怎么做?请简单描述一下实现步骤?

①IoC(Inversion of Control,控制倒转)。这是spring的核心,贯穿始终。所谓IoC,对于spring框架来说,就是由spring来负责控制对象的生命周期和对象间的关系。

IoC的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象。这一点是通过DI(Dependency Injection,依赖注入)来实现的。比如对象A需要操作数据库,以前我们总是要在A中自己编写代码来获得一个Connection对象,有了 spring我们就只需要告诉spring,A中需要一个Connection,至于这个Connection怎么构造,何时构造,A不需要知道。在系统运行时,spring会在适当的时候制造一个Connection,然后像打针一样,注射到A当中,这样就完成了对各个对象之间关系的控制。A需要依赖 Connection才能正常运行,而这个Connection是由spring注入到A中的,依赖注入的名字就这么来的。那么DI是如何实现的呢? Java 1.3之后一个重要特征是反射(reflection),它允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性,spring就是通过反射来实现注入的。

举个简单的例子,我们找女朋友常见的情况是,我们到处去看哪里有长得漂亮身材又好的女孩子,然后打听她们的兴趣爱好、qq号、电话号、ip号、iq号………,想办法认识她们,投其所好送其所要,这个过程是复杂深奥的,我们必须自己设计和面对每个环节。传统的程序开发也是如此,在一个对象中,如果要使用另外的对象,就必须得到它(自己new一个,或者从JNDI中查询一个),使用完之后还要将对象销毁(比如Connection等),对象始终会和其他的接口或类藕合起来。

②实现IOC的步骤

定义用来描述bean的配置的Java类

解析bean的配置,將bean的配置信息转换为上面的BeanDefinition对象保存在内存中,spring中采用HashMap进行对象存储,其中会用到一些xml解析技术

遍历存放BeanDefinition的HashMap对象,逐条取出BeanDefinition对象,获取bean的配置信息,利用Java的反射机制实例化对象,將实例化后的对象保存在另外一个Map中即可。

请简单说明一下依赖注入的方式有哪几种?以及这些方法如何使用?

1、Set注入 2、构造器注入 3、接口注入

请介绍一下bean的生命周期

Spring生命周期流程图:

[外链图片转存中…(img-IvkLGw4f-1631606888950)]

你知道TCP协议、IP协议、HTTP协议分别在哪一层吗?

运输层,网络层,应用层。

网络七层模型:

物理层,数据链路层,网络层,运输层,会话层,表现层,应用层

网络五层模型:

物理层,数据链路层,网络层,运输层,应用层

[外链图片转存中…(img-9brVzCEw-1631606888951)]

[外链图片转存中…(img-qzqZjKxu-1631606888952)]

请你说明一下,TCP协议的4次握手。

由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这个原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。

TCP的连接的拆除需要发送四个包,因此称为四次挥手(four-way handshake)。客户端或服务器均可主动发起挥手动作,在socket编程中,任何一方执行close()操作即可产生挥手操作。

(1)客户端A发送一个FIN,用来关闭客户A到服务器B的数据传送。

(2)服务器B收到这个FIN,它发回一个ACK,确认序号为收到的序号加1。和SYN一样,一个FIN将占用一个序号。

(3)服务器B关闭与客户端A的连接,发送一个FIN给客户端A。

(4)客户端A发回ACK报文确认,并将确认序号设置为收到序号加1。

[外链图片转存中…(img-vWTQZK6C-1631606888952)]

谈一下,为什么tcp为什么要建立连接?

保证可靠传输。

请你解释一下TCP为什么可靠一些

三次握手,超时重传,滑动窗口,拥塞控制。

请说明一下哪种应用场景会使用TCP协议,使用它的意义

当对网络通讯质量有要求的时候,比如:整个数据要准确无误的传递给对方,这往往用于一些要求可靠的应用,比如HTTP、HTTPS、FTP等传输文件的协议,POP、SMTP等邮件传输的协议

请说明一下http和https的区别

1)https协议要申请证书到ca,需要一定经济成本;
2) http是明文传输,https是加密的安全传输;
3) 连接的端口不一样,http是80,https是443;
4)http连接很简单,没有状态;https是ssl加密的传输,身份认证的网络协议,相对http传输比较安全。

请讲一下浏览器从接收到一个URL,到最后展示出页面,经历了哪些过程。

1.DNS解析

2.TCP连接

3.发送HTTP请求

4.服务器处理请求并返回HTTP报文

5.浏览器解析渲染页面

64位和32位的区别?

操作系统只是硬件和应用软件中间的一个平台。32位操作系统针对的32位的CPU设计。64位操作系统针对的64位的CPU设计。

进程和线程的区别是什么?

进程是执行着的应用程序,而线程是进程内部的一个执行序列。一个进程可以有多个线程。线程又叫做轻量级进程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值