Java基础面试题

Java概述

谈谈你对 Java 平台的理解?“Java 是解释执行”,这句话正确吗?

对Java平台的理解主要包括以下三个方面:面向对象和核心类库方面,跨平台方面和虚拟机和垃圾收集

面向对象和核心类库方面

  • Java是一门面向对象编程语言,极好地实现了面向对象理论,允许程序员以优雅的思维方式进行复杂的编程。

  • Java核心类库提供了包含集合容器、线程相关类、IO/NIO、J.U.C并发包,异常和安全等类库,极大地方便了程序员的开发; 

  • JDK提供的工具包含:基本的编译工具、虚拟机性能检测相关工具等。

跨平台方面

所谓的“一次编写,到处运行”(Write once, run anywhere),能够非常容易地获得跨平台能力。跟c/c++最大的不同点在于,c/c++编程是面向操作系统的,需要开发者极大地关心不同操作系统之间的差异性;而Java平台通过虚拟机屏蔽了操作系统的底层细节,使得开发者无需过多地关心不同操作系统之间的差异性。

通过增加一个间接的中间层来进行”解耦“是计算机领域非常常用的一种”艺术手法“,虚拟机是这样,操作系统是这样;

虚拟机和垃圾收集

另外就是Java虚拟机和垃圾收集(GC, Garbage Collection),Java 通过垃圾收集器(Garbage Collector)回收分配内存,大部分情况下,程序员不需要自己操心内存的分配和回收。

同时,围绕虚拟机的效率问题展开,将涉及到一些优化技术,例如:JIT、AOT。因为如果虚拟机加载字节码后,一行一行地解释执行,这势必会影响执行效率。所以,对于这个运行环节,虚拟机会进行一些优化处理,例如JIT技术,会将热点代码编译成机器码。而AOT技术,是在运行前,通过工具直接将字节码转换为机器码。

对于“Java 是解释执行”这句话,这个说法不太准确。我们开发的 Java 的源代码,首先通过 Javac 编译成为字节码(bytecode),然后,在运行时,通过 Java 虚拟机(JVM)内嵌的解释器将字节码转换成为最终的机器码。但是常见的 JVM,比如我们大多数情况使用的 Oracle JDK 提供的 Hotspot JVM,都提供了 JIT(Just-In-Time)编译器,也就是通常所说的动态编译器,JIT 能够在运行时将热点代码编译成机器码,这种情况下部分热点代码就属于编译执行,而不是解释执行了。

JVM、JRE和JDK的关系

JVM

Java Virtual Machine是Java虚拟机,Java程序需要运行在虚拟机上,不同的平台有自己的虚拟机,因此Java语言可以实现跨平台。

JRE

Java Runtime Environment包括Java虚拟机和Java程序所需的核心类库等。核心类库主要是java.lang包:包含了运行Java程序必不可少的系统类,如包装类型、基本数学函数、字符串处理、线程、异常处理类等,系统缺省加载这个包。如果想要运行一个开发好的Java程序,计算机中只需要安装JRE即可。

JDK

Java Development Kit是提供给Java开发人员使用的,其中包含了Java的开发工具,也包括了JRE。所以安装了JDK,就无需再单独安装JRE了。其中的开发工具:编译工具(javac.exe),打包工具(jar.exe)等

JVM&JRE&JDK关系图

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

所谓跨平台性,是指java语言编写的程序,一次编译后,可以在多个系统平台上运行。

实现原理:Java程序是通过虚拟机在系统平台上运行的,只要该系统安装相应的java虚拟机,该系统就可以运行java程序。

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

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

采用字节码的好处

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

先看下java中的编译器和解释器

Java源程序经过编译器编译后变成字节码,字节码由虚拟机解释执行,虚拟机将每一条要执行的字节码送给解释器,解释器将其翻译成特定机器上的机器码,然后在特定的机器上运行

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

基础语法

Java有哪些数据类型

定义:Java语言是强类型语言,对于每一种数据都定义了明确的具体的数据类型,在内存中分配了不同大小的内存空间。

分类

  • 基本数据类型

    • 整数类型(byte,short,int,long)

    • 浮点类型(float,double)

    • 数值型

    • 字符型(char)

    • 布尔型(boolean)

  • 引用数据类型

    • 类(class)

    • 接口(interface)

    • 数组([])

Java基本数据类型图

访问修饰符 public,private,protected,以及不写(默认)时的区别

定义:Java中可以使用访问修饰符来保护对类、变量、方法的访问。Java 支持 4 种不同的访问权限。

分类

private : 在同一类内可见。使用对象:变量、方法。注意:不能修饰类(外部类)

default (即缺省,什么也不写,不使用任何关键字): 在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。

protected : 对同一包内的类和所有子类可见。使用对象:变量、方法。注意:不能修饰类(外部类)。

public : 对所有类可见。使用对象:类、接口、变量、方法

访问修饰符图

&和&&的区别

&运算符有两种用法:(1)按位与;(2)逻辑与。

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

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

@$final finally finalize区别

  • final是一个修饰符关键字,可以修饰类、方法、变量,修饰类表示该类不能被继承、修饰方法表示该方法不能被重写、修饰变量表示该变量是一个常量不能被重新赋值。

  • finally是一个异常处理的关键字,一般作用在try-catch-finally代码块中,在处理异常的时候,通常我们将一定要执行的代码放在finally代码块中,表示不管是否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码。

  • finalize是属于Object类的一个方法,该方法一般由垃圾回收器来调用,它的设计目的是保证对象在被垃圾收集前完成特定资源的回收。finalize 机制现在已经不推荐使用,并且在 JDK 9 被标记为 deprecated。

this super关键字的用法

this是指向对象本身的一个指针。

this的用法在java中大体可以分为3种:

  1. 普通的直接引用,this相当于是指向当前对象本身。

  2. 当形参与成员名字重名,用this来区分

  3. 引用本类的构造函数

super可以理解为是指向自己父类对象的一个指针,而这个超类指的是离自己最近的一个父类。

super也有三种用法:

  1. 普通的直接引用,super相当于是指向当前对象的父类引用,这样就可以用super.xxx来引用父类的成员。

  2. 当子类中的成员变量或方法与父类中的成员变量或方法同名时,用super进行区分

  3. 引用父类构造函数

static存在的主要意义

static的主要意义是在于创建独立于具体对象的变量或者方法。即使没有创建对象,也能使用属性和调用方法

static关键字还有一个比较关键的作用就是 用来形成静态代码块来优化程序性能。static块可以置于类中的任何地方,类中可以有多个static块。在类初次被加载的时候,会按照static块的顺序来执行每个static块,并且只会执行一次。因此,很多时候会将一些只需要进行一次初始化的操作放在static代码块中。

break ,continue ,return 的区别及作用

break 结束当前的循环体

continue 跳出本次循环,继续执行下次循环-

return 结束当前的方法,直接返回

面向对象

面向对象和面向过程的区别

面向过程

优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发,性能是最重要的因素。

缺点:没有面向对象易维护、易复用、易扩展

面向对象

优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护

缺点:性能比面向过程低

面向过程是具体化的,流程化的,解决一个问题,你需要一步一步的分析,一步一步的实现

面向对象是抽象化的,模型化的,你只需抽象出一个类,这是一个封闭的盒子,在这里你拥有数据也拥有解决问题的方法。需要什么功能直接使用就可以了,不必去一步一步的实现,至于这个功能是如何实现的,我们可以不用太关心,会用就可以了

面向对象的底层其实还是面向过程,把面向过程抽象成类,然后封装,方便我们使用的就是面向对象了。

@$面向对象三大特性

面向对象的特征主要有以下几个方面

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

其中Java 面向对象编程三大特性:封装 继承 多态

封装:封装是把一个对象的属性私有化,隐藏内部的实现细节,同时提供一些可以被外界访问属性的方法。通过封装可以使程序便于使用,提高复用性和安全性

继承:继承是使用已存在的类的定义作为基础,建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过继承可以提高代码复用性。继承是多态的前提。

关于继承如下 3 点请记住

  1. 子类拥有父类非 private 的属性和方法。

  2. 子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。

  3. 子类可以用自己的方式实现父类的方法。

多态性:父类或接口定义的引用变量可以指向子类或具体实现类的实例对象。多态提高了程序的扩展性。一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在程序运行期间才能决定。

Java实现多态有三个必要条件:继承、重写、向上转型。

  • 继承:在多态中必须存在有继承关系的子类和父类。

  • 重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。

  • 向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才能够具备调用父类和子类的方法的技能。

只有满足了上述三个条件,我们才能够在同一个继承结构中使用统一的逻辑实现代码处理不同的对象,从而达到执行不同的行为。

@$抽象类和接口的对比

抽象类是用来捕捉子类的通用特性的,实现代码重用。接口是抽象方法的集合,利用接口可以达到 API 定义和实现分离的目的,提供程序的扩展性和可维护性。

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

相同点

  • 接口和抽象类都不能实例化

  • 都位于继承的顶端,用于被其他类实现或继承

  • 都包含抽象方法,其子类都必须重写这些抽象方法

不同点

参数抽象类接口
声明抽象类使用abstract关键字声明接口使用interface关键字声明
实现子类使用extends关键字来继承抽象类。如果一个类继承了抽象类,那么该子类必须实现抽象类的所有抽象方法。子类使用implements关键字来实现接口。如果一个类实现了接口,那么该子类必须实现父接口的所有方法。
构造器抽象类可以有构造器接口不能有构造器
访问修饰符抽象类中的方法可以是任意访问修饰符接口方法默认修饰符是public。并且不允许定义为 private 或者 protected
字段声明抽象类的字段声明可以是任意的接口的字段默认都是 static 和 final 的
多继承一个类最多只能继承一个抽象类一个类可以实现多个接口

备注:Java8中接口中引入默认方法和静态方法,以此来减少抽象类和接口之间的差异。现在,我们可以为接口提供默认实现的方法了,并且不用强制子类来实现它。

接口和抽象类各有优缺点,在接口和抽象类的选择上,必须遵守下面的几个原则:

  • 抽象类用来定义某个领域的固有属性,即抽象类表示它是什么,接口用来定义某个领域的扩展功能,即接口表示它能做什么。

  • 当需要为子类提供公共的实现代码时,应优先考虑抽象类。因为抽象类中的非抽象方法可以被子类继承,使实现功能的代码更简洁。

  • 当注重代码的扩展性和可维护性时,应当优先采用接口。①接口与实现类之间可以不存在任何层次关系,接口可以实现毫不相关类的行为,比抽象类的使用更加方便灵活;②接口只关心对象之间的交互方法,而不关心对象所对应的具体类。接口是程序之间的一个协议,比抽象类的使用更安全、清晰。一般使用接口的情况更多。

@$成员变量与局部变量的区别有哪些

