2023年最全!最真实!Java基础面试题!!!
2023年最全!最真实!Java基础面试题!!!
1.1、⾯向对象的三个基本特征?
⾯向对象的三个基本特征是:封装、继承和多态。
继承:让某个类型的对象获得另⼀个类型的对象的属性和⽅法。继承就是⼦类继承⽗类的特征和⾏为,使得⼦类对象(实例)具有⽗类的实例域和⽅法,或⼦类从⽗类继承⽅法,使得⼦类具有⽗类相同的⾏为。
封装:隐藏部分对象的属性和实现细节,对数据的访问只能通过外公开的接⼝。通过这种⽅式,对象对内部数据提供了不同级别的保护,以防⽌程序中⽆关的部分意外的改变或错误的使⽤了对象的私有部分。
多态:对于同⼀个⾏为,不同的⼦类对象具有不同的表现形式。多态存在的3个条件:
1)继承;
2)重写;
3)⽗类引⽤指向⼦类对象。
举个简单的例⼦:英雄联盟⾥⾯我们按下 Q 键这个动作:
• 对于亚索,就是斩钢闪
• 对于提莫,就是致盲吹箭
• 对于剑圣,就是阿尔法突袭
同⼀个事件发⽣在不同的对象上会产⽣不同的结果。
下⾯再举个简单的例⼦帮助⼤家理解,这个例⼦可能不是完全准确,但是依然是可以帮助我们理解的。
public class Animal { // 动物
public void sleep() {
System.out.println("躺着睡");
}
}
class Horse extends Animal { // ⻢ 是⼀种动物
public void sleep() {
System.out.println("站着睡");
}
}
class Cat extends Animal { // 猫 是⼀种动物
private int age;
public int getAge() {
return age + 1;
}
@Override
public void sleep() {
System.out.println("四脚朝天的睡");
}
}
在这个例⼦中:
House 和 Cat 都是 Animal,所以他们都继承了 Animal,同时也从 Animal 继承了 sleep 这个⾏为。但是针对 sleep 这个⾏为,House 和 Cat 进⾏了重写,有了不同的表现形式(实现),这个我们称为多态。
在 Cat ⾥,将 age 属性定义为 private,外界⽆法直接访问,要获取 Cat 的 age 信息只能通过 getAge⽅法,从⽽对外隐藏了 age 属性,这个就叫做封装。当然,这边 age 只是个例⼦,实际使⽤中可能是⼀个复杂很多的对象。
1.2、访问修饰符public,private,protected,以及不写(
default)时的区别?
修饰符 | 当前类 | 同包 | ⼦类 | 其他包 |
---|---|---|---|---|
public | √ | √ | √ | √ |
protected | √ | √ | √ | × |
default | √ | √ | × | × |
private | √ | × | × | × |
类的成员不写访问修饰符默认为default,默认对于同⼀个包的其他类相当于公开(public),对于不是同⼀个包的其他类相当于私有(private)。受保护(protected)对⼦类相当于公开,对于不是同⼀个包没有⽗⼦关系的类相当于私有。
Java中,外部类的修饰符只能是public或默认,类的成员(包括内部类)的修饰符可以是以上四种。
1.3、下⾯两个代码块能正常编译和执⾏吗?
// 代码块1
short s1 = 1; s1 = s1 + 1;
// 代码块2
short s1 = 1; s1 += 1;
代码块1编译报错,错误原因是:不兼容的类型: 从int转换到short可能会有损失”。
代码块2正常编译和执⾏。
我们将代码块2进⾏编译,字节码如下:
public class com.joonwhee.open.demo.Convert {
public com.joonwhee.open.demo.Convert();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: iconst_1 // 将int类型值1⼊(操作数)栈
1: istore_1 // 将栈顶int类型值保存到局部变量1中
2: iload_1 // 从局部变量1中装载int类型值⼊栈
3: iconst_1 // 将int类型值1⼊栈
4: iadd // 将栈顶两int类型数相加,结果⼊栈
5: i2s // 将栈顶int类型值截断成short类型值,后带符号扩展成int类型值⼊栈。
6: istore_1 // 将栈顶int类型值保存到局部变量1中
7: return
}
可以看到字节码中包含了 i2s 指令,该指令⽤于将 int 转成 short。i2s 是 int to short 的缩写。其实,s1 += 1 相当于 s1 = (short)(s1 + 1),有兴趣的可以⾃⼰编译下这两⾏代码的字节码,你会发现是⼀摸⼀样的。
说好的 Java 基础题,怎么⼜开始变态起来了???
1.4、基础考察,指出下题的输出结果
public static void main(String[] args) {
Integer a = 128, b = 128, c = 127, d = 127;
System.out.println(a == b);
System.out.println(c == d);
}
答案是:false,true。
执⾏ Integer a = 128,相当于执⾏:Integer a = Integer.valueOf(128),基本类型⾃动转换为包装类的过程称为⾃动装箱(autoboxing)。
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
在 Integer 中引⼊了 IntegerCache 来缓存⼀定范围的值,IntegerCache 默认情况下范围为:-128~127。本题中的 127 命中了 IntegerCache,所以 c 和 d 是相同对象,⽽ 128 则没有命中,所以 a 和 b 是不同对象。但是这个缓存范围时可以修改的,可能有些⼈不知道。可以通过JVM启动参数:
-XX:AutoBoxCacheMax=<size> 来修改上限值,如下图所⽰:
1.5、⽤最有效率的⽅法计算2乘以8?
2 << 3。(左移 相当于乘以2的⼏次幂 n << m 相当于n乘2的m次幂)
进阶:通常情况下,可以认为位运算是性能最⾼的。但是,其实编译器现在已经“⾮常聪明了”,很
多指令编译器都能⾃⼰做优化。所以在实际实⽤中,我们⽆需特意去追求实⽤位运算,这样不仅会导
致代码可读性很差,⽽且某些⾃作聪明的优化反⽽会误导编译器,使得编译器⽆法进⾏更好的优化。
1.6、&和&&的区别?
&&:逻辑与运算符。当运算符左右两边的表达式都为 true,才返回 true。同时具有短路性,如果第⼀个表达式为 false,则直接返回 false。
static boolean f1() { System.out.println( "function f1 called." );
return true;
}
static boolean f2() { System.out.println( "function f2 called." );
return false;
}
if ( false && f1() ) {} // f1不会被调⽤
if ( true || f2() ){} // f2不会被调⽤
&:逻辑与运算符、按位与运算符。
以开关开灯论:
有这样两个开关,0为开关关闭,1为开关打开。
与运算进⾏的是这样的算法:
0&0=0,0&1=0,1&0=0,1&1=1
在与运算中两个开关是串联的,如果我们要开灯,需要两个开关都打开灯才会打开。 理解为A与B都打开,则开灯,所以是1&1=1,任意⼀个开关没打开,都不开灯,所以其他运算都是0。
按位与运算符:⽤于⼆进制的计算,只有对应的两个⼆进位均为1时,结果位才为1 ,否则为0。
逻辑与运算符:& 在⽤于逻辑与时,和 && 的区别是不具有短路性。所在通常使⽤逻辑与运算符都会使⽤ &&,⽽ & 更多的适⽤于位运算。
1.7、String 是 Java 基本数据类型吗
答:不是。Java 中的基本数据类型只有8个:byte、short、int、long、float、double、char、
boolean;除了基本类型(primitive type),剩下的都是引⽤类型(reference type)。
基本数据类型:数据直接存储在栈上
引⽤数据类型区别:数据存储在堆上,栈上只存储引⽤地址。
1.8、String 类可以继承吗?
不⾏。String 类使⽤ final 修饰,⽆法被继承。
1.9、String和StringBuilder、StringBuffer的区别?
String:String 的值被创建后不能修改,任何对 String 的修改都会引发新的 String 对象的⽣成。
StringBuffer:跟 String 类似,但是值可以被修改,使⽤ synchronized 来保证线程安全。
StringBuilder:StringBuffer 的⾮线程安全版本,没有使⽤ synchronized,具有更⾼的性能,推荐优先使⽤。
1.10、String s = new String("xyz") 创建了⼏个字符串对象?
⼀个或两个。如果字符串常量池已经有“xyz”,则是⼀个;否则,两个。当字符串常量池没有 “xyz”,此时会创建如下两个对象:
⼀个是字符串字⾯量 "xyz" 所对应的、驻留(intern)在⼀个全局共享的字符串常量池中的实例,此时该实例也是在堆中,字符串常量池只放引⽤。
另⼀个是通过 new String() 创建并初始化的,内容与"xyz"相同的实例,也是在堆中。
1.11、String s = "xyz" 和 String s = new String("xyz") 区别?
⼀个或两个。如果字符串常量池已经有“xyz”,则是⼀个;否则,两个。
当字符串常量池没有 “xyz”,此时会创建如下两个对象:
⼀个是字符串字⾯量 "xyz" 所对应的、驻留(intern)在⼀个全局共享的字符串常量池中的实例,此
时该实例也是在堆中,字符串常量池只放引⽤。
另⼀个是通过 new String() 创建并初始化的,内容与"xyz"相同的实例,也是在堆中。
1.12、== 和 equals 的区别是什么?
==:运算符,⽤于⽐较基础类型变量和引⽤类型变量。
对于基础类型变量,⽐较的变量保存的值是否相同,类型不⼀定要相同。
short s1 = 1; long l1 = 1;
// 结果:true。类型不同,但是值相同
System.out.println(s1 == l1);
//对于引⽤类型变量,⽐较的是两个对象的地址是否相同。
Integer i1 = new Integer(1);
Integer i2 = new Integer(1);
// 结果:false。通过new创建,在内存中指向两个不同的对象
System.out.println(i1 == i2);
// 结果:false。通过new创建,在内存中指向两个不同的对象
System.out.println(i1 == i2);
Integer i1 = new Integer(1);
Integer i2 = new Integer(1);
// 结果:true。两个不同的对象,但是具有相同的值
System.out.println(i1.equals(i2));
// Integer的equals重写⽅法
public boolean equals(Object obj) {
if (obj instanceof Integer) {
// ⽐较对象中保存的值是否相同
return value == ((Integer)obj).intValue();
}
return false;
}
1.13、两个对象的 hashCode() 相同,则 equals() 也?定为 true,对吗?
不对。hashCode() 和 equals() 之间的关系如下:
当有 a.equals(b) true 时,则 a.hashCode() b.hashCode() 必然成?,反过来,当a.hashCode() == b.hashCode() 时,a.equals(b) 不?定为 true。
1.14、什么是反射?
反射是指在运?状态中,对于任意?个类都能够知道这个类所有的属性和?法;并且对于任意?个对象,都能够调?它的任意?个?法;这种动态获取信息以及动态调?对象?法的功能称为反射机制。
反射涉及到四个核?类:
java.lang.Class.java:类对象;
java.lang.reflect.Constructor.java:类的构造器对象;
java.lang.reflect.Method.java:类的?法对象;
java.lang.reflect.Field.java:类的属性对象;
反射有什么??
操作因访问权限限制的属性和?法;
实现?定义注解;
动态加载第三?jar包;
按需加载类,节省编译和初始化APK的时间;
反射工作作原理:
当我们编写完?个Java项?之后,每个java?件都会被编译成?个.class?件,这些Class对象承载了这个类的所有信息,包括?类、接?、构造函数、?法、属性等,这些class?件在程序运?时会被ClassLoader加载到虚拟机中。当?个类被加载以后,Java虚拟机就会在内存中?动产??个Class对象。我们通过new的形式创建对象实际上就是通过这些Class来创建,只是这个过程对于我们是不透明的?已。反射的?作原理就是借助Class.java、Constructor.java、Method.java、Field.java这四个类在程序运?时动态访问和修改任何类的?为及状态。
1.15、深拷贝和浅拷贝区别是什么?
数据分为基本数据类型和引⽤数据类型。基本数据类型:数据直接存储在栈中;引⽤数据类型:存储在栈中的是对象的引⽤地址,真实的对象数据存放在堆内存⾥。
浅拷贝:对于基础数据类型:直接复制数据值;对于引⽤数据类型:只是复制了对象的引⽤地址,新
旧对象指向同⼀个内存地址,修改其中⼀个对象的值,另⼀个对象的值随之改变。
深拷贝:对于基础数据类型:直接复制数据值;对于引⽤数据类型:开辟新的内存空间,在新的内存空间⾥复制⼀个⼀模⼀样的对象,新⽼对象不共享内存,修改其中⼀个对象的值,不会影响另⼀个对象。
深拷贝相⽐于浅拷贝速度较慢并且花销较⼤。
举个例⼦这就好⽐两兄弟⼤家买⾐服可以⼀⼈⼀套,然后房⼦⼤家住在⼀套房子里(浅拷贝),当两个⼈成家⽴业了,房⼦分开了⼀⼈⼀套互不影响(深拷⻉)
1.16、并发和并⾏有什么区别?
并发:两个或多个事件在同⼀时间间隔发⽣。
并行:两个或者多个事件在同⼀时刻发⽣。
并⾏是真正意义上,同⼀时刻做多件事情,⽽并发在同⼀时刻只会做⼀件事件,只是可以将时间切
碎,交替做多件事情。
并⾏在多处理器系统中存在,⽽并发可以在单处理器和多处理器系统中都存在,并发能够在单处理器系统中存在是因为并发是并⾏的假象,并⾏要求程序能够同时执⾏多个操作,⽽并发只是要求程序假装同时执⾏多个操作(每个⼩时间⽚执⾏⼀个操作,多个操作快速切换执⾏)。
当系统有⼀个以上 CPU 时,则线程的操作有可能⾮并发。当⼀个 CPU 执⾏⼀个线程时,另⼀个 CPU可以执⾏另⼀个线程,两个线程互不抢占 CPU 资源,可以同时进⾏,这种⽅式我们称之为并⾏(Parallel)。
并发编程的⽬标是充分的利⽤处理器的每⼀个核,以达到最⾼的处理性能。
1.17、当⼀个对象被当作参数传递到⼀个⽅法后,此⽅法可改变这个对象的属性,并可返回变化后的结果,那么这⾥到底是值传递还是引⽤传递?
值传递。Java 中只有值传递,对于对象参数,值的内容是对象的引⽤。
1.18、重载(Overload)和重写(Override)的区别?
⽅法的重载和重写都是实现多态的⽅式,区别在于前者实现的是编译时的多态性,⽽后者实现的是运
⾏时的多态性。
重载:⼀个类中有多个同名的⽅法,但是具有有不同的参数列表(参数类型不同、参数个数不同或者
⼆者都不同)。
重写:发⽣在⼦类与⽗类之间,⼦类对⽗类的⽅法进⾏重写,参数都不能改变,返回值类型可以不相
同,但是必须是⽗类返回值的派⽣类。即外壳不变,核⼼重写!重写的好处在于⼦类可以根据需要,
定义特定于⾃⼰的⾏为。
1.19、构造器是否可被重写?
Constructor 不能被 override(重写),但是可以 overload(重载),所以你可以看到⼀个类中有多个构造函数的情况。
1.20、为什么不能根据返回类型来区分重载?
如果我们有两个⽅法如下,当我们调⽤:test(1) 时,编译器⽆法确认要调⽤的是哪个。
// ⽅法1
int test(int a);
// ⽅法2
long test(int a);
⽅法的返回值只是作为⽅法运⾏之后的⼀个“状态”,但是并不是所有调⽤都关注返回值,所以不能将返回值作为重载的唯⼀区分条件。
1.21、Java 静态变量和成员变量的区别。
public class Demo {
/**
* 静态变量:⼜称类变量,static修饰
*/
public static String STATIC_VARIABLE = "静态变量";
/**
* 实例变量:⼜称成员变量,没有static修饰
*/
public String INSTANCE_VARIABLE = "实例变量";
}
成员变量存在于堆内存中。静态变量存在于⽅法区中。
成员变量与对象共存亡,随着对象创建⽽存在,随着对象被回收⽽释放。静态变量与类共存亡,随着类的加载⽽存在,随着类的消失⽽消失。
成员变量所属于对象,所以也称为实例变量。静态变量所属于类,所以也称为类变量。
成员变量只能被对象所调⽤ 。静态变量可以被对象调⽤,也可以被类名调⽤。
1.22、是否可以从⼀个静态(static)⽅法内部发出对⾮静态(non static)⽅法的调⽤?
区分两种情况,发出调⽤时是否显⽰创建了对象实例。
1)没有显⽰创建对象实例:不可以发起调⽤,⾮静态⽅法只能被对象所调⽤,静态⽅法可以通过对
象调⽤,也可以通过类名调⽤,所以静态⽅法被调⽤时,可能还没有创建任何实例对象。因此通过静
态⽅法内部发出对⾮静态⽅法的调⽤,此时可能⽆法知道⾮静态⽅法属于哪个对象。
public class Demo {
public static void staticMethod() {
// 直接调⽤⾮静态⽅法:编译报错
instanceMethod();
}
public void instanceMethod() {
System.out.println("⾮静态⽅法");
}
}
2)显⽰创建对象实例:可以发起调⽤,在静态⽅法中显⽰的创建对象实例,则可以正常的调⽤。
public class Demo {
public static void staticMethod() {
// 先创建实例对象,再调⽤⾮静态⽅法:成功执⾏
Demo demo = new Demo();
demo.instanceMethod();
}
public void instanceMethod() {
System.out.println("⾮静态⽅法");
}
}
1.23、初始化考察,请指出下⾯程序的运⾏结果。
public class InitialTest {
public static void main(String[] args) {
A ab = new B();
ab = new B();
}
}
class A {
static { // ⽗类静态代码块
System.out.print("A");
}
public A() { // ⽗类构造器
System.out.print("a");
}
}
class B extends A {
static { // ⼦类静态代码块
System.out.print("B");
}
public B() { // ⼦类构造器
System.out.print("b");
}
}
执⾏结果:ABabab,两个考察点:
1)静态变量只会初始化(执⾏)⼀次。
2)当有⽗类时,完整的初始化顺序为:⽗类静态变量(静态代码块)->⼦类静态变量(静态代码
块)->⽗类⾮静态变量(⾮静态代码块)->⽗类构造器 ->⼦类⾮静态变量(⾮静态代码块)->⼦类构
造器 。
1.24、抽象类(abstract class)和接⼝(interface)有什么区别?
1)抽象类只能单继承,接⼝可以多实现。
2)抽象类可以有构造⽅法,接⼝中不能有构造⽅法。
3)抽象类中可以有成员变量,接⼝中没有成员变量,只能有常量(默认就是 public static final)
4)抽象类中可以包含⾮抽象的⽅法,在 Java 7 之前接⼝中的所有⽅法都是抽象的,在 Java 8 之
后,接⼝⽀持⾮抽象⽅法:default ⽅法、静态⽅法等。Java 9 ⽀持私有⽅法、私有静态⽅法。
5)抽象类中的抽象⽅法类型可以是任意修饰符,Java 8 之前接⼝中的⽅法只能是 public 类型,Java
9 ⽀持 private 类型。
设计思想的区别:
接⼝是⾃上⽽下的抽象过程,接⼝规范了某些⾏为,是对某⼀⾏为的抽象。我需要这个⾏为,我就去实现某个接⼝,但是具体这个⾏为怎么实现,完全由⾃⼰决定。
抽象类是⾃下⽽上的抽象过程,抽象类提供了通⽤实现,是对某⼀类事物的抽象。我们在写实现类的时候,发现某些实现类具有⼏乎相同的实现,因此我们将这些相同的实现抽取出来成为抽象类,然后如果有⼀些差异点,则可以提供抽象⽅法来⽀持⾃定义实现。
⽹上看到有个说法,挺形象的:
普通类像亲爹 ,他有啥都是你的。
抽象类像叔伯,有⼀部分会给你,还能指导你做事的⽅法。
接⼝像⼲爹,可以给你指引⽅法,但是做成啥样得你⾃⼰努⼒实现。
1.25、Java 中的 final 关键字有哪些⽤法?
修饰类:该类不能再派⽣出新的⼦类,不能作为⽗类被继承。因此,⼀个类不能同时被声明为
abstract 和 final。
修饰⽅法:该⽅法不能被⼦类重写。
修饰变量:该变量必须在声明时给定初值,⽽在以后只能读取,不可修改。 如果变量是对象,则指的
是引⽤不可修改,但是对象的属性还是可以修改的。
public class FinalDemo {
// 不可再修改该变量的值
public static final int FINAL_VARIABLE = 0;
// 不可再修改该变量的引⽤,但是可以直接修改属性值
public static final User USER = new User();
public static void main(String[] args) {
// 输出:User(id=0, name=null, age=0)
System.out.println(USER);
// 直接修改属性值
USER.setName("test");
// 输出:User(id=0, name=test, age=0)
System.out.println(USER);
}
}
1.26、阐述 final、finally、finalize 的区别。
其实是三个完全不相关的东西,只是⻓的有点像。
final 如上所⽰。
finally:finally 是对 Java 异常处理机制的最佳补充,通常配合 try、catch 使⽤,⽤于存放那些⽆论是否出现异常都⼀定会执⾏的代码。在实际使⽤中,通常⽤于释放锁、数据库连接等资源,把资源释放⽅法放到 finally 中,可以⼤⼤降低程序出错的⼏率。
finalize:Object 中的⽅法,在垃圾收集器将对象从内存中清除出去之前做必要的清理⼯作。
finalize()⽅法仅作为了解即可,在 Java 9 中该⽅法已经被标记为废弃,并添加新的java.lang.ref.Cleaner,提供了更灵活和有效的⽅法来释放资源。这也侧⾯说明了,这个⽅法的设计是失败的,因此更加不能去使⽤它。
1.27、try、catch、finally 考察,请指出下⾯程序的运⾏结果(1)。
public class TryDemo {
public static void main(String[] args) {
System.out.println(test());
}
public static int test() {
try {
return 1;
} catch (Exception e) {
return 2;
} finally {
System.out.print("3");
}
}
}
执⾏结果:31。
相信很多同学应该都做对了,try、catch。finally 的基础⽤法,在 return 前会先执⾏ finally 语句块,所以是先输出 finally ⾥的 3,再输出 return 的 1。
1.28、try、catch、finally 考察,请指出下⾯程序的运⾏结果(2)。
public class TryDemo {
public static void main(String[] args) {
System.out.println(test1());
}
public static int test1() {
try {
return 2;
} finally {
return 3;
}
}
}
执⾏结果:3。
这题有点意思,但也不难,try 返回前先执⾏ finally,结果 finally ⾥不按套路出牌,直接 return 了,
⾃然也就⾛不到 try ⾥⾯的 return 了。
finally ⾥⾯使⽤ return 仅存在于⾯试题中,实际开发中千万不要这么⽤。
1.29、try、catch、finally 考察3,请指出下⾯程序的运⾏结果(3)。
public class TryDemo {
public static void main(String[] args) {
System.out.println(test1());
}
public static int test1() {
int i = 0;
try {
i = 2;
return i;
} finally {
i = 3;
}
}
}
i执⾏结果:2。
这边估计有不少同学会以为结果应该是 3,因为我们知道在 return 前会执⾏ finally,⽽ 在 finally 中被修改为 3 了,那最终返回 i 不是应该为 3 吗?
这边的根本原因是,在执⾏ finally 之前,JVM 会先将 i 的结果暂存起来,然后 finally 执⾏完毕后,会返回之前暂存的结果,⽽不是返回 i,所以即使这边 i 已经被修改为 3,最终返回的还是之前暂存起来的结果 2。
这边其实根据字节码可以很容易看出来,在进⼊ finally 之前,JVM 会使⽤ iload、istore 两个指令,将结果暂存,在最终返回时在通过 iload、ireturn 指令返回暂存的结果。
1.30、Error 和 Exception 有什么区别?
Error 和 Exception 都是 Throwable 的⼦类,⽤于表⽰程序出现了不正常的情况。区别在于:
Error 表⽰系统级的错误和程序不必处理的异常,是恢复不是不可能但很困难的情况下的⼀种严重问题,⽐如内存溢出,不可能指望程序能处理这样的情况。Exception 表⽰需要捕捉或者需要程序进⾏处理的异常,是⼀种设计或实现问题,也就是说,它表⽰如果程序运⾏正常,从不会发⽣的情况。
1.31、JDK1.8之后有哪些新特性?
1)接⼝默认⽅法:Java 8 允许我们给接⼝添加⼀个⾮抽象的⽅法实现,只需要使⽤ default 关键字
即可。
从Java 8开始,引⼊了接⼝默认⽅法,这样的好处也是很明显的,⾸先解决了 Java8 以前版本接⼝兼容性问题,同时对于我们以后的程序开发,也可以在接⼝⼦类中直接使⽤接⼝默认⽅法,⽽不再需要在各个⼦类中各⾃实现响应接⼝⽅法。
public interface IMathOperation {
/**
* 定义接⼝默认⽅法 ⽀持⽅法形参
*/
default void print(){
System.out.println("这是数值运算基本接⼝。。。");
}
/**
* 定义静态默认⽅法
*/
static void version(){
System.out.println("这是1.0版简易计算器");
}
}
public class MathOperationImpl implements IMathOperation {
@Override
public int add(int a, int b) {
// ⼦类中可以直接调⽤⽗类接⼝默认⽅法
IMathOperation.super.print();
// 调⽤⽗类静态默认⽅法
IMathOperation.version();
return a+b;
}
}
2)Lambda 表达式和函数式接⼝:Lambda 表达式本质上是⼀段匿名内部类,也可以是⼀段可以传递的代码。Lambda 允许把函数作为⼀个⽅法的参数(函数作为参数传递到⽅法中),使⽤ Lambda表达式使代码更加简洁,但是也不要滥⽤,否则会有可读性等问题,《Effective Java》作者 JoshBloch 建议使⽤ Lambda 表达式最好不要超过3⾏。
匿名内部类
@Test
public void test1(){
Comparator<Integer> com = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return Integer.compare(o1, o2);
}
};
TreeSet<Integer> treeSet = new TreeSet<>(com);
}
Lambda 表达式
Java
1 Comparator<Integer> com = (x, y) -> Integer.compare(x, y);
函数式接⼝
Lambda表达式需要函数式接⼝的⽀持,所以,我们有必要来说说什么是函数式接⼝。
只包含⼀个抽象⽅法的接⼝,称为函数式接⼝。 可以通过 Lambda 表达式来创建该接⼝的对象。(若 Lambda表达式抛出⼀个受检异常,那么该异常需要在⽬标接⼝的抽象⽅法上进⾏声明)。可以在任意函数式接⼝上使⽤ @FunctionalInterface 注解,这样做可以检查它是否是⼀个函数式接⼝,同时 javadoc 也会包含⼀条声明,说明这个接⼝是⼀个函数式接⼝。
@FunctionalInterface
public interface MyFunc <T> {
public T getValue(T t);
}
public String handlerString(MyFunc<String> myFunc, String str){
return myFunc.getValue(str);
}
@Test
public void test6(){
String str = handlerString((s) -> s.toUpperCase(), "binghe");
System.out.println(str);//输出结果BINGHE
}
3)Stream API:⽤函数式编程⽅式在集合类上进⾏复杂操作的⼯具,配合Lambda表达式可以⽅便
的对集合进⾏处理。Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进⾏的操作,可以
执⾏⾮常复杂的查找、过滤和映射数据等操作。使⽤Stream API 对集合数据进⾏操作,就类似于使
⽤ SQL 执⾏的数据库查询。也可以使⽤ Stream API 来并⾏执⾏操作。简⽽⾔之,Stream API 提供了
⼀种⾼效且易于使⽤的处理数据的⽅式。
List<Teacher> teacherList = new ArrayList<>();
teacherList.add(new Teacher("张磊",22,"zl"));
teacherList.add(new Teacher("李鹏",36,"lp"));
teacherList.add(new Teacher("刘敏",50,"lm"));
teacherList.add(new Teacher("宋亚楠",62,"syn"));
teacherList.add(new Teacher("彩彬",18,"cb"));
//filter 过滤
List<Teacher> list = teacherList.stream().filter(x -> x.getAge() > 30).collect(Collectors.toList());
//joining拼接 所有⽼师姓名拼接成字符串
String nameJoin = teacherList.stream().map(Teacher::getName).collect(Collectors.joining(","));
//排序
List sortList = teacherList.stream().sorted(Comparator.comparing(Teacher::getAge).reversed()).collect(Collectors.toList());
System.out.println(nameJoin);
System.out.println(list);
System.out.println("按年龄降序: "+sortList);
/*输出结果
*[Teacher(name=李鹏, age=36, nikeName=lp), Teacher(name=刘敏, age=50,
nikeName=lm), Teacher(name=宋亚楠, *age=62, nikeName=syn)]
*张磊,李鹏,刘敏,宋亚楠,彩彬
*按年龄降序: [Teacher(name=宋亚楠, age=62, nikeName=syn), Teacher(name=刘敏,
age=50, nikeName=lm), *Teacher(name=李鹏, age=36, nikeName=lp), Teacher(name=
张磊, age=22, nikeName=zl), Teacher(name=彩彬, age=18, nikeName=cb)]
*/
4)⽅法引⽤:⽅法引⽤提供了⾮常有⽤的语法,可以直接引⽤已有Java类或对象(实例)的⽅法或构造器。与lambda联合使⽤,⽅法引⽤可以使语⾔的构造更紧凑简洁,减少冗余代码。
⽅法引⽤就是操作符“::”将⽅法名和对象或类的名字分隔开来。
如下三种使⽤情况:
• 对象::实例⽅法
• 类::静态⽅法
• 类::实例⽅法
5)⽇期时间API:Java 8 引⼊了新的⽇期时间API改进了⽇期时间的管理。在Java 8之前,所有关于
时间和⽇期的API都存在各种使⽤⽅⾯的缺陷,主要有:
Java的java.util.Date和java.util.Calendar类易⽤性差,不⽀持时区,⽽且他们都不是线程安全
的;
1.⽤于格式化⽇期的类DateFormat被放在java.text包中,它是⼀个抽象类,所以我们需要实例化
⼀个SimpleDateFormat对象来处理⽇期格式化,并且DateFormat也是⾮线程安全,这意味着
如果你在多线程程序中调⽤同⼀个DateFormat对象,会得到意想不到的结果。
a.对⽇期的计算⽅式繁琐,⽽且容易出错,因为⽉份是从0开始的,从Calendar中获取的⽉份
需要加⼀才能表⽰当前⽉份。
i.由于以上这些问题,出现了⼀些三⽅的⽇期处理框架,例如Joda-Time,date4j等开源项⽬。但是,
Java需要⼀套标准的⽤于处理时间和⽇期的框架,于是Java 8中引⼊了新的⽇期API。新的⽇期API是
JSR-310规范的实现,Joda-Time框架的作者正是JSR-310的规范的倡导者,所以能从Java 8的⽇期
API中看到很多Joda-Time的特性。
Java 8的⽇期和时间类包含
LocalDate 、 LocalTime 、 Instant 、 Duration 以及 Period ,这些类都包含在
java.time 包中,下⾯我们看看这些类的⽤法。
6)Optional 类:著名的 NullPointerException 是引起系统失败最常⻅的原因。很久以前 Google
Guava 项⽬引⼊了 Optional 作为解决空指针异常的⼀种⽅式,不赞成代码被 null 检查的代码污染,
期望程序员写整洁的代码。受Google Guava的⿎励,Optional 现在是Java 8库的⼀部分。
7)新⼯具:新的编译⼯具,如:Nashorn引擎 jjs、 类依赖分析器 jdeps。
1.32、Java的多态表现在哪⾥
多态要有动态绑定,否则就不是多态,⽅法重载也不是多态(因为⽅法重载是编译期决定好的,没有后期也
就是运⾏期的动态绑定)当满⾜这三个条件:
1.有继承 2. 有重写 3. 要有⽗类引⽤指向⼦类对象
1.33、接⼝有什么用
1、重要性:在Java语⾔中, abstract class 和interface 是⽀持抽象类定义的两种机制。正是由于这两种机制的存在,才赋予了Java强⼤的 ⾯向对象能⼒。
2、简单、规范性:如果⼀个项⽬⽐较庞⼤,那么就需要⼀个能理清所有业务的架构师来定义⼀些主要的接⼝,这些接⼝不仅告诉开发⼈员你需要实现那些业务,⽽且也将命名规范限制住了(防⽌⼀些开发⼈员随便命名导致别的程序员⽆法看明⽩)。
3、维护、拓展性:⽐如你要做⼀个画板程序,其中⾥⾯有⼀个⾯板类,主要负责绘画功能,然后你就这样定义了这个类。可是在不久将来,你突然发现这个类满⾜不了你了,然后你⼜要重新设计这个类,更糟糕是你可能要放弃这个类,那么其他地⽅可能有引⽤他,这样修改起来很⿇烦。
如果你⼀开始定义⼀个接⼝,把绘制功能放在接⼝⾥,然后定义类时实现这个接⼝,然后你只要⽤这个接⼝去引⽤实现它的类就⾏了,以后要换的话只不过是引⽤另⼀个类⽽已,这样就达到维护、拓展的⽅便性。
4、安全、严密性:接⼝是实现软件松耦合的重要⼿段,它描叙了系统对外的所有服务,⽽不涉及任何具体的实现细节。这样就⽐较安全、严密⼀些(⼀般软件服务商考虑的⽐较多)。
1.34、说说http,https协议
HTTPS(Secure Hypertext Transfer Protocol)安全超⽂本传输协议:
它是⼀个安全通信通道,它基于HTTP开发,⽤于在客⼾计算机和服务器之间交换信息,它使⽤安全套接字层(SSL)进⾏信息交换,简单来说它是HTTP的安全版。
它是由Netscape开发并内置于其浏览器中,⽤于对数据进⾏压缩和解压操作,并返回⽹络上传送回的结果。HTTPS实际上应⽤了Netscape的安全全套接字层(SSL)作为HTTP应⽤层的⼦层。
(HTTPS使⽤端⼝443,⽽不是象HTTP那样使⽤端⼝80来和TCP/IP进⾏通信。)SSL使⽤40 位关键字作为RC4流加密算法,这对于商业信息的加密是合的。
HTTPS和SSL⽀持使⽤X.509数字认证,如果需要的话⽤⼾可以确认发送者是谁。总的来说,HTTPS协议是由SSL+HTTP协议构建的可进⾏加密传输、⾝份认证的⽹络协议要⽐http协议安全。
在URL前加https://前缀表明是⽤SSL加密的,你的电脑与服务器之间收发的信息传输将更加安全。Web服务器启⽤SSL需要获得⼀个服务器证书并将该证书与要使⽤SSL的服务器绑定。
HTTPS和HTTP的区别:
https协议需要到ca申请证书,⼀般免费证书很少,需要交费。
http是超⽂本传输协议,信息是明⽂传输,https 则是具有安全性的ssl加密传输协议。
http和https使⽤的是完全不同的连接⽅式⽤的端⼝也不⼀样,前者是80,后者是443。
http的连接很简单,是⽆状态的。
HTTPS协议是由SSL+HTTP协议构建的可进⾏加密传输、⾝份认证的⽹络协议 要⽐http协议安全。
HTTPS解决的问题:
1 . 信任主机的问题.
采⽤https 的server 必须从CA 申请⼀个⽤于证明服务器⽤途类型的证书. 改证书只有⽤于对应的server 的时候,客⼾度才信任此主机. 所以⽬前所有的银⾏系统⽹站,关键部分应⽤都是https 的.客⼾通过信任该证书,从⽽信任了该主机. 其实这样做效率很低,但是银⾏更侧重安全. 这⼀点对我们没有任何意义,我们的server ,采⽤的证书不管⾃⼰issue 还是从公众的地⽅issue, 客⼾端都是⾃⼰⼈,所以我们也就肯定信任该server.
2 . 通讯过程中的数据的泄密和被窜改
1) ⼀般意义上的https, 就是 server 有⼀个证书.
a) 主要⽬的是保证server 就是他声称的server. 这个跟第⼀点⼀样.
b) 服务端和客⼾端之间的所有通讯,都是加密的.
i. 具体讲,是客⼾端产⽣⼀个对称的密钥,通过server 的证书来交换密钥. ⼀般意义上的握⼿过程.
ii. 所有的信息往来就都是加密的. 第三⽅即使截获,也没有任何意义.因为他没有密钥. 当然窜改也就没有什么意义了.
2). 少许对客⼾端有要求的情况下,会要求客⼾端也必须有⼀个证书.
a) 这⾥客⼾端证书,其实就类似表⽰个⼈信息的时候,除了⽤⼾名/密码, 还有⼀个CA 认证过的⾝份.应为个⼈证书⼀般来说别⼈⽆法模拟的,所有这样能够更深的确认⾃⼰的⾝份.
b) ⽬前少数个⼈银⾏的专业版是这种做法,具体证书可能是拿U盘作为⼀个备份的载体.
3 .HTTPS ⼀定是繁琐的.
a) 本来简单的http协议,⼀个get⼀个response. 由于https 要还密钥和确认加密算法的需要.单握⼿就需要6/7 个往返.
i. 任何应⽤中,过多的round trip 肯定影响性能.
b) 接下来才是具体的http协议,每⼀次响应或者请求, 都要求客⼾端和服务端对会话的内容做加密/解密.
i. 尽管对称加密/解密效率⽐较⾼,可是仍然要消耗过多的CPU,为此有专⻔的SSL 芯⽚. 如果CPU 信能⽐较低的话,肯定会降低性能,从⽽不能serve 更多的请求.
ii. 加密后数据量的影响. 所以,才会出现那么多的安全认证提⽰
1.35、tcp/ip协议簇
TCP/IP协议簇是Internet的基础,也是当今最流⾏的组⽹形式。TCP/IP是⼀组协议的代名词,包括许多别的协议,组成了TCP/IP协议簇。其中⽐较重要的有SLIP协议、PPP协议、IP协议、ICMP协议、ARP协议、TCP协议、UDP协议、FTP协议、DNS协议、SMTP协议等。TCP/IP协议并不完全符合OSI的七层参考模型。传统的开放式系统互连参考模型,是⼀种通信协议的7层抽象的参考模型,其中每⼀层执⾏某⼀特定任务。该模型的⽬的是使各种硬件在相同的层次上相互通信。⽽TCP/IP通讯协议采⽤了4层的层级结构,每⼀层都呼叫它的下⼀层所提供的⽹络来完成⾃⼰的需求
SLIP协议编辑
SLIP提供在串⾏通信线路上封装IP分组的简单⽅法,使远程⽤⼾通过电话线和MODEM能⽅便地接⼊TCP/IP⽹络。SLIP是⼀种简单的组帧⽅式,但使⽤时还在⼀些问题。
⾸先,SLIP不⽀持在连接过程中的动态IP地址分配,通信双⽅必须事先告知对⽅IP地址,这给没有固定IP地址的个⼈⽤⼾上INTERNET⽹带来了很⼤的不便。
其次,SLIP帧中⽆校验字段,因此链路层上⽆法检测出差错,必须由上层实体或具有纠错能⼒
MODEM来解决传输差错问题。
PPP协议编辑
为了解决SLIP存在的问题,在串⾏通信应⽤中⼜开发了PPP协议。PPP协议是⼀种有效的点对点通信协议,它由串⾏通信线路上的组帧⽅式,⽤于建⽴、配制、测试和拆除数据链路的链路控制协议LCP及⼀组⽤以⽀持不同⽹络层协议的⽹络控制协议NCPs三部分组成。
PPP中的LCP协议提供了通信双⽅进⾏参数协商的⼿段,并且提供了⼀组NCPs协议,使得PPP可以⽀
持多种⽹络层协议,如IP,IPX,OSI等。
另外,⽀持IP的NCP提供了在建⽴链接时动态分配IP地址的功能,解决了个⼈⽤⼾上INTERNET⽹的问题。
IP协议编辑即互联⽹协议(Internet Protocol),它将多个⽹络连成⼀个互联⽹,可以把⾼层的数据以多个数据包
的形式通过互联⽹分发出去。IP的基本任务是通过互联⽹传送数据包,各个IP数据包之间是相互独⽴
的。
ICMP协议编辑
即互联⽹控制报⽂协议。从IP互联⽹协议的功能,可以知道IP 提供的是⼀种不可靠的⽆连接报⽂分组
传送服务。
若路由器或主机发⽣故障时⽹络阻塞,就需要通知发送主机采取相应措施。为了使互联⽹能报告差错,或提供有关意外情况的信息,在IP层加⼊了⼀类特殊⽤途的报⽂机制,即ICMP。
分组接收⽅利⽤ICMP来通知IP模块发送⽅,进⾏必需的修改。ICMP通常是由发现报⽂有问题的站产⽣的,例如可由⽬的主机或中继路由器来发现问题并产⽣的ICMP。
如果⼀个分组不能传送,ICMP便可以被⽤来警告分组源,说明有⽹络,主机或端⼝不可达。ICMP也
可以⽤来报告⽹络阻塞。
ARP协议编辑
即地址转换协议。在TCP/IP⽹络环境下,每个主机都分配了⼀个32位的IP地址,这种互联⽹地址是在⽹际范围标识主机的⼀种逻辑地址。为了让报⽂在物理⽹上传送,必须知道彼此的物理地址。这样就存在把互联⽹地址变换成物理地址的转换问题。这就需要在⽹络层有⼀组服务将 IP地址转换为
相应物理⽹络地址,这组协议即ARP。
TCP协议编辑
即传输控制协议,它提供的是⼀种可靠的数据流服务。当传送受差错⼲扰的数据,或举出⽹络故障,或⽹络负荷太重⽽使⽹际基本传输系统不能正常⼯作时,就需要通过其他的协议来保证通信的可靠。
TCP就是这样的协议。TCP采⽤“带重传的肯定确认”技术来实现传输的可靠性。并使⽤“滑动窗⼝”的流量控制机制来提⾼⽹络的吞吐量。TCP通信建⽴实现了⼀种“虚电路”的概念。
双⽅通信之前,先建⽴⼀条链接然后双⽅就可以在其上发送数据流。这种数据交换⽅式能提⾼效率,但事先建⽴连接和事后拆除连接需要开销。
UDP协议编辑
即⽤⼾数据包协议,它是对IP协议组的扩充,它增加了⼀种机制,发送⽅可以区分⼀台计算机上的多个接收者。每个UDP报⽂除了包含数据外还有报⽂的⽬的端⼝的编号和报⽂源端⼝的编号,从⽽使UDP软件可以把报⽂递送给正确的接收者,然后接收者要发出⼀个应答。由于UDP的这种扩充,使得在两个⽤⼾进程之间递送数据包成为可能。我们频繁使⽤的OICQ软件正是基于UDP协议和这种机制。
FTP协议编辑
即⽂件传输协议,它是⽹际提供的⽤于访问远程机器的协议,它使⽤⼾可以在本地机与远程机之间进⾏有关⽂件的操作。FTP⼯作时建⽴两条TCP链接,分别⽤于传送⽂件和⽤于传送控制。FTP采⽤客⼾/服务器模式?它包含客⼾FTP和服务器FTP。客⼾FTP启动传送过程,⽽服务器FTP对其作出应答。
DNS协议编辑
即域名服务协议,它提供域名到IP地址的转换,允许对域名资源进⾏分散管理。DNS最初设计的⽬的是使邮件发送⽅知道邮件接收主机及邮件发送主机的IP地址,后来发展成可服务于其他许多⽬标的协议。
SMTP协议编辑
即简单邮件传送协议互联⽹标准中的电⼦邮件是⼀个简单的基于⽂本的协议,⽤于可靠、有效地数据传输。SMTP作为应⽤层的服务,并不关⼼它下⾯采⽤的是何种传输服务,它可通过⽹络在TXP链接上传送邮件,或者简单地在同⼀机器的进程之间通过进程通信的通道来传送邮件,这样,邮件传输就独⽴于传输⼦系统,可在TCP/IP环境或X.25协议环境中传输邮件
1.36、tcp,udp区别
TCP(Transmission Control Protocol,传输控制协议)是⾯向连接的协议,也就是说,在收发数据前,必须和对⽅建⽴可靠的连接。⼀个TCP连接必须要经过三次“对话”才能建⽴起来,其中的过程⾮常复杂,只简单的描述下这三次对话的简单过程:
主机A向主机B发出连接请求数据包:“我想给你发数据,可以吗?”,这是第⼀次对话;主机B向主机A发送同意连接和要求同步(同步就是两台主机⼀个在发送,⼀个在接收,协调⼯作)的数据包:“可以,你什么时候发?”,这是第⼆次对话;主机A再发出⼀个数据包确认主机B的要求同步:“我现在就发,你接着吧!”,这是第三次对话。三次“对话”的⽬的是使数据包的发送和接收同步,经过三次“对话”之后,主机A才向主机B正式发送数据
UDP(User Data Protocol,⽤⼾数据报协议)
(1) UDP是⼀个⾮连接的协议,传输数据之前源端和终端不建⽴连接,当它想传送时就简单地去抓取来⾃应⽤程序的数据,并尽可能快地把它扔到⽹络上。在发送端,UDP传送数据的速度仅仅是受应⽤程序⽣成数据的速度、计算机的能⼒和传输带宽的限制;在接收端,UDP把每个消息段放在队列中,应⽤程序每次从队列中读⼀个消息段。
(2) 由于传输数据不建⽴连接,因此也就不需要维护连接状态,包括收发状态等,因此⼀台服务机可同时向多个客⼾机传输相同的消息。
(3) UDP信息包的标题很短,只有8个字节,相对于TCP的20个字节信息包的额外开销很⼩。
(4) 吞吐量不受拥挤控制算法的调节,只受应⽤软件⽣成数据的速率、传输带宽、源端和终端主机性能的限制。
(5)UDP使⽤尽最⼤努⼒交付,即不保证可靠交付,因此主机不需要维持复杂的链接状态表(这⾥⾯有许多参数)。
(6)UDP是⾯向报⽂的。发送⽅的UDP对应⽤程序交下来的报⽂,在添加⾸部后就向下交付给IP层。既不拆分,也不合并,⽽是保留这些报⽂的边界,因此,应⽤程序需要选择合适的报⽂⼤⼩。我们经常使⽤“ping”命令来测试两台主机之间TCP/IP通信是否正常,其实“ping”命令的原理就是向对⽅主机发送UDP数据包,然后对⽅主机确认收到数据包,如果数据包是否到达的消息及时反馈回来,那么⽹络就是通的。
UDP的包头结构:
源端⼝ 16位
⽬的端⼝ 16位
⻓度 16位
校验和 16位
⼩结TCP与UDP的区别:
1 .基于连接与⽆连接;
2 .对系统资源的要求(TCP较多,UDP少);
3 .UDP程序结构较简单;
4 .流模式与数据报模式 ;
5 .TCP保证数据正确性,UDP可能丢包,TCP保证数据顺序,UDP不保证。
1.37、⽤过哪些加密算法:对称加密,⾮对称加密算法
对称加密是最快速、最简单的⼀种加密⽅式,加密(encryption)与解密(decryption)⽤的是同样的密钥(secret key)。对称加密有很多种算法,由于它效率很⾼,所以被⼴泛使⽤在很多加密协议的核⼼当中。对称加密通常使⽤的是相对较⼩的密钥,⼀般⼩于256 bit。因为密钥越⼤,加密越强,但加密与解密的过程越慢。如果你只⽤1 bit来做这个密钥,那⿊客们可以先试着⽤0来解密,不⾏的话就再⽤1解;但如果你的密钥有1 MB⼤,⿊客们可能永远也⽆法破解,但加密和解密的过程要花费很⻓的时间。密钥的⼤⼩既要照顾到安全性,也要照顾到效率,是⼀个trade-off常⻅对称加密算法 DES算法,3DES算法TDEA算法,Blowfish算法,RC5算法,IDEA算法⾮对称加密为数据的加密与解密提供了⼀个⾮常安全的⽅法,它使⽤了⼀对密钥,公钥(public
key)和私钥(private key)。私钥只能由⼀⽅安全保管,不能外泄,⽽公钥则可以发给任何请求它的⼈。⾮对称加密使⽤这对密钥
中的⼀个进⾏加密,⽽解密则需要另⼀个密钥。
⽐如,你向银⾏请求公钥,银⾏将公钥发给你,你使⽤公钥对消息加密,那么只有私钥的持有⼈--银⾏才能对你的消息解密。与对称加密不同的是,银⾏不需要将私钥通过⽹络发送出去,因此安全性⼤⼤提⾼。
⽬前最常⽤的⾮对称加密算法是RSA算法 Elgamal、背包算法、Rabin、HD,ECC(椭圆曲线加密算
法)
1.38、说说tcp三次握⼿,四次挥⼿
据段:"我已收到回复,我现在要开始传输实际数据了
这样3次握⼿就完成了,主机A和主机B 就可以传输数据了.
3次握⼿的特点:
没有应⽤层的数据SYN这个标志位只有在TCP建产连接时才会被置1握⼿完成后SYN标志位被置0
TCP建⽴连接要进⾏3次握⼿,⽽断开连接要进⾏4次
1 当主机A完成数据传输后,将控制位FIN置1,提出停⽌TCP连接的请求
2 主机B收到FIN后对其作出响应,确认这⼀⽅向上的TCP连接将关闭,将ACK置1
3 由B 端再提出反⽅向的关闭请求,将FIN置1
4 主机A对主机B的请求进⾏确认,将ACK置1,双⽅向的关闭结束.
由TCP的三次握⼿和四次断开可以看出,TCP使⽤⾯向连接的通信⽅式,⼤⼤提⾼了数据通信的可靠性,使发送数据端和接收端在数据正式传输前就有了交互,为数据正式传输打下了可靠的基础
名词解释
ACK TCP报头的控制位之⼀,对数据进⾏确认.确认由⽬的端发出,⽤它来告诉发送端这个序列号之前的
数据段
都收到了.⽐如,确认号为X,则表⽰前X-1个数据段都收到了,只有当ACK=1时,确认号才有效,当ACK=0时,确认号⽆效,这时会要求重传数据,保证数据的完整性.
SYN 同步序列号,TCP建⽴连接时将这个位置1FIN 发送端完成发送任务位,当TCP完成数据传输需要断开时,提出断开连接的⼀⽅将这位置1
TCP的包头结构:
源端⼝ 16位
⽬标端⼝ 16位
序列号 32位
回应序号 32位
TCP头⻓度 4位
reserved 6位
控制代码 6位
窗⼝⼤⼩ 16位
偏移量 16位
校验和 16位
选项 32位(可选)
这样我们得出了TCP包头的最⼩⻓度,为20字节。
1.40、cookie和session的区别,分布式环境怎么保存⽤⼾状态
1、session保存在服务器,客⼾端不知道其中的信息;cookie保存在客⼾端,服务器能够知道其中的
信息。
2、session中保存的是对象,cookie中保存的是字符串。
3、session不能区分路径,同⼀个⽤⼾在访问⼀个⽹站期间,所有的session在任何⼀个地⽅都可以
访问到。⽽cookie中如果设置了路径参数,那么同⼀个⽹站中不同路径下的cookie互相是访问不到
的。
4、session需要借助cookie才能正常。如果客⼾端完全禁⽌cookie,session将失效。
分布式Session的⼏种实现⽅式
• 1 .基于数据库的Session共享
• 2 .基于NFS共享⽂件系统
• 3 .基于memcached 的session,如何保证 memcached 本⾝的⾼可⽤性?
• 4 . 基于resin/tomcat web容器本⾝的session复制机制
• 5 . 基于TT/Redis 或 jbosscache 进⾏ session 共享。
• 6 . 基于cookie 进⾏session共享
1.41、Git,svn区别;
GIT是分布式的,SVN不是:这是GIT和其它⾮分布式的版本控制系统,例如SVN,CVS等,最核⼼的
区
GIT把内容按元数据⽅式存储,⽽SVN是按⽂件
GIT分⽀和SVN的分⽀不同:
分⽀在SVN中⼀点不特别,就是版本库中的另外的⼀个⽬录。如果你想知道是否合并了⼀个分⽀,你
需要⼿⼯运⾏像这样的命令svn propget svn:mergeinfo,来确认代码是否被合并。
然⽽,处理GIT的分⽀却是相当的简单和有趣。你可以从同⼀个⼯作⽬录下快速的在⼏个分⽀间切
换。你很容易发现未被合并的分⽀,你能简单⽽快捷的合并这些⽂件
GIT没有⼀个全局的版本号,⽽SVN有
GIT的内容完整性要优于SVN
1.42、ThreadLocal可以⽤来共享数据吗;
ThreadLocal是基于线程对象的,类似于⼀个map ,key为当前线程对象,所以它可以在同线程内共享数
据
1.43、bio,nio,aio的区别
IO的⽅式通常分为⼏种,同步阻塞的BIO、同步⾮阻塞的NIO、异步⾮阻塞的AIO
BIO
在JDK1.4出来之前,我们建⽴⽹络连接的时候采⽤BIO模式,需要先在服务端启动⼀个ServerSocket,然后在客⼾端启动Socket来对服务端进⾏通信,
默认情况下服务端需要对每个请求建⽴⼀堆线程等待请求,⽽客⼾端发送请求后,先咨询服务端是否有线程相应,如果没有则会⼀直等待或者遭到拒绝请求,如果有的话,客⼾端会线程会等待请求结束后才继续执⾏。
⼆、NIO
NIO本⾝是基于事件驱动思想来完成的,其主要想解决的是BIO的⼤并发问题: 在使⽤同步I/O的⽹络应⽤中,如果要同时处理多个客⼾端请求,或是在客⼾端要同时和多个服务器进⾏通讯,就必须使⽤多线程来处理。
也就是说,将每⼀个客⼾端请求分配给⼀个线程来单独处理。这样做虽然可以达到我们的要求,但同时⼜会带来另外⼀个问题。由于每创建⼀个线程,就要为这个线程分配⼀定的内存空间(也叫⼯作存储器),⽽且操作系统本⾝也对线程的总数有⼀定的限制。如果客⼾端的请求过多,服务端程序可能会因为不堪重负⽽拒绝客⼾端的请求,甚⾄服务器可能会因此⽽瘫痪。
NIO基于Reactor,当socket有流可读或可写⼊socket时,操作系统会相应的通知引⽤程序进⾏处理,应⽤再将流读取到缓冲区或写⼊操作系统。
也就是说,这个时候,已经不是⼀个连接就要对应⼀个处理线程了,⽽是有效的请求,对应⼀个线程,当连接没有数据时,是没有⼯作线程来处理的。
BIO与NIO⼀个⽐较重要的不同,是我们使⽤BIO的时候往往会引⼊多线程,每个连接⼀个单独的线程;⽽NIO则是使⽤单线程或者只使⽤少量的多线程,每个连接共⽤⼀个线程。
NIO的最重要的地⽅是当⼀个连接创建后,不需要对应⼀个线程,这个连接会被注册到多路复⽤器上⾯,所以所有的连接只需要⼀个线程就可以搞定,
当这个线程中的多路复⽤器进⾏轮询的时候,发现连接上有请求的话,才开启⼀个线程进⾏处理,也就是⼀个请求⼀个线程模式。
在NIO的处理⽅式中,当⼀个请求来的话,开启线程进⾏处理,可能会等待后端应⽤的资源(JDBC连接等),其实这个线程就被阻塞了,当并发上来的话,还是会有BIO⼀样的问题。HTTP/1.1出现后,有了Http⻓连接,这样除了超时和指明特定关闭的http header外,这个链接是⼀直打开的状态的,这样在NIO处理中可以进⼀步的进化,在后端资源中可以实现资源池或者队列,
当请求来的话,开启的线程把请求和请求数据传送给后端资源池或者队列⾥⾯就返回,并且在全局的地⽅保持住这个现场(哪个连接的哪个请求等),这样前⾯的线程还是可以去接受其他的请求,
⽽后端的应⽤的处理只需要执⾏队列⾥⾯的就可以了,这样请求处理和后端应⽤是异步的.当后端处理完,到全局地⽅得到现场,产⽣响应,这个就实现了异步处理。
三、AIO
与NIO不同,当进⾏读写操作时,只须直接调⽤API的read或write⽅法即可。这两种⽅法均为异步的,对于读操作⽽⾔,当有流可读取时,操作系统会将可读的流传⼊read⽅法的缓冲区,并通知应⽤
程序;
对于写操作⽽⾔,当操作系统将write⽅法传递的流写⼊完毕时,操作系统主动通知应⽤程序。 即可以理解为,read/write⽅法都是异步的,完成后会主动调⽤回调函数。
在JDK1.7中,这部分内容被称作NIO.2,主要在Java.nio.channels包下增加了下⾯四个异步通道:
AsynchronousSocketChannel
AsynchronousServerSocketChannel
AsynchronousFileChannel
AsynchronousDatagramChannel
其中的read/write⽅法,会返回⼀个带回调函数的对象,当执⾏完读取/写⼊操作后,直接调⽤回调函数。
BIO是⼀个连接⼀个线程。
NIO是⼀个请求⼀个线程。
AIO是⼀个有效请求⼀个线程。
先来个例⼦理解⼀下概念,以银⾏取款为例:
同步 : ⾃⼰亲⾃出⻢持银⾏卡到银⾏取钱(使⽤同步IO时,Java⾃⼰处理IO读写);
异步 : 委托⼀⼩弟拿银⾏卡到银⾏取钱,然后给你(使⽤异步IO时,Java将IO读写委托给OS处理,需要将数据缓冲区地址和⼤⼩传给OS(银⾏卡和密码),OS需要⽀持异步IO操作API);
阻塞 : ATM排队取款,你只能等待(使⽤阻塞IO时,Java调⽤会⼀直阻塞
异步阻塞IO:此种⽅式下是指应⽤发起⼀个IO操作以后,不等待内核IO操作的完成,等内核完成IO操作以后会通知应⽤程序,这其实就是同步和异步最关键的区别,同步必须等待或者主动的去询问IO是否完成,那么为什么说是阻塞的呢?因为此时是通过select系统调⽤来完成的,⽽select函数本⾝的实现⽅式是阻塞的,⽽采⽤select函数有个好处就是它可以同时监听多个⽂件句柄,从⽽提⾼系统的并发性!
异步⾮阻塞IO:在此种模式下,⽤⼾进程只需要发起⼀个IO操作然后⽴即返回,等IO操作真正的完成以后,应⽤程序会得到IO操作完成的通知,此时⽤⼾进程只需要对数据进⾏处理就好了,不需要进⾏
实际的IO读写操作,
因为真正的IO读取或者写⼊操作已经由内核完成了。⽬前Java中还没有⽀持此种IO模型
1.44、nio框架:dubbo的实现原理;
client⼀个线程调⽤远程接⼝,⽣成⼀个唯⼀的ID(⽐如⼀段随机字符串,UUID等),Dubbo是使⽤AtomicLong从0开始累计数字的将打包的⽅法调⽤信息(如调⽤的接⼝名称,⽅法名称,参数值列表等),和处理结果的回调对象callback,全部封装在⼀起,组成⼀个对象object向专⻔存放调⽤信息的全局ConcurrentHashMap⾥⾯put(ID, object)将ID和打包的⽅法调⽤信息封装成⼀对象connRequest,使⽤IoSession.write(connRequest)异步发送出去
当前线程再使⽤callback的get()⽅法试图获取远程返回的结果,在get()内部,则使⽤synchronize获取回调对象callback的锁, 再先检测是否已经获取到结果,如果没有,然后调⽤callback的wait()⽅法,释放callback上的锁,让当前线程处于等待状态。服务端接收到请求并处理后,将结果(此结果中包含了前⾯的ID,即回传)发送给客⼾端,客⼾端socket连接上专⻔监听消息的线程收到消息,分析结果,取到ID,再从前⾯的ConcurrentHashMap⾥⾯get(ID),从⽽找到callback,将⽅法调⽤结果设置到callback对象⾥。
监听线程接着使⽤synchronized获取回调对象callback的锁(因为前⾯调⽤过wait(),那个线程已释放callback的锁了),再notifyAll(),唤醒前⾯处于等待态的线程继续执⾏(callback的get()⽅法继续执⾏就能拿到调⽤结果了),⾄此,整个过程结束。当前线程怎么让它“暂停”,等结果回来后,再向后执⾏?
答:先⽣成⼀个对象obj,在⼀个全局map⾥put(ID,obj)存放起来,再⽤synchronized获取obj
锁,再调⽤obj.wait()让当前线程处于等待状态,然后另⼀消息监听线程等到服 务端结果来了后,再map.get(ID)找到obj,再⽤synchronized获取obj锁,再调⽤obj.notifyAll()唤醒前⾯处于等待状态的线程。
正如前⾯所说,Socket通信是⼀个全双⼯的⽅式,如果有多个线程同时进⾏远程⽅法调⽤,这时建⽴在client server之间的socket连接上会有很多双⽅发送的消息传递,前后顺序也可能是乱七⼋糟的,server处理完结果后,将结果消息发送给client,client收到很多消息,怎么知道哪个消息结果是原先哪个线程调⽤的?
答:使⽤⼀个ID,让其唯⼀,然后传递给服务端,再服务端⼜回传回来,这样就知道结果是原先哪
个线程的了。