变量:在程序执行的过程中,其值可以在某个范围内发生改变的量。从本质上讲,变量其实是内存中的一小块区域

各变量联系与区别

  • 成员变量:作用范围是整个类,相当于C中的全局变量,定义在方法体和语句块之外,一般定义在类的声明之下;成员变量包括实例变量和静态变量(类变量);

  • 实例变量:独立于与方法之外的变量,无static修饰,声明在一个类中,但在方法、构造方法和语句块之外,数值型变量默认值为0,布尔型默认值为false,引用类型默认值为null;

  • 静态变量(类变量):独立于方法之外的变量,用static修饰,默认值与实例变量相似,一个类中只有一份,属于对象共有,存储在静态存储区,经常被声明为常量,调用一般是类名.静态变量名,也可以用对象名.静态变量名调用;

  • 局部变量:类的方法中的变量,访问修饰符不能用于局部变量,声明在方法、构造方法或语句块中,在栈上分配,无默认值,必须初始化后才能使用;

成员变量和局部变量的区别

成员变量局部变量
作用域作用范围是整个类在方法或者语句块内有效
存储位置和生命周期随着对象的创建而存在,随着对象的消失而消失,存储在堆内存中在方法被调用的时候存在,方法调用完会自动释放,存储在栈内存中
初始值有默认初始值没有默认初始值,使用前必须赋值
使用原则就近原则,首先在局部位置找,有就使用;接着在成员位置找

@$重载(Overload)和重写(Override)的区别。重载的方法能否根据返回类型进行区分?

方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。

重载:发生在同一个类中,方法名相同参数列表不同(参数类型不同、个数不同、顺序不同),与方法返回值和访问修饰符无关,即重载的方法不能根据返回类型进行区分

重写:发生在父子类中,方法名、参数列表必须相同,返回值小于等于父类,抛出的异常小于等于父类,访问修饰符大于等于父类(里氏代换原则);如果父类方法访问修饰符为private则子类中就不是重写。

@$== 和 equals 的区别是什么

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

equals() : 它的作用也是判断两个对象是否相等。有两种使用情况:

情况1:类没有覆盖 equals() 方法,等价于通过“==”比较这两个对象。

情况2:类覆盖了 equals() 方法。一般我们都覆盖 equals() 方法来判断两个对象的内容相等;若它们的内容相等,则返回 true (即,认为这两个对象相等)。

举个例子

public class test1 {
   public static void main(String[] args) {
       String a = new String("ab"); // a 为一个引用
       String b = new String("ab"); // b为另一个引用,对象的内容一样
       String aa = "ab"; // 放在常量池中
       String bb = "ab"; // 从常量池中查找
       if (aa == bb) // true
           System.out.println("aa==bb");
       if (a == b) // false,非同一对象
           System.out.println("a==b");
       if (a.equals(b)) // true
           System.out.println("aEQb");
       if (42 == 42.0) { // true
           System.out.println("true");
      }
  }
}

说明:

  • String中的equals方法是被重写过的,因为object的equals方法是比较的对象的内存地址,而String的equals方法比较的是对象的值。

  • 当创建String类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个String对象。

@$hashCode 与 equals

hashCode()介绍

hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int类型的整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object类中,这就意味着Java中的任何类都包含有hashCode()函数。

散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)

为什么要有 hashCode

我们以“HashSet 如何检查重复”为例子来说明为什么要有 hashCode

当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashcode 值作比较,如果没有相同的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 equals()方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。

先进行 hashcode 比较,后进行 equals 方法比较的目的:可以大大减少了 equals 方法比较的次数,相应就大大提高了执行速度。

hashCode()与equals()的相关规定

如果两个对象相等,则hashcode一定也是相同的

两个对象相等,对两个对象分别调用equals方法都返回true

两个对象有相同的hashcode值,它们不一定是相等的

因此,当重写equals方法后有必要将hashCode方法也重写,这样做才能保证不违背hashCode方法中“相同对象必须有相同哈希值”的约定。

值传递和引用传递有什么区别

值传递,按值调用(call by value):指的是在方法调用时,传递的参数是值的拷贝,传递后就互不相关了。

引用传递,引用调用(call by reference):指的是在方法调用时,传递的参数是引用变量所对应的内存地址。传递前和传递后都指向同一个引用(也就是同一个内存空间)。

一个方法可以修改引用传递所对应的变量值,而不能修改值传递所对应的变量值

为什么 Java 中只有值传递

Java采用按值调用。也就是说,方法得到的是所有参数值的一个拷贝,也就是说,方法不能修改传递给它的任何参数变量的内容。

下面通过 3 个例子来给大家说明

example 1

public static void main(String[] args) {
   int num1 = 10;
   int num2 = 20;

   swap(num1, num2);

   System.out.println("num1 = " + num1);
   System.out.println("num2 = " + num2);
}

public static void swap(int a, int b) {
   int temp = a;
   a = b;
   b = temp;

   System.out.println("a = " + a);
   System.out.println("b = " + b);
}

结果

a = 20
b = 10
num1 = 10
num2 = 20

解析

在swap方法中,a、b的值进行交换,并不会影响到 num1、num2。因为a、b中的值,只是从 num1、num2 的复制过来的。也就是说,a、b相当于num1、num2 的副本,副本的内容无论怎么修改,都不会影响到原件本身。

通过上面例子,我们已经知道了一个方法不能修改一个基本数据类型的参数,而对象引用作为参数就不一样,请看 example2.

example 2

   public static void main(String[] args) {
       int[] arr = { 1, 2, 3, 4, 5 };
       System.out.println(arr[0]);
       change(arr);
       System.out.println(arr[0]);
  }

   public static void change(int[] array) {
       // 将数组的第一个元素变为0
       array[0] = 0;
  }

结果

1
0

解析

array 被初始化 arr 的拷贝也就是一个对象的引用,也就是说 array 和 arr 指向的时同一个数组对象。因此,外部对引用对象的改变会反映到所对应的对象上。

通过 example2 我们已经看到,实现一个改变对象参数状态的方法并不是一件难事。理由很简单,方法得到的是对象引用的拷贝,对象引用及其他的拷贝同时引用同一个对象。

很多程序设计语言(特别是,C++和Pascal)提供了两种参数传递的方式:值调用和引用调用。有些程序员(甚至本书的作者)认为Java程序设计语言对对象采用的是引用调用,实际上,这种理解是不对的。由于这种误解具有一定的普遍性,所以下面给出一个反例来详细地阐述一下这个问题。

example 3

public class Test {

   public static void main(String[] args) {
       // TODO Auto-generated method stub
       Student s1 = new Student("小张");
       Student s2 = new Student("小李");
       Test.swap(s1, s2);
       System.out.println("s1:" + s1.getName());
       System.out.println("s2:" + s2.getName());
  }

   public static void swap(Student x, Student y) {
       Student temp = x;
       x = y;
       y = temp;
       System.out.println("x:" + x.getName());
       System.out.println("y:" + y.getName());
  }
}

结果

x:小李
y:小张
s1:小张
s2:小李

解析

交换之前:

 

交换之后:

通过上面两张图可以很清晰的看出:方法并没有改变存储在变量 s1 和 s2 中的对象引用。swap方法的参数x和y被初始化为两个对象引用的拷贝,这个方法交换的是这两个拷贝

总结

Java对对象采用的不是引用调用,实际上,对象引用是按值传递的。

下面再总结一下Java中方法参数的使用情况:

  • 一个方法不能修改一个基本数据类型的参数(即数值型或布尔型)

  • 一个方法可以改变一个对象参数的状态。

  • 一个方法不能让对象参数引用一个新的对象。

IO流

java 中 IO 流分为几种?

  • 按照流的流向分,可以分为输入流和输出流;

  • 按照操作单元划分,可以分为字节流和字符流;

  • 按照流的角色划分,可以分为节点流和处理流。

Java IO流共涉及40多个类,这些类看上去很杂乱,但实际上很有规则,而且彼此之间存在非常紧密的联系, Java IO流的40多个类大部分都是从如下4个抽象类基类中派生出来的。

  • InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。

  • OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。

@$BIO,NIO,AIO 有什么区别?

在讲 BIO,NIO,AIO 之前先来回顾一下这样几个概念:同步与异步,阻塞与非阻塞。

同步与异步

  • 同步: 同步就是发起一个请求,被调用者未处理完请求之前,调用不返回。

  • 异步: 异步就是发起一个请求,立刻得到被调用者的响应表示已接收到请求,但是被调用者并没有返回请求处理结果,此时我们可以处理其他的请求,被调用者通过事件和回调等机制来通知调用者其返回结果。

同步和异步的区别在于调用者需不需要等待被调用者的处理结果。

阻塞和非阻塞

  • 阻塞: 阻塞就是发起一个请求,调用者一直等待请求结果返回,也就是当前线程会被挂起,无法从事其他任务,只有当返回结果才能继续。

  • 非阻塞: 非阻塞就是发起一个请求,调用者不用一直等着结果返回,可以先去干其他事情。

阻塞和非阻塞的区别在于调用者的线程需不需要挂起。

Java IO 方式有很多种,基于不同的 IO 抽象模型和交互方式,可以进行简单区分。

  • BIO(jdk1.4之前):Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它基于流模型实现,一个连接一个线程,客户端有连接请求时,服务器端就需要启动一个线程进行处理,线程开销大。伪异步IO:将请求连接放入线程池,一对多,但线程还是很宝贵的资源。它的特点是模式简单使用方便,但并发处理能力低,容易成为应用性能的瓶颈。BIO是面向流的,BIO的Stream是单向的。

    很多时候,人们也把 java.net 包下的部分网络 API,比如 Socket、ServerSocket、HttpURLConnection 也归类到同步阻塞 IO,因为网络通信同样是 IO 行为。

  • NIO(jdk1.4 之后  linux的多路复用技术 select 模式):Non IO 同步非阻塞 IO,是传统 IO 的升级,提供了 Channel、Selector、Buffer 等新的抽象,客户端和服务器端通过 Channel(通道)通讯,实现了多路复用。客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。Mina2.0和Netty5.0网络通信框架都是通过NIO实现的网络通信。NIO是面向缓冲区的,NIO的channel是双向的。

    NIO 能解决什么问题?

    通过一个固定大小的线程池,来负责管理工作线程,避免频繁创建、销毁线程的开销,这是我们构建并发服务的典型方式。

    NIO 则是利用了单线程轮询事件的机制,通过高效地定位就绪的 Channel,来决定做什么,仅仅 select 阶段是阻塞的,可以有效避免大量客户端连接时,频繁线程切换带来的问题,应用的扩展能力有了非常大的提高。

  • AIO(jdk  1.7过后 又叫NIO 2):Asynchronous IO 异步非堵塞 IO,是 NIO 的升级,异步 IO 的操作基于事件和回调机制,性能是最好的。底层实现是通过epoll的I/O多路复用机制。

BIO、NIO、AIO 实现原理

BIO 实现原理

BIO模型是最早的jdk提供的一种处理网络连接请求的模型,是同步阻塞结构,通常由一个独立的Acceptor线程负责监听客户端的连接,它接收到客户端连接请求之后为每个客户端创建一个新的线程进行链路处理,处理完成后,通过输出流返回应答给客户端,线程销毁。即典型的一请求一应答模型

BIO是同步阻塞式IO,通常在while循环中服务端会调用accept方法等待接收客户端的连接请求,一旦接收到一个连接请求,就可以建立通信套接字在这个通信套接字上进行读写操作,此时不能再接收其他客户端连接请求,只能等待同当前连接的客户端的操作执行完成。

如果BIO要能够同时处理多个客户端请求,就必须使用多线程,即每次accept阻塞等待来自客户端请求,一旦受到连接请求就建立通信套接字同时开启一个新的线程来处理这个套接字的数据读写请求,然后立刻又继续accept等待其他客户端连接请求,即为每一个客户端连接请求都创建一个线程来单独处理。

NIO 实现原理

nio模型事件处理流程:

  • Acceptor注册Selector,监听accept事件;

  • 当客户端连接后,触发accept事件;

  • 服务器构建对应的Channel,并在其上注册Selector,监听读写事件;

  • 当发生读写事件后,进行相应的读写处理。

NIO本身是基于事件驱动思想来完成的,其主要想解决的是BIO的大并发问题,即在使用同步I/O的网络应用中,如果要同时处理多个客户端请求,或是在客户端要同时和多个服务器进行通讯,就必须使用多线程来处理。也就是说,将每一个客户端请求分配给一个线程来单独处理。这样做虽然可以达到我们的要求,但同时又会带来另外一个问题。由于每创建一个线程,就要为这个线程分配一定的内存空间(也叫工作存储器),而且操作系统本身也对线程的总数有一定的限制。如果客户端的请求过多,服务端程序可能会因为不堪重负而拒绝客户端的请求,甚至服务器可能会因此而瘫痪。

NIO基于Reactor,当selector有流可读或可写入socket时,操作系统会相应的通知应用程序进行处理,应用再将流读取到缓冲区或写入操作系统

也就是说,这个时候,已经不是一个连接就要对应一个处理线程了,而是有效的请求,对应一个线程,当连接没有数据时,是没有工作线程来处理的

Reactor单线程模型

这是最简单的单Reactor单线程模型。Reactor线程负责多路分离套接字、accept新连接,并分派请求到处理器链中。该模型适用于处理器链中业务处理组件能快速完成的场景。不过,这种单线程模型不能充分利用多核资源,所以实际使用的不多。

这个模型和上面的NIO流程很类似,只是将消息相关处理独立到了Handler中去了。

虽然上面说到NIO一个线程就可以支持所有的IO处理。但是瓶颈也是显而易见的。我们看一个客户端的情况,如果这个客户端多次进行请求,如果在Handler中的处理速度较慢,那么后续的客户端请求都会被积压,导致响应变慢!所以引入了Reactor多线程模型。

Reactor多线程模型

相比上一种模型,该模型在处理器链部分采用了多线程(线程池):

Reactor多线程模型就是将Handler中的IO操作和非IO操作分开,操作IO的线程称为IO线程,非IO操作的线程称为工作线程。这样的话,客户端的请求会直接被丢到线程池中,客户端发送请求就不会堵塞。

但是当用户进一步增加的时候,Reactor会出现瓶颈!因为Reactor既要处理IO操作请求,又要响应连接请求。为了分担Reactor的负担,所以引入了主从Reactor模型。

主从Reactor多线程模型

主从Reactor多线程模型是将Reactor分成两部分,mainReactor负责监听server socket,accept新连接,并将建立的socket分派给subReactor。subReactor负责多路分离已连接的socket,读写网络数据,对业务处理功能,其扔给worker线程池完成。通常,subReactor个数上可与CPU个数等同:

可见,主Reactor用于响应连接请求,从Reactor用于处理IO操作请求。

AIO

与NIO不同,当进行读写操作时,只须直接调用API的read或write方法即可。这两种方法均为异步的,对于读操作而言,当有流可读取时,操作系统会将可读的流传入read方法的缓冲区,并通知应用程序;对于写操作而言,当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序

AIO是一种接口标准,各家操作系统可以实现也可以不实现。在不同操作系统上在高并发情况下最好都采用操作系统推荐的方式。Linux上还没有真正实现网络方式的AIO。

异步IO则采用“订阅-通知”模式:即应用程序向操作系统注册IO监听,然后继续做自己的事情。当操作系统发生IO事件,并且准备好数据后,在主动通知应用程序,触发相应的函数。也可以如下图理解:

和同步IO一样,异步IO也是由操作系统进行支持的。微软的windows系统提供了一种异步IO技术:IOCP(I/O CompletionPort,I/O完成端口);Linux下由于没有这种异步IO技术,所以使用的是epoll对异步IO进行模拟。

反射

什么是反射机制?

JAVA反射机制是在程序运行过程中,对于任意一个类或对象,都能够知道这个类或对象的所有属性和方法,这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。

静态编译和动态编译

  • 静态编译:在编译时确定类型,绑定对象

  • 动态编译:在运行时确定类型,绑定对象

反射机制优缺点

  • 优点 :运行期类型的判断,动态加载类,提高代码的灵活性。

  • 缺点:性能瓶颈:反射相当于一系列解释操作,通知 JVM 要做的事情,性能比直接的java代码要慢很多。

@$反射为什么慢

  1. 反射调用过程中会产生大量的临时对象,这些对象会占用内存,可能会导致频繁 gc,从而影响性能。

  2. 反射调用方法时会从方法数组中遍历查找,并且检查可见性等操作会比较耗时。

  3. 反射在达到一定次数时,会动态编写字节码并加载到内存中,这个字节码没有经过编译器优化,也不能享受 JIT 优化。

  4. 反射一般会涉及自动装箱/拆箱和类型转换,都会带来一定的资源开销。

反射机制的应用场景有哪些?

反射是框架设计的灵魂。

在我们平时的项目开发过程中,基本上很少会直接使用到反射机制,但这不能说明反射机制没有用,实际上有很多设计、开发都与反射机制有关,例如模块化的开发,通过反射去调用对应的字节码;动态代理设计模式也采用了反射机制,还有我们日常使用的 Spring/Hibernate 等框架也大量使用到了反射机制。

举例:①我们在使用JDBC连接数据库时使用Class.forName()通过反射加载数据库的驱动程序;②Spring框架也用到很多反射机制,最经典的就是xml的配置模式。Spring 通过 XML 配置模式装载 Bean 的过程:1) 将程序内所有 XML 或 Properties 配置文件加载入内存中; 2)Java类里面解析xml或properties里面的内容,得到对应实体类的字节码字符串以及相关的属性信息; 3)使用反射机制,根据这个字符串获得某个类的Class实例; 4)动态配置实例的属性

网络编程

TCP/IP的五层体系结构分别是什么?

应用层

应用层( application-layer )的任务是通过应用进程间的交互来完成特定网络应用。应用层协议定义的是应用进程间的通信和交互的规则

对于不同的网络应用需要不同的应用层协议。在互联网中应用层协议很多,如域名系统 DNS,支持万维网应用的 HTTP 协议,支持电子邮件的 SMTP 协议等等。

运输层

运输层(transport layer)的主要任务就是负责向两台主机进程之间的通信提供通用的数据传输服务。应用进程利用该服务传送应用层报文。

运输层主要使用一下两种协议

  1. 传输控制协议-TCP:提供面向连接的,可靠的数据传输服务。

  2. 用户数据协议-UDP:提供无连接的,尽最大努力的数据传输服务(不保证数据传输的可靠性)。

UDPTCP
是否连接无连接面向连接
是否可靠不可靠传输,不使用流量控制和拥塞控制可靠传输,使用流量控制和拥塞控制
连接对象个数支持一对一,一对多,多对一和多对多交互通信只能是一对一通信
传输方式面向报文面向字节流
首部开销首部开销小,仅8字节首部最小20字节,最大60字节
场景适用于实时应用(IP电话、视频会议、直播等)适用于要求可靠传输的应用,例如文件传输

每一个应用层(TCP/IP参考模型的最高层)协议一般都会使用到两个传输层协议之一:

运行在TCP协议上的协议:

  • HTTP(Hypertext Transfer Protocol,超文本传输协议),主要用于普通浏览。

  • HTTPS(HTTP over SSL,安全超文本传输协议),HTTP协议的安全版本。

  • FTP(File Transfer Protocol,文件传输协议),用于文件传输。

  • POP3(Post Office Protocol, version 3,邮局协议),收邮件用。

  • SMTP(Simple Mail Transfer Protocol,简单邮件传输协议),用来发送电子邮件。

  • TELNET(Teletype over the Network,网络电传),通过一个终端(terminal)登陆到网络。

  • SSH(Secure Shell,用于替代安全性差的TELNET),用于加密安全登陆用。

运行在UDP协议上的协议:

  • BOOTP(Boot Protocol,启动协议),应用于无盘设备。

  • NTP(Network Time Protocol,网络时间协议),用于网络同步。

  • DHCP(Dynamic Host Configuration Protocol,动态主机配置协议),动态配置IP地址。

运行在TCPUDP协议上:

  • DNS(Domain Name Service,域名服务),用于完成地址查找,邮件转发等工作。

网络层

网络层的任务就是选择合适的网间路由和交换结点,确保计算机通信的数据及时传送。在发送数据时,网络层把运输层产生的报文段或用户数据报封装成分组和包进行传送。在 TCP/IP 体系结构中,由于网络层使用 IP 协议,因此分组也叫 IP 数据报 ,简称数据报。

互联网是由大量的异构(heterogeneous)网络通过路由器(router)相互连接起来的。互联网使用的网络层协议是无连接的网际协议(Intert Prococol)和许多路由选择协议,因此互联网的网络层也叫做网际层或 IP 层。

数据链路层

数据链路层(data link layer)通常简称为链路层。两台主机之间的数据传输,总是在一段一段的链路上传送的,这就需要使用专门的链路层的协议

在两个相邻节点之间传送数据时,数据链路层将网络层交下来的 IP 数据报组装成帧,在两个相邻节点间的链路上传送帧。每一帧包括数据和必要的控制信息(如同步信息,地址信息,差错控制等)。

在接收数据时,控制信息使接收端能够知道一个帧从哪个比特开始和到哪个比特结束。

一般的web应用的通信传输流是这样的:

发送端在层与层之间传输数据时,每经过一层时会被打上一个该层所属的首部信息。反之,接收端在层与层之间传输数据时,每经过一层时会把对应的首部信息去除。

物理层

在物理层上所传送的数据单位是比特。物理层(physical layer)的作用是实现相邻计算机节点之间比特流的透明传送,尽可能屏蔽掉具体传输介质和物理设备的差异。使其上面的数据链路层不必考虑网络的具体传输介质是什么。“透明传送比特流”表示经实际电路传送后的比特流没有发生变化,对传送的比特流来说,这个电路好像是看不见的。

四层协议,五层协议,七层协议的区别

为了使不同体系结构的计算机网络都能互联,国际标准化组织 ISO 于1977年提出了一个试图使各种计算机在世界范围内互联成网的标准框架,即著名的开放系统互联基本参考模型 OSI/RM,简称为OSI。

OSI 的七层协议体系结构的概念清楚,理论也较完整,但它既复杂又不实用,TCP/IP 体系结构则不同,但它现在却得到了非常广泛的应用。TCP/IP 是一个四层体系结构,它包含应用层,运输层,网际层和网络接口层(用网际层这个名字是强调这一层是为了解决不同网络的互连问题),不过从实质上讲,TCP/IP 只有最上面的三层,因为最下面的网络接口层并没有什么具体内容,因此在学习计算机网络的原理时往往采用折中的办法,即综合 OSI 和 TCP/IP 的优点,采用一种只有五层协议的体系结构,这样既简洁又能将概念阐述清楚,有时为了方便,也可把最底下两层称为网络接口层。

四层协议,五层协议和七层协议的关系如下:

  • TCP/IP是一个四层的体系结构,主要包括:应用层、运输层、网际层和网络接口层。

  • 五层协议的体系结构主要包括:应用层、运输层、网络层,数据链路层和物理层。

  • OSI七层协议模型主要包括是:应用层(Application)、表示层(Presentation)、会话层(Session)、运输层(Transport)、网络层(Network)、数据链路层(Data Link)、物理层(Physical)。

注:五层协议的体系结构只是为了介绍网络原理而设计的,实际应用还是 TCP/IP 四层体系结构。

@$TCP的三次握手四次挥手的过程

TCP是一种面向连接的、可靠的、基于字节流的传输层通信协议,它会处理IP层或以下的层的丢包、重复以及错误问题。在发送数据前,通信双方必须在彼此间建立一条连接。所谓的“连接”,其实是客户端和服务端保存的一份关于对方的信息,如ip地址、端口号等,这些信息放在TCP头部

一个TCP连接通常分为三个阶段:连接、数据传输、退出(关闭)。通过三次握手建立一个连接,通过四次挥手来关闭一个连接。当一个连接被建立或被终止时,交换的报文段只包含TCP头部,而没有数据。

TCP报文的头部结构

在了解TCP连接之前先来了解一下TCP报文的头部结构。

上图中有几个字段需要重点介绍下:

(1)序号:seq序号,占32位,用来标识从TCP源端向目的端发送的字节流,发起方发送数据时对此进行标记。

(2)确认序号:ack序号,占32位,只有ACK标志位为1时,确认序号字段才有效,ack=seq+1。

(3)标志位:共6个,即URG、ACK、PSH、RST、SYN、FIN等,具体含义如下:

  • ACK:确认序号有效。

  • FIN:释放一个连接。

  • PSH:接收方应该尽快将这个报文交给应用层。

  • RST:重置连接。

  • SYN:发起一个新连接。

  • URG:紧急指针(urgent pointer)有效。

需要注意的是:

  • 不要将确认序号ack与标志位中的ACK搞混了。

  • 确认方ack=发起方seq+1,两端配对。

三次握手

三次握手的本质是确认通信双方收发数据的能力

首先,我让信使运输一份信件给对方,对方收到了,那么他就知道了我的发件能力和他的收件能力是可以的

于是他给我回信,我若收到了,我便知我的发件能力和他的收件能力是可以的,并且他的发件能力和我的收件能力是可以

然而此时他还不知道他的发件能力和我的收件能力到底可不可以,于是我最后反馈一次,他若收到了,他便清楚了他的发件能力和我的收件能力是可以的

这就是三次握手,这样说,你理解了吗?

  • 第一次握手:客户端要向服务端发起连接请求,首先客户端随机生成一个起始序列号ISN(比如是100),那客户端向服务端发送的报文段包含SYN标志位(也就是SYN=1),序列号seq=100。

  • 第二次握手:服务端收到客户端发过来的报文后,发现SYN=1,知道这是一个连接请求,于是将客户端的起始序列号100存起来,并且随机生成一个服务端的起始序列号(比如是300)。然后给客户端回复一段报文,回复报文包含SYN和ACK标志(也就是SYN=1,ACK=1)、序列号seq=300、确认号ack=101(客户端发过来的序列号+1)。

  • 第三次握手:客户端收到服务端的回复后发现ACK=1并且ack=101,于是知道服务端已经收到了序列号为100的那段报文;同时发现SYN=1,知道了服务端同意了这次连接,于是就将服务端的序列号300给存下来。然后客户端再回复一段报文给服务端,报文包含ACK标志位(ACK=1)、ack=301(服务端序列号+1)、seq=101(第一次握手时发送报文是占据一个序列号的,所以这次seq就从101开始,需要注意的是不携带数据的ACK报文是不占据序列号的,所以后面第一次正式发送数据时seq还是101)。当服务端收到报文后发现ACK=1并且ack=301,就知道客户端收到序列号为300的报文了,就这样客户端和服务端通过TCP建立了连接。

四次挥手

四次挥手的目的是关闭一个连接

比如客户端初始化的序列号ISA=100,服务端初始化的序列号ISA=300。TCP连接成功后客户端总共发送了1000个字节的数据,服务端在客户端发FIN报文前总共回复了2000个字节的数据。

  • 第一次挥手:当客户端的数据都传输完成后,客户端向服务端发出连接释放报文(当然数据没发完时也可以发送连接释放报文并停止发送数据),释放连接报文包含FIN标志位(FIN=1)、序列号seq=1101(100+1+1000,其中的1是建立连接时占的一个序列号)。需要注意的是客户端发出FIN报文段后只是不能发数据了,但是还可以正常收数据;另外FIN报文段即使不携带数据也要占据一个序列号。

  • 第二次挥手:服务端收到客户端发的FIN报文后给客户端回复确认报文,确认报文包含ACK标志位(ACK=1)、确认号ack=1102(客户端FIN报文序列号1101+1)、序列号seq=2300(300+2000)。此时服务端处于关闭等待状态,而不是立马给客户端发FIN报文,这个状态还要持续一段时间,因为服务端可能还有数据没发完。

  • 第三次挥手:服务端将最后数据(比如50个字节)发送完毕后就向客户端发出连接释放报文,报文包含FIN和ACK标志位(FIN=1,ACK=1)、确认号和第二次挥手一样ack=1102、序列号seq=2350(2300+50)。

  • 第四次挥手:客户端收到服务端发的FIN报文后,向服务端发出确认报文,确认报文包含ACK标志位(ACK=1)、确认号ack=2351、序列号seq=1102。注意客户端发出确认报文后不是立马释放TCP连接,而是要经过2MSL(最长报文段寿命的2倍时长)后才释放TCP连接。而服务端一旦收到客户端发出的确认报文就会立马释放TCP连接,所以服务端结束TCP连接的时间要比客户端早一些。

为什么TCP连接的时候是3次?2次不可以吗?

因为需要考虑连接时丢包的问题,如果只握手2次,第二次握手时如果服务端发给客户端的确认报文段丢失,此时服务端已经准备好了收发数(可以理解服务端已经连接成功)据,而客户端一直没收到服务端的确认报文,所以客户端就不知道服务端是否已经准备好了(可以理解为客户端未连接成功),这种情况下客户端不会给服务端发数据,也会忽略服务端发过来的数据。

如果是三次握手,即便发生丢包也不会有问题,比如如果第三次握手客户端发的确认ack报文丢失,服务端在一段时间内没有收到确认ack报文的话就会重新进行第二次握手,也就是服务端会重发SYN报文段,客户端收到重发的报文段后会再次给服务端发送确认ack报文。

为什么TCP连接的时候是3次,关闭的时候却是4次?

因为只有在客户端和服务端都没有数据要发送的时候才能断开TCP。而客户端发出FIN报文时只能保证客户端没有数据发了,服务端还有没有数据发客户端是不知道的。而服务端收到客户端的FIN报文后只能先回复客户端一个确认报文来告诉客户端我服务端已经收到你的FIN报文了,但我服务端一些数据没发完,等这些数据发完了,服务端才能给客户端发FIN报文(所以不能一次性将确认报文和FIN报文发给客户端,就是这里多出来了一次)。

为什么客户端发出第四次挥手的确认报文后要等2MSL的时间才能释放TCP连接?

要考虑丢包的问题,如果第四次挥手的报文丢失,服务端没收到确认ack报文就会重发第三次挥手的报文,这样报文一去一回最长时间就是2MSL,所以需要等这么长时间来确认服务端确实已经收到了。

@$滑动窗口协议

为了最大效率的利用网络资源,增加网络的吞吐量,便产生了“滑动窗口”这种协议。滑动窗口机制是TCP协议中用来控制发送数据包速率的,发送方每次只能发送滑动窗口内部的数据包,同时解决了丢包,出错,乱序等一些情况。

问题一:如何保证次序?

提出问题:在我们滑动窗口协议之前,我们如何来保证发送方与接收方之间,每个包都能被收到。并且是按次序的呢?

发送方发送一个包1,这时候接收方确认包1。发送包2,确认包2。就这样一直下去,直到把数据完全发送完毕,这样就结束了。那么就解决了丢包,出错,乱序等一些情况!同时也存在一些问题。问题:吞吐量非常的低。我们发完包1,一定要等确认包1。我们才能发送第二个包。

问题二:如何提高吞吐量?

提出问题:那么我们就不能先连发几个包等他一起确认吗?这样的话,我们的速度会不会更快,吞吐量更高些呢?

如图,这个就是我们把两个包一起发送,然后一起确认。可以看出我们改进的方案比之前的好很多,所花的时间只是一个来回的时间。接下来,我们还有一个问题:改善了吞吐量的问题

问题三:如何实现最优解?

问题:我们每次需要发多少个包过去呢?发送多少包是最优解呢?

我们能不能把第一个和第二个包发过去后,收到第一个确认包就把第三个包发过去呢?而不是去等到第二个包的确认包才去发第三个包。这样就很自然的产生了我们"滑动窗口"的实现。

每收到一个新的确认(ack),滑动窗口的位置就向右移动一格

在图中,我们可看出灰色1号2号3号包已经发送完毕,并且已经收到Ack。这些包就已经是过去式。4、5、6、7号包是黄色的,表示已经发送了。但是并没有收到对方的Ack,所以也不知道接收方有没有收到。8、9、10号包是绿色的。是我们还没有发送的。这些绿色也就是我们接下来马上要发送的包。后面的11-16还没有被读进内存。

正常情况

可以看到4号包对方已经被接收到,所以被涂成了灰色。“窗口”就往右移一格,这里只要保证“窗口”是7格的。我们就把11号包读进了我们的缓存。进入了“待发送”的状态。8、9号包已经变成了黄色,表示已经发送出去了。接下来的操作就是一样的了,确认包后,窗口往后移,继续将未发送的包读进缓存,把“待发送“状态的包变为”已发送“。

丢包情况

有可能我们包发过去,对方的Ack丢了,也有可能我们的包并没有发送过去。从发送方角度看就是我们没有收到Ack。

发生的情况:一直在等Ack。如果一直等不到的话,我们也会把读进缓存的待发送的包也一起发过去。但是,这个时候我们的窗口已经发满了。所以并不能把12号包读进来,而是始终在等待5号包的Ack。

如果我们这个Ack始终不来怎么办呢?

超时重发

这时候我们有个解决方法:超时重发 这里有一点要说明:这个Ack是要按顺序的。必须要等到5的Ack收到,才会把6-11的Ack发送过去。这样就保证了滑动窗口的一个顺序。

这时候可以看出5号包已经接受到Ack,后面的6、7、8号包也已经发送过去已Ack。窗口便继续向后移动。

@$DNS解析过程

DNS(Domain Name System):因特网使用的域名系统,本质是一个分布式数据库,用于解决域名和IP地址的映射关系。

DNS解析:互联网都是通过URL来请求资源的,而URL中的域名需要解析成IP地址才能与远程主机建立连接,将域名解析成IP地址就属于DNS解析的工作范畴。

域名结构

从技术角度来看,域名是在Internet上用于解决IP地址的一种方法。一个完整的域名由2个或2个以上的部分组成,各部分之间用英文的句号“.”来分隔,最后一个“.”的右边部分称为顶级域名(TLD,也称为一级域名),最后一个“.”的左边部分称为二级域名(SLD),二级域名的左边部分称为三级域名,以此类推,每一级的域名控制它下一级域名的分配。

如域名mail.cctv.com,其中:com为顶级域名( top-level-domain,TLD), cctv为二级域名,mail为三级域名

DNS解析过程

  1. 浏览器会检查缓存中有没有这域名对应的解析过的IP地址,如果缓存中有,这个解析过程就将结束。

  2. 如果过程1中浏览器缓存中没有域名对应的ip,则从操作系统本身去做域名解析,我们在windows中的host文件可以设置特定域名映射到特定ip。C:\Windows\System32\drivers\etc\hosts

然后在终端中ping www.baidu.com,如下图所示:

  1. 向本地域名解析服务器(LDNS)发起域名解析请求

    上述步骤的1、2都是在本机中完成的域名解析,如果经过1、2步骤都没有完成域名的解析,则需要向LDNS发起域名解析,对于LDNS,Window中可以通过ipconfig/all来查询,如下所示:

LDNS 一般都缓存了大部分的域名解析结果,当然缓存时间也受域名失效的时间控制,大部分的解析工作到这里就差不多结束了,LDNS负责了大部分的解析工作。

  1. 向根域名解析服务器(RDNS)发起域名解析的请求

    当步骤3中没有完成域名的解析,则需要向RDNS发起域名解析的请求

  2. 根域名服务器返回通用顶级域名解析服务器(gTDL)地址

    LDNS向根域名服务器发起请求,根域名服务器返回的是所查询的通用顶级域名(Generic top-level-domain, gTLD)地址,常见的通用顶级域名有.com、.org、.edu。

  3. 本地域名服务器向gTLD发起解析域名请求

  4. gTLD服务器接收请求并返回注册的域名服务器(Name Server服务器,即名称服务器)

    当gTLD服务器接收到本地域名服务器发起的请求后,并根据需要解析的域名,找到该域名对应的Name Server服务器,通常情况下,这个Name Server服务器就是你注册的域名服务器,那么你注册的域名的服务上的服务器将承担起域名解析的任务。

  5. 本地域名服务器向Name Server服务器发起域名解析请求

  6. Name Server服务器会查询存储的域名和IP的映射关系表,然后返回该域名对应的ip和TTL给本地域名服务器,本地域名服务器进行缓存这个域名和ip的对应关系,缓存时间由TTL决定。

  7. 本地域名服务器返回查询域名对应的ip给用户(浏览器),浏览器进行缓存,缓存时间由TTL决定。

经过以上的10个步骤,就可以拿到真正的ip了,然后通过ip去对应的服务器上请求资源。

@$CDN原理,CDN加速过程

CDN内容分发网络,全称为Content DeliveryNetwork,通过将网络内容发布到最靠近用户的『边缘节点』,使不同地区的用户在访问相同页面、图片或视频时就可以就近获取。

CDN本质是一种分布式缓存系统,无需考虑数据持久化,如果缓存服务器出现问题,在缓存集群中标记为删除即可。

CDN中实现原理:给源站域名添加CNAME别名,别名为加速节点的域名。当用户向源站发起请求时,dns服务器解析源站域名时会发现有CNAME记录,这时dns服务器会向CNAME域名发起请求,请求会被调度至加速节点的域名。

CDN的优点

  1. 根据用户与业务服务器的距离,自动选择就近的cache服务器

  2. 镜像服务,消除运营商之间互联的瓶颈影响,保证不同网络的用户都能得到良好的访问质量

  3. 带宽优化,分担网络流量,减轻压力

域名与ip的对应关系,被称为记录(record),可分为各种类型

  • A记录: Address,域名指向的IP地址,A记录允许将多个域名解析到一个IP地址,但不允许将一个域名解析到多个IP上。

  • NS记录:Name Server,指定了特定的DNS服务器去解析。

  • MX记录:Mail eXchange,接受电子邮件的服务器地址

  • CNAME记录:Canonical Name,别名解析,可以将指定的域名解析到其他域名上

CDN的过程

使用CDN的方法很简单,只需要修改自己的DNS解析,设置一个CNAME指向CDN服务商即可。

用户访问未使用CDN缓存资源的过程

  1. 浏览器通过DNS解析,以得到此域名对应的IP地址;

  2. 浏览器使用所得到的IP地址,向域名的服务主机发出数据访问请求;

  3. 服务器向浏览器返回响应数据

使用CDN后

假设您的加速域名为www.a.com,接入CDN网络,开始使用加速服务后,当终端用户(北京)发起HTTP请求时,处理流程如下图所示。

  1. 当终端用户(北京)向www.a.com下的指定资源发起请求时,首先向LDNS(本地DNS)发起域名解析请求。

  2. LDNS检查缓存中是否有www.a.com的IP地址记录。如果有,则直接返回给终端用户;如果没有,则向授权DNS查询。

  3. 当授权DNS解析www.a.com时,返回域名CNAME www.a.tbcdn.com对应IP地址。

  4. 域名解析请求发送至阿里云DNS调度系统,并为请求分配最佳节点IP地址。

  5. LDNS获取DNS返回的解析IP地址。

  6. 用户获取解析IP地址。

  7. 用户向获取的IP地址发起对该资源的访问请求。

    • 如果该IP地址对应的节点已缓存该资源,则会将数据直接返回给用户,例如,图中步骤7和8,请求结束。

    • 如果该IP地址对应的节点未缓存该资源,则节点向源站发起对该资源的请求。获取资源后,结合用户自定义配置的缓存策略,将资源缓存至节点,例如,图中的北京节点,并返回给用户,请求结束。

GET和POST区别

说道GET和POST,就不得不提HTTP协议,因为浏览器和服务器的交互是通过HTTP协议执行的,而GET和POST也是HTTP协议中的两种方法。

HTTP全称为Hyper Text Transfer Protocol,中文翻译为超文本传输协议,目的是保证浏览器与服务器之间的通信。HTTP的工作方式是客户端与服务端之间的请求-应答协议。

HTTP协议中定义了浏览器和服务器进行交互的不同方法,基本方法有4种,分别是GET,POST,PUT,DELETE。这四种方法可以理解为,对服务器资源的查,改,增,删。

  • GET:从服务器上获取数据,也就是所谓的查,仅仅是获取服务器资源,不进行修改。

  • POST:向服务器提交数据,这就涉及到了数据的更新,也就是更改服务器的数据。

  • PUT:英文含义是放置,也就是向服务器新添加数据,就是所谓的增。

  • DELETE:从字面意思也能看出,这种方式就是删除服务器数据的过程。

GET和POST区别

  1. Get是不安全的,因为在传输过程,数据被放在请求的URL中;Post的所有操作对用户来说都是不可见的。但是这种做法也不是绝对的,大部分人的做法也是按照上面的说法来的,但是也可以在get请求加上 request body,给 post请求带上 URL 参数。

  2. Get请求提交的url中的数据最多只能是2048字节,这个限制是浏览器或者服务器给添加的,http协议并没有对url长度进行限制,目的是为了保证服务器和浏览器能够正常运行,防止有人恶意发送请求。Post请求则没有大小限制。

  3. Get限制Form表单的数据集的值必须为ASCII字符;而Post支持整个ISO10646字符集。

  4. Get执行效率比Post快。Get是form提交的默认方法。

  5. GET产生一个TCP数据包;POST产生两个TCP数据包。

    对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);

    而对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。

GetPost
安全性Get是不安全的,因为在传输过程,数据被放在请求的URL中Post的所有操作对用户来说都是不可见的,相对安全
url数据大小Get请求提交的url中的数据受浏览器和服务器的限制,防止有人恶意发送请求Post请求url数据没有大小限制
表单字符集Get限制Form表单的数据集的值必须为ASCII字符Post支持整个ISO10646字符集
TCP数据包数量GET产生一个TCP数据包POST产生两个TCP数据包
执行效率Get执行效率比Post快Post执行效率比Get慢

@$Session、Cookie和Token的主要区别

HTTP协议本身是无状态的。什么是无状态呢,即服务器无法判断用户身份。

什么是cookie

cookie是由Web服务器保存在用户浏览器上的小文件(key-value格式),包含用户相关的信息。客户端向服务器发起请求,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie。客户端浏览器会把Cookie保存起来。当浏览器再请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务器。服务器检查该Cookie,以此来辨认用户身份。

什么是session

session是依赖Cookie实现的。session是服务器端对象

session 是浏览器和服务器会话过程中,服务器分配的一块储存空间。服务器默认为浏览器在cookie中设置 sessionid,浏览器在向服务器请求过程中传输 cookie 包含 sessionid ,服务器根据 sessionid 获取出会话中存储的信息,然后确定会话的身份信息。

cookie与session区别

  • 存储位置与安全性:cookie数据存放在客户端上,安全性较差,session数据放在服务器上,安全性相对更高;

  • 存储空间:单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie,session无此限制

  • 占用服务器资源:session一定时间内保存在服务器上,当访问增多,占用服务器性能,考虑到服务器性能方面,应当使用cookie。

什么是Token

Token的引入:Token是在客户端频繁向服务端请求数据,服务端频繁的去数据库查询用户名和密码并进行对比,判断用户名和密码正确与否,并作出相应提示,在这样的背景下,Token便应运而生。

Token的定义:Token是服务端生成的一串字符串,以作客户端进行请求的一个令牌,当第一次登录后,服务器生成一个Token便将此Token返回给客户端,以后客户端只需带上这个Token前来请求数据即可,无需再次带上用户名和密码。

使用Token的目的:Token的目的是为了减轻服务器的压力,减少频繁的查询数据库,使服务器更加健壮。

session与token区别

  • session机制存在服务器压力增大,CSRF跨站伪造请求攻击,扩展性不强等问题;

  • session存储在服务器端,token存储在客户端

  • token提供认证和授权功能,作为身份认证,token安全性比session好;

  • session这种会话存储方式方式只适用于客户端代码和服务端代码运行在同一台服务器上,token适用于项目级的前后端分离(前后端代码运行在不同的服务器下)

Servlet接口中有哪些方法及Servlet生命周期探秘

在Java Web程序中,Servlet主要负责接收用户请求HttpServletRequest,在doGet()doPost()中做相应的处理,并将回应HttpServletResponse反馈给用户。Servlet可以设置初始化参数,供Servlet内部使用。

Servlet接口定义了5个方法,其中前三个方法与Servlet生命周期相关

  • void init(ServletConfig config) throws ServletException

  • void service(ServletRequest req, ServletResponse resp) throws ServletException, java.io.IOException

  • void destory()

  • java.lang.String getServletInfo()

  • ServletConfig getServletConfig()

生命周期:

Web容器加载Servlet并将其实例化后,Servlet生命周期开始,容器运行其init()方法进行Servlet的初始化;

请求到达时调用Servlet的service()方法,service()方法会根据需要调用与请求对应的doGet或doPost等方法;

当服务器关闭或项目被卸载时服务器会将Servlet实例销毁,此时会调用Servlet的destroy()方法

init方法和destory方法只会执行一次,service方法客户端每次请求Servlet都会执行。Servlet中有时会用到一些需要初始化与销毁的资源,因此可以把初始化资源的代码放入init方法中,销毁资源的代码放入destroy方法中,这样就不需要每次处理客户端的请求都要初始化与销毁资源。

Servlet是线程安全的吗

Servlet不是线程安全的,多线程并发的读写会导致数据不同步的问题。

解决的办法是尽量不要在实现servlet接口的类中定义实例变量,而是要把变量分别定义在doGet()和doPost()方法内。虽然使用synchronized(name){}语句块可以解决问题,但是会造成线程的等待,不是很科学的办法。

注意:多线程的并发的读写Servlet类属性会导致数据不同步。但是如果只是并发地读取属性而不写入,则不存在数据不同步的问题。因此Servlet里的只读属性最好定义为final类型的。

Servlet执行流程

  1. web客户向Servlet容器发出HTTP请求;

  2. Servlet容器解析web的HTTP请求.

  3. Servlet容器创建一个HttpRequest对象,在这个对象中封装了http请求信息;

  4. Servlet容器创建一个HttpResponse对象;

  5. Servlet容器(如果访问的该servlet不是在服务器启动时创建的,则先创建servlet实例并调用init()方法初始化对象)调用HttpServlet的service()方法,把HttpRequest和HttpResponse对象为service方法的参数传给HttpServlet对象;

  6. HttpServlet调用HttpRequest的有关方法,获取HTTP请求信息;

  7. HttpServlet调用HttpResponse的有关方法,生成响应数据;

  8. Servlet容器把HttpServlet的响应结果传给web客户.

forward和redirect的区别

实际发生地址不同,地址栏不同

  • 转发是发生在服务器的,转发是由服务器进行跳转的,浏览器的地址栏是没有发生变化的,实现转发只是一次的http请求

  • 重定向是由浏览器进行跳转的,浏览器的地址会发生变化的,由浏览器进行的页面跳转,实现重定向会发出两个http请求request域对象是无效的,因为它不是同一个request对象

访问范围不一样

  • 转发是服务器跳转只能去往当前web应用的资源

  • 重定向是浏览器跳转,可以去往任何的资源

传递数据的类型不同

  • 转发的request对象可以传递各种类型的数据,包括对象

  • 重定向只能传递字符串

跳转的时间不同

  • 转发:执行到跳转语句时就会立刻跳转

  • 重定向:整个页面执行完之后才执行跳转

那么转发(forward)和重定向(redirect)使用哪一个?

  • 根据上面说明了转发和重定向的区别也可以很容易概括出来。转发是带着转发前请求的参数。重定向是新的请求

典型的应用场景:

  • forward一般用于用户登陆的时候根据角色转发到相应的模块。 

  • redirect一般用于用户注销登陆时返回主页面和跳转到其它的网站等。

如何解决跨域问题?

CORS全称Cross-Origin Resource Sharing,意为跨域资源共享。当一个资源去访问另一个不同域名或者同域名不同端口的资源时,就会发出跨域请求。如果此时另一个资源不允许其进行跨域资源访问,那么访问就会遇到跨域问题。

跨域指的是浏览器不能执行其它网站的脚本,它是由浏览器的同源策略造成的,是浏览器对JavaScript 施加的安全限制。

同源策略它是由 Netscape 提出的一个安全策略,它是浏览器最核心也是最基本的安全功能,如果缺少同源策略,则浏览器的正常功能可能都会受到影响,现在所有支持JavaScript的浏览器都会使用这个策略。

所谓同源指的是:协议、域名、端口号都相同,只要有一个不相同,那么都是非同源。

解决方案:

  • 使用ajax的jsonp

  • nginx 转发:利用nginx反向代理,将请求分发到部署相应项目的tomcat服务器,当然也不存在跨域问题。

  • 使用cors:写一个配置类实现WebMvcConfigurer接口或者配置FilterRegistrationBean

什么是 CSRF 攻击?如何防御CSRF 攻击

CSRF(Cross-site request forgery) 跨站请求伪造。CSRF 攻击是在受害者毫不知情的情况下,以受害者名义伪造请求发送给受攻击站点,从而在受害者并未授权的情况下执行受害者权限下的各种操作。

CSRF 攻击专门针对状态改变请求,而不是数据窃取,因为攻击者无法查看对伪造请求的响应。

目前防御 CSRF 攻击主要有三种策略:

  • 验证 HTTP Referer 字段

  • 在请求地址中添加 token 并验证

  • 在 HTTP 头中自定义属性并验证

什么是Socket

在计算机通信领域,socket 被翻译为“套接字”,它是计算机之间进行通信的一种约定。网络上的两个程序通过一个双向的链路连接实现数据的交换,这个双向链路的一端称为一个Socket,一个Socket由一个IP地址和一个端口确定唯一性。

Socket所支持的协议种类不光是TCP/IP、UDP,因此两者之间是没有必然联系的。在Java环境下,Socket编程主要是指基于TCP/IP协议的网络编程。

socket连接就是所谓的长连接,客户端和服务器需要互相连接,理论上客户端和服务器端一旦建立起连接将不会主动断掉的,但是有时候网络波动还是有可能的。

Socket偏向于底层。一般很少直接使用Socket来编程,框架底层使用Socket比较多。

socket 的典型应用就是 Web 服务器和浏览器:浏览器获取用户输入的 URL,向服务器发起请求,服务器分析接收到的 URL,将对应的网页内容返回给浏览器,浏览器再经过解析和渲染,就将文字、图片、视频等元素呈现给用户。例如我们每天浏览网页、QQ 聊天、收发 email 等等。

Socket属于网络的哪个层面

Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个外观模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

Socket通讯的过程

  • 基于TCP:服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把响应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。

  • 基于UDP:UDP 协议是用户数据报协议的简称,也用于网络数据的传输。虽然 UDP 协议是一种不太可靠的协议,但有时在需要较快地接收数据并且可以忍受较小错误的情况下,UDP 就会表现出更大的优势。我客户端只需要发送,服务端能不能接收的到我不管

http和https的基本概念

HTTP:是互联网上应用最为广泛的一种网络协议,是一个客户端和服务器端请求和应答的标准(TCP),用于计算机之间传输文字,图片,音频,视频等超文本数据的协议,它可以使浏览器更加高效,使网络传输减少。Http协议属于应用层

HTTPS:是以安全为目标的HTTP通道,简单讲是HTTP的安全版,即HTTP下加入SSL层,HTTPS的安全基础是SSL,HTTPS就是从HTTP加上加密处理(一般是SSL安全通信线路)+认证+完整性保护

HTTPS协议的主要作用可以分为两种:一种是建立一个信息安全通道,来保证数据传输的安全;另一种就是确认网站的真实性。

@$http和https的区别?

HTTP存在的不足

  • 通信内容明文传输,容易被第三方窃听

  • 容易被第三方劫持、篡改从而无法保证内容的完整性与正确性

  • 不验证通信方的身份,因此有可能遭遇伪装,无法保证信息的来源

既然HTTP存在这些问题,那么HTTPS就是为了解决这些问题的。

HTTPS的优点

  • 通信内容进行加密,防止信息在传输过程中泄露

  • 保证数据完整性、准确性

  • 对数据来源进行验证,确保来源无法伪造

区别HTTPHTTPS
协议运行在 TCP 之上,明文传输,客户端与服务器端都无法验证对方的身份身披 SSL( Secure Socket Layer )外壳的 HTTP,运行于 SSL 上,SSL 运行于 TCP 之上, 是添加了加密和认证机制的 HTTP
端口80443
资源消耗较少由于加解密处理,会消耗更多的 CPU 和内存资源
证书无需证书需要证书,而证书一般需要向认证机构购买
加密机制共享密钥加密和公开密钥加密并用的混合加密机制
安全性由于加密机制,安全性强

HTTPS工作原理

  1. Client发起一个HTTPS(https:/demo.linianhui.dev)的请求,根据RFC2818的规定,Client知道需要连接Server的443(默认)端口。

  2. Server把事先配置好的公钥证书(public key certificate)返回给客户端。

  3. Client验证公钥证书:比如是否在有效期内,证书的用途是不是匹配Client请求的站点,是不是在CRL吊销列表里面,它的上一级证书是否有效,这是一个递归的过程,直到验证到根证书(操作系统内置的Root证书或者Client内置的Root证书)。如果验证通过则继续,不通过则显示警告信息。

  4. Client使用伪随机数生成器生成加密所使用的会话密钥,然后用证书的公钥加密这个会话密钥,发给Server。

  5. Server使用自己的私钥(private key)解密这个消息,得到会话密钥。至此,Client和Server双方都持有了相同的会话密钥

  6. Server使用会话密钥加密“明文内容A”,发送给Client。

  7. Client使用会话密钥解密响应的密文,得到“明文内容A”。

  8. Client再次发起HTTPS的请求,使用会话密钥加密请求的“明文内容B”,然后Server使用会话密钥解密密文,得到“明文内容B”。

简单总结下,HTTPS是使用了证书的一个混合密码系统,其中证书的作用在于传递会话密钥,以及验证网站的真实性

一次完整的HTTP请求所经历几个步骤?

  1. 根据域名和 DNS 解析到服务器的IP地址 (DNS + CDN)

  2. 通过ARP协议获得IP地址对应的物理机器的MAC地址

  3. 浏览器对服务器发起 TCP 3 次握手

  4. 建立 TCP 连接后发起 HTTP 请求报文

  5. 服务器响应 HTTP 请求,将响应报文返回给浏览器

  6. 短连接情况下,请求结束则通过 TCP 四次挥手关闭连接,长连接在没有访问服务器的若干时间后,进行连接的关闭

  7. 浏览器得到响应信息中的 HTML 代码, 并请求 HTML 代码中的资源(如js、css、图片等)

  8. 浏览器对页面进行渲染并呈现给用户

常用HTTP状态码是怎么分类的,有哪些常见的状态码?

HTTP状态码表示客户端HTTP请求的返回结果、标识服务器处理是否正常、表明请求出现的错误等。

状态码的类别:

状态码类别描述
1xxInformational(信息状态码)信息,服务器收到请求,需要请求者继续执行操作
2xxSuccess(成功状态码)成功,操作被成功接收并处理
3xxRedirection(重定向状态码)重定向,需要进一步的操作以完成请求
4xxClient Error(客户端错误状态码)客户端错误,请求包含语法错误或无法完成请求
5xxServer Error(服务器错误状态码)服务器错误,服务器在处理请求的过程中发生了错误

HTTP的状态码总数达60余种,但是常用的大概只有14种。接下来,我们就介绍一下这些具有代表性的14个状态码。

14种常用的HTTP状态码列表

状态码状态码英文名称中文描述
2xx
200OK请求成功。一般用于GET与POST请求
204No Content无内容。服务器成功处理,但未返回内容。在未更新网页的情况下,可确保浏览器继续显示当前文档
206Partial Content是对资源某一部分的请求,服务器成功处理了部分GET请求,响应报文中包含由Content-Range指定范围的实体内容。
3xx
301Moved Permanently永久性重定向。请求的资源已被永久的移动到新URI,返回信息会包括新的URI,浏览器会自动定向到新URI。今后任何新的请求都应使用新的URI代替
302Found临时性重定向。与301类似。但资源只是临时被移动。客户端应继续使用原有URI
303See Other查看其它地址。与302类似。使用GET请求查看
304Not Modified未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。客户端通常会缓存访问过的资源,通过提供一个头信息指出客户端希望只返回在指定日期之后修改的资源
307Temporary Redirect临时重定向。与302类似。使用GET请求重定向,会按照浏览器标准,不会从POST变成GET。
4xx
400Bad Request客户端请求报文中存在语法错误,服务器无法理解
401Unauthorized请求要求用户的身份认证,通过HTTP认证(BASIC认证,DIGEST认证)的认证信息,若之前已进行过一次请求,则表示用户认证失败
402Payment Required保留,将来使用
403Forbidden服务器理解请求客户端的请求,但是拒绝执行此请求
404Not Found服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置"您所请求的资源无法找到"的个性页面。也可以在服务器拒绝请求且不想说明理由时使用
5xx
500Internal Server Error服务器内部错误,无法完成请求,也可能是web应用存在bug或某些临时故障
501Not Implemented服务器不支持请求的功能,无法完成请求
503Service Unavailable由于超载或系统维护,服务器暂时的无法处理客户端的请求。延时的长度可包含在服务器的Retry-After头信息中

Http有哪些请求方法

根据 HTTP 标准,HTTP 请求可以使用多种请求方法。

HTTP1.0 定义了三种请求方法:GET, POST 和 HEAD方法。

HTTP1.1 新增了六种请求方法:OPTIONS、PUT、PATCH、DELETE、TRACE 和 CONNECT 方法。

序号方法描述
1GET请求指定的页面信息,并返回实体主体。
2HEAD类似于 GET 请求,只不过返回的响应中没有具体的内容,用于获取报头
3POST向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST 请求可能会导致新的资源的建立和/或已有资源的修改。
4PUT从客户端向服务器传送的数据取代指定的文档的内容。
5DELETE请求服务器删除指定的页面。
6CONNECTHTTP/1.1 协议中预留给能够将连接改为管道方式的代理服务器。
7OPTIONS允许客户端查看服务器的性能。
8TRACE回显服务器收到的请求,主要用于测试或诊断。
9PATCH是对 PUT 方法的补充,用来对已知资源进行局部更新 。

Socket和http的区别和应用场景

Socket连接就是所谓的长连接,理论上客户端和服务器端一旦建立起连接将不会主动断掉;

Socket适用场景:网络游戏,银行持续交互,直播,在线视屏等。

http连接就是所谓的短连接,即客户端向服务器端发送一次请求,服务器端响应后连接即会断开等待下次连接

http适用场景:公司OA服务,互联网服务,电商,办公,网站等等等等

什么是对称加密与非对称加密

  • 对称密钥加密是指加密和解密使用同一个密钥的方式,这种方式存在的最大问题就是密钥发送问题,即如何安全地将密钥发给对方;

  • 而非对称加密是指使用一对非对称密钥,即公钥和私钥,公钥可以随意发布,但私钥只有自己知道。发送密文的一方使用对方的公钥进行加密处理,对方接收到加密信息后,使用自己的私钥进行解密。由于非对称加密的方式不需要发送用来解密的私钥,所以可以保证安全性;但是和对称加密比起来,非常的慢

常用API

自动装箱与拆箱

装箱:将基本类型用它们对应的引用类型包装起来;原理:Integer.valueOf() 方法

拆箱:将包装类型转换为基本数据类型;原理:Integer.intValue() 方法

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

Integer a= 127 与 Integer b = 127相等吗

对于对象引用类型:==比较的是对象的内存地址。对于基本数据类型:==比较的是值。

如果整型字面量的值在-128到127之间,那么自动装箱时不会new新的Integer对象,而是直接引用常量池中的Integer对象,超过范围 a1==b1的结果是false

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比较
    System.out.println(b == c); // true

    Integer a1 = 128;
    Integer b1 = 128;
    System.out.println(a1 == b1); // false

    Integer a2 = 127;
    Integer b2 = 127;
    System.out.println(a2 == b2); // true
}

字符型常量和字符串常量的区别

  1. 形式上:字符常量是单引号引起的一个字符,字符串常量是双引号引起的若干个字符

  2. 含义上:字符常量相当于一个整形值(ASCII值),可以参加表达式运算,字符串常量代表一个地址值(该字符串在内存中存放位置)

  3. 占用内存大小:字符常量只占两个字节,字符串常量占若干个字节

String的创建机理是什么?什么是字符串常量池?

创建机理:由于String在Java世界中使用过于频繁,为了提高内存的使用率,避免开辟多块空间存储相同的字符串,引入了字符串常量池(字符串常量池位于堆内存中)。

其运行机制是:在创建字符串时 JVM 会首先检查字符串常量池,如果该字符串已经存在池中,则返回它的引用,如果不存在,则实例化一个字符串放到池中,并返回其引用。

String 是最基本的数据类型吗

不是。Java 中的基本数据类型只有 8 个,除了基本类型(primitive type),剩下的都是引用类型(referencetype)

基本数据类型中用来描述文本数据的是 char,但是它只能表示单个字符,如果要描述一段文本,就需要使用 char 类型数组,但是使用数组过于麻烦,所以就有了 String,String 底层就是一个 char 类型的数组,只是使用的时候开发者不需要直接操作底层数组,使用更加简便

String s = new String(“abc”);创建了几个字符串对象

当JVM遇到上述代码时,会先检索常量池中是否存在“abc”,如果不存在“abc”这个字符串,则会先在常量池中创建这个字符串。然后再执行new操作,在堆内存中创建一个String对象,对象的引用赋值给s。此过程创建了2个对象。

当然,如果检索常量池时发现已经存在了对应的字符串,那么只会在堆内创建一个新的String对象,此过程只创建了1个对象。

String str1 = "hello"; //str1指向静态区
String str2 = new String("hello");  //str2指向堆上的对象
String str3 = "hello";
String str4 = new String("hello");
System.out.println(str1.equals(str2)); //true
System.out.println(str2.equals(str4)); //true
System.out.println(str1 == str3); //true
System.out.println(str1 == str2); //false
System.out.println(str2 == str4); //false
System.out.println(str2 == "hello"); //false
str2 = str1;
System.out.println(str2 == "hello"); //true

@$String为什么设计为final,一个类修饰为final有什么好处

  • final修饰的String类,代表了String类的不可被继承,final修饰的char[]代表了被存储的数据不可更改。String类一旦在常量池(节省资源,提高效率,因为如果已经存在这个常量便不会再创建,直接拿来用)被创建,是无法修改的,即便你在后面拼接一些其他字符,也会把新生成的字符串存到另外一个地址。但是,虽然final代表了不可变,但仅仅是引用地址不可变,并不代表了数组本身不会变

  • 线程安全,多线程下对资源进行写操作是有风险的,不可变对象不能被写,所以线程安全

  • 因为字符串是不可变的,所以在它创建的时候HashCode就被缓存了,不需要重新计算,这就是HashMap中的键往往都使用字符串的原因之一

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

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

@$String和StringBuffer、StringBuilder的区别是什么?String为什么是不可变的

可变性

String类中使用字符数组保存字符串,private final char value[],所以string对象是不可变的。

StringBuilder与StringBuffer都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串,char[] value,这两种对象都是可变的。

线程安全

String中的对象是不可变的,也就可以理解为常量,线程安全。

AbstractStringBuilder是StringBuilder与StringBuffer的公共父类,定义了一些字符串的基本操作,如expandCapacity、append、insert、indexOf等公共方法。StringBuffer对方法加了同步锁,所以是线程安全的。StringBuilder并没有对方法进行加同步锁,所以是非线程安全的。

性能

每次对String 类型对象进行改变的时候,都会生成一个新的String对象,然后将指针指向新的String 对象。

StringBuffer每次都会对StringBuffer对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用StirngBuilder 相比使用StringBuffer 仅能获得10%~15% 左右的性能提升,但却要冒多线程不安全的风险。

使用场景

在字符串内容不经常发生变化的业务场景,优先使用String类,例如常量声明、少量的字符串拼接操作等。

在单线程环境下,频繁地进行字符串的操作,建议使用StringBuilder,例如SQL语句拼装、JSON封装等。

在多线程环境下,频繁地进行字符串的操作,建议使用StringBuffer,例如XML解析、HTTP参数解析与封装。

异常

什么是异常?请描述一下Java异常架构

Java异常是Java提供的一种识别及响应错误的一致性机制。

Java异常处理机制可以使程序中异常处理代码和正常业务代码分离,保证程序代码更加优雅,并提高程序健壮性。在正确使用异常的情况下,异常能清晰的回答what, where, why这3个问题:异常类型回答了“什么”被抛出,异常堆栈跟踪回答了“在哪”抛出,异常信息回答了“为什么”会抛出。

Throwable 是 Java 语言中所有错误与异常的超类。

Throwable 包含两个子类:Error(错误)和 Exception(异常),它们通常用于指示发生了异常情况。

Throwable 包含了线程创建时执行堆栈的快照,它提供了 printStackTrace() 等接口用于获取堆栈跟踪数据等信息。

Error 和 Exception 区别是什么?

Error 是程序正常运行中,不大可能出现的错误,通常为虚拟机相关错误,如系统崩溃,内存不足,堆栈溢出等。编译器不会对这类错误进行检测,应用程序也不应对这类错误进行捕获,一旦这类错误发生,通常应用程序会被终止,仅靠应用程序本身是无法恢复的;

Exception 是程序正常运行中,可以预料的意外情况,通常遇到这种异常,应对其进行处理,使应用程序可以继续正常运行。

@$什么是运行时异常,编译时异常?什么是受检异常与非受检异常

程序本身可以捕获并且可以处理的异常。Exception 这种异常又分为两类:运行时异常和编译时异常。

运行时异常

定义:RuntimeException 类及其子类。

特点:Java 编译器不会检查它。也就是说,当程序中可能出现这类异常时,倘若既"没有通过throws声明抛出它",也"没有用try-catch语句捕获它",还是会编译通过。比如NullPointerException空指针异常、ArrayIndexOutBoundException数组下标越界异常、ClassCastException类型转换异常、ArithmeticExecption算术异常。此类异常属于非受检异常,在程序中可以选择捕获,抛出,也可以不处理,此类异常一般是由程序逻辑错误引起的,需要通过修改代码来进行避免。例如,若会发生除数为零的情况,则需要通过代码避免该情况的发生!

编译时异常

定义: Exception 中除 RuntimeException 及其子类之外的异常。

特点: Java 编译器会检查它。如果程序中出现此类异常,比如 ClassNotFoundException(没有找到指定的类异常),IOException(IO流异常),要么通过throws进行声明抛出,要么通过try-catch进行捕获处理,否则不能通过编译。在程序中,通常不会自定义该类异常,而是直接使用系统提供的异常类。

Java 的所有异常可以分为受检异常(checked exception)和非受检异常(unchecked exception)。

受检异常

编译器要求必须处理的异常。Exception 中除 RuntimeException 及其子类之外的异常都属于受检异常。编译器会检查此类异常,也就是说当编译器检查到应用中的某处可能会此类异常时,将会提示你处理本异常——要么使用try-catch捕获,要么使用方法签名中用 throws 关键字抛出,否则编译不通过。

非受检异常

编译器不会进行检查并且不要求必须处理的异常,也就说当程序中出现此类异常时,即使我们没有try-catch捕获它,也没有使用throws抛出该异常,编译也会正常通过。该类异常包括运行时异常(RuntimeException及其子类)和错误(Error)。

如何选择异常类型

可以根据下图来选择是捕获异常,声明异常还是抛出异常

运行时异常和一般异常(受检异常)区别是什么?

运行时异常包括 RuntimeException 类及其子类,表示 JVM 在运行期间可能出现的异常。Java 编译器不会检查运行时异常。

受检异常是Exception 中除 RuntimeException 及其子类之外的异常。Java 编译器会检查受检异常。

RuntimeException异常和受检异常之间的区别:是否强制要求调用者必须处理此异常,如果强制要求调用者必须进行处理,那么就使用受检异常,否则就选择非受检异常(RuntimeException)。一般来讲,如果没有特殊的要求,我们建议使用RuntimeException异常。

@$JVM 是如何处理异常的?

在一个方法中如果发生异常,这个方法会创建一个异常对象,并转交给 JVM,该异常对象包含异常名称,异常描述以及异常发生时应用程序的状态。创建异常对象并转交给 JVM 的过程称为抛出异常。可能有一系列的方法调用,最终才进入抛出异常的方法,这一系列方法调用的有序列表叫做调用栈。

JVM 会顺着调用栈去查找看是否有可以处理异常的代码,如果有,则调用异常处理代码。如果 JVM 没有找到可以处理该异常的代码块,JVM 就会将该异常转交给默认的异常处理器(默认处理器为 JVM 的一部分),默认异常处理器打印出异常信息并终止应用程序。

从性能角度来审视一下 Java 的异常处理机制

  • try-catch 代码段会产生额外的性能开销,或者换个角度说,它往往会影响 JVM 对代码进行优化,所以建议仅捕获有必要的代码段,尽量不要一个大的 try 包住整段的代码;

  • 利用异常控制代码流程,也不是一个好主意,远比我们通常意义上的条件语句(if/else、switch)要低效;

  • Java 每实例化一个 Exception,都会对当时的堆栈进行快照,这是一个相对比较重的操作。如果发生的非常频繁,这个开销可就不能被忽略了。

throw 和 throws 的区别是什么?

Java 中的异常处理除了包括捕获和处理异常之外,还包括声明异常和拋出异常,可以通过 throws 关键字在方法上声明该方法要拋出的异常,或者在方法内部通过 throw 拋出异常。

throws 关键字和 throw 关键字在使用上的几点区别如下

  • throw 关键字用在方法内部,只能用于抛出一种异常,用来抛出方法或代码块中的异常。

  • throws 关键字用在方法声明上,可以抛出多个异常,用来标识该方法可能抛出的异常列表。调用该方法的方法必须包含可处理异常的代码,否则也要在方法声明中用 throws 关键字声明相应的异常。

try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗?

答:会执行,在 return 前执行。

注意:在 finally 中改变返回值的做法是不好的,因为如果存在 finally 代码块,try中的 return 语句不会立马返回调用者,而是记录下返回值待 finally 代码块执行完毕之后再向调用者返回其值,如果在 finally 中修改了返回值,就会返回修改后的值。显然,在 finally 中返回或者修改返回值会对程序造成很大的困扰,C#中直接用编译错误的方式来阻止程序员干这种龌龊的事情,Java 中也可以通过提升编译器的语法检查级别来产生警告或错误。

Java常见异常有哪些

Error

  • java.lang.OutOfMemoryError:内存不足错误。当可用内存不足以让Java虚拟机分配给一个对象时抛出该错误。

  • java.lang.StackOverflowError:堆栈溢出错误。当一个应用递归调用的层次太深而导致堆栈溢出或者陷入死循环时抛出该错误。

Exception

  • IOException(IO异常)

RuntimeException

  • NullPointerException(空指针异常)

  • ClassCastException(类转换异常)

  • IndexOutOfBoundsException(索引越界异常)

@$说几个Java异常处理最佳实践

  • 在 finally 块中清理资源或者使用 try-with-resource 语句

  • 对异常进行文档说明

  • 使用描述性消息抛出异常

  • Throw early, catch late 原则,在发现问题的时候,第一时间抛出,能够更加清晰地反映问题

  • 尽量不要捕获类似 Exception 这样的通用异常,优先捕获最具体的异常

  • 不要捕获 Throwable 类

  • 不要忽略异常,也不要生吞(swallow)异常

  • 不要记录并抛出异常,但这经常会给同一个异常输出多条日志。如果想要提供更加有用的信息,那么可以将异常包装为自定义异常。因此,仅仅当想要处理异常时才去捕获,否则只需要在方法签名中声明让调用者去处理。

  • 包装异常时不要抛弃原始的异常

  • 不要使用异常控制程序的流程

  • 尽量使用标准异常

  • 异常会影响性能

NoClassDefFoundError 和 ClassNotFoundException 有什么区别

NoClassDefFoundError是一个错误(Error),而ClassNOtFoundException是一个异常,在Java中对于错误和异常的处理是不同的,我们可以从异常中恢复程序但却不应该尝试从错误中恢复程序。

ClassNotFoundException的产生原因主要是:Java支持使用反射方式在运行时动态加载类,例如使用Class.forName方法来动态地加载类时,可以将类名作为参数传递给上述方法从而将指定类加载到JVM内存中,如果这个类在类路径中没有被找到,那么此时就会在运行时抛出ClassNotFoundException异常。解决该问题需要确保所需的类连同它依赖的包存在于类路径中,常见问题在于类名书写错误。

另外还有一个导致ClassNotFoundException的原因:当一个类已经被某个类加载器加载到内存中了,此时另一个类加载器又尝试着动态地从同一个包中加载这个类。通过控制动态类加载过程,可以避免上述情况发生。

NoClassDefFoundError产生的原因在于:如果JVM或者ClassLoader实例尝试加载类的时候却找不到类的定义。要查找的类在编译的时候是存在的,运行的时候却找不到了。这个时候就会导致NoClassDefFoundError。

造成该问题的原因可能是打包过程漏掉了部分类,或者jar包出现损坏或者篡改。解决这个问题的办法是查找那些在开发期间存在于类路径下但在运行期间却不在类路径下的类。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
回答: Java基础面试题可以包括很多方面的知识,以下是一些常见的问题和答案: 1. 什么是JNI? JNI是Java Native Interface的缩写,它提供了一组API,用于实现Java和其他语言(主要是C和C++)之间的通信。JNI允许Java代码与本地已编译的代码进行交互,尽管这可能会降低平台的可移植性。\[2\] 2. JNI的步骤是什么? JNI的步骤包括以下几个部分: - 在Java类中编写带有native声明的方法。 - 使用javac命令编译Java类。 - 使用javah命令生成头文件。 - 使用C/C++实现本地方法。 - 生成动态连接库。 - 执行Java代码。\[1\] 3. 请解释一下super.getClass()方法的作用。 super.getClass()方法是用于获取当前对象的父类的Class对象。在给定的示例中,Test类继承自Date类,当调用super.getClass().getName()时,会返回Test类的名称。因此,输出的结果是"Test"。\[3\] 希望以上回答能够帮助你理解Java基础面试题。如果你有其他问题,请随时提问。 #### 引用[.reference_title] - *1* *2* [Java基础常见面试题及详细答案(总结40个)](https://blog.csdn.net/ayouki123456/article/details/124983188)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [Java基础面试题50题](https://blog.csdn.net/weixin_38337769/article/details/100560220)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值