JavaSE与网络面试题
大佬的: https://github.com/Snailclimb/JavaGuide
https://osjobs.net/topk/all/
自增自减
要点:
-
赋值 = ,最后计算
-
= 右边的从左到右加载值,一次压入操作数栈
-
实际先算哪个看运算符的优先级
-
自增、自减操作都是直接修改变量的值,不经过操作数栈
-
做后赋值之前,临时结果也是先储存在数栈中
-
局部变量表 存储局部变量 和 操作数栈 进行运算
-
++ 在前先赋值给局部变量,然后在压入操作数栈
-
++ 在后先压入操作数栈,在赋值给局部变量
答案:
i = 4
j = 1
k = 11
单例模式
1.饿汉式
-
直接创建对象,不存在线程安全问题
-
直接实例化饿汉式(简洁直观)
-
枚举式(最简洁)
-
静态代码块饿汉式(适合复杂实例化)
静态代码块饿汉式(适合复杂实例化)
一般用于读取配置文件 的信息
public class Singleton3 {
public static final Singleton3 INSTANCE;
private String info;
static{
try {
Properties pro = new Properties(); pro.load(Singleton3.class.getClassLoader().getResourceAsStream("single.properties"));
INSTANCE = new Singleton3(pro.getProperty("info"));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private Singleton3(String info){
this.info = info;
}
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
@Override
public String toString() {
return "Singleton3 [info=" + info + "]";
}
}
2.懒汉式
-
线程不安全
线程不安全(适用于单线程)
只有调用方法才创建对象public class Singleton4 { private static Singleton4 instance; private Singleton4() { } public static Singleton4 getInstance() { if (instance == null) { try { TimeUnit.MICROSECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } instance = new Singleton4(); } return instance; } }
-
线程安全
线程安全(适用于多线程)
利用synchronized 锁住class对象public class Singleton5 { private volatile static Singleton5 instance; private Singleton5() { } public static Singleton5 getInstance() { if (instance == null) { synchronized (Singleton5.class) { if (instance == null) { try { TimeUnit.MICROSECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } instance = new Singleton5(); } } } return instance; } }
-
静态内部类形式
静态内部类形式(适用于多线程)
利用静态内部类实现单例懒汉式在内部类被加载和初始化时 才创建INSTANCE对象
静=态内部类不会随着外部类的加载和初始化而初始化,他是要单独去加载和初始化的
因为是在内部类加载和初始化,创建的 因此是线程安全的
public class Singleton6 {
private Singleton6() {
}
private static class Inner {
private static final Singleton6 INSTANCE = new Singleton6();
}
public static Singleton6 getInstance() {
return Inner.INSTANCE;
}
}
类初始化和实例初始化
父类的静态变量->父类静态代码块->子类静态变量->子类静态代码块->父类的非静变量->父类非静态构造块->父类构造器->子类的非静变量->子类非静态构造块->子类构造器
其中静态代码块和静态变量看先后顺序
非静态变量和非静态代码块看先后顺序
5 1 10 6 9 3 2 9 8 7
9 3 2 9 8 7
答案:
(5)(1)(10)(6)(9)(3)(2)(9)(8)(7)
(9)(3)(2)(9)(8)(7)
因为实例初始化 this对象是子类 子类重写了test方法,因此会调用子类的test方法
类初始化
-
一个类要创建实例需要先加载并初始化该类main方法所在的类需要先加载和初始化
-
一个子类要初始化需要先初始化父类
-
一个类初始化就是执行()方法(每个类都有 字节码文件中,只有一个)
()方法由静态类变量显示赋值代码和静态代码块组成
类变量显示赋值代码和静态代码块代码从上到下顺序执行
()方法只执行一次
实例初始化
1.实例初始化就是执行()方法(在字节码文件中)
2.()方法可能重载有多个,有几个构造器就有几个方法
3.()方法由非静态实例变量显示赋值代码和非静态代码块、对应构造器代码组成
4.非静态实例变量显示赋值代码和非静态代码块代码从上到下顺序执行,而对应构造器的代码最后执行
5.每次创建实例对象,调用对应构造器,执行的就是对应的方法
6.方法的首行是super()或super(实参列表),即对应父类的方法
重写和重载
方法的重写Override
在Java程序中,类的继承关系可以产生一个子类,子类继承父类,它(子类)具备了父类所有的特征,继承了父类所有的方法和变量。
子类可以定义新的特征,当子类需要修改父类的一些方法进行扩展,增大功能,程序设计者常常把这样的一种操作方法称为重写,也叫称为覆写或覆盖。
重写的优点:
-
重写体现了Java优越性,重写是建立在继承关系上,它使语言结构更加丰富。在Java中的继承中,子类既可以隐藏和访问父类的方法,也可以覆盖继承父类的方法。
-
重写的好处在于子类可以根据需要,定义特定于自己的行为。也就是说子类能够根据需要实现父类的方法。
-
在面向对象原则里,重写意味着可以重写任何现有方法。
子类将父类中的方法重写了,调用的时候肯定是调用被重写过的方法,那么如果现在一定要调用父类中的方法该怎么办呢?
此时,通过使用super关键就可以实现这个功能,super关键字可以从子类访问父类中的内容,如果要访问被重写过的方法,使用“super.方法名(参数列表)”的形式调用。
如果要使用super关键字不一定非要在方法重写之后使用,也可以明确地表示某个方法是从父类中继承而来的。使用super只是更加明确的说,要从父类中查找,就不在子类查找了。
注意:super 和 this 不能同时使用
哪些方法不可以被重写
- final方法
- 静态方法
- private等子类中不可见方法
对象的多态性
- 子类如果重写了父类的方法,通过子类对象调用的一定是子类重写过的代码
- 非静态方法默认的调用对象是this
- this对象在构造器或者说方法中就是正在创建的对象
重写的规则
那看来解决问题的关键还必须从invokevirtual指令本身入手,要弄清 楚它是如何确定调用方法版本、如何实现多态查找来着手分析才行。根据《Java虚拟机规范》,invokevirtual指令的运行时解析过程[4]大致分为以下几步:
1)找到操作数栈顶的第一个元素所指向的对象的实际类型,记作C。
2)如果在类型C中找到与常量中的描述符和简单名称都相符的方法,则进行访问权限校验,如果 通过则返回这个方法的直接引用,查找过程结束;不通过则返回java.lang.IllegalAccessError异常。
3)否则,按照继承关系从下往上依次对C的各个父类进行第二步的搜索和验证过程。
4)如果始终没有找到合适的方法,则抛出java.lang.AbstractMethodError异常。 正是因为invokevirtual指令执行的第一步就是在运行期确定接收者的实际类型,所以两次调用中的
invokevirtual指令并不是把常量池中方法的符号引用解析到直接引用上就结束了,还会根据方法接收者
的实际类型来选择方法版本,这个过程就是Java语言中方法重写的本质。
把这种在运行期根据实 际类型确定方法执行版本的分派过程称为动态分派。
既然这种多态性的根源在于虚方法调用指令invokevirtual的执行逻辑,那自然我们得出的结论就==只 会对方法有效,对字段是无效的,因为字段不使用这条指令。事实上,在Java里面只有虚方法存在, 字段永远不可能是虚的,换句话说,字段永远不参与多态,哪个类的方法访问某个名字的字段时,该 名字指的就是这个类能看到的那个字段。==当子类声明了与父类同名的字段时,虽然在子类的内存中两 个字段都会存在,但是子类的字段会遮蔽父类的同名字段。
在重写方法时,需要遵循以下的规则:(两同 两小 一大)
(一) 父类方法的参数列表必须完全与被子类重写的方法的参数列表相同,否则不能称其为重写而是重载。
(二) 父类的返回类型必须与被子类重写的方法返回类型相同,否则不能称其为重写而是重载。
(三) Java中规定,被子类重写的方法不能拥有比父类方法更加严格的访问权限。编写过Java程序的人就知道,父类中的方法并不是在任何情况下都可以重写的,当父类中方法的访问权限修饰符为private时,该方法只能被自己的类访问,不能被外部的类访问,在子类是不能被重写的。如果定义父类的方法为public,在子类定义为private,程序运行时就会报错。
(四) 由于父类的访问权限修饰符的限制一定要大于被子类重写方法的访问权限修饰符,而private权限最小。所以如果某一个方法在父类中的访问权限是private,那么就不能在子类中对其进行重写。如果重新定义,也只是定义了一个新的方法,不会达到重写的效果。
(五) 在继承过程中如果父类当中的方法抛出异常,那么在子类中重写父类的该方法时,也要抛出异常,而且抛出的异常不能多于父类中抛出的异常(可以等于父类中抛出的异常)。换句话说,重写方法一定不能抛出新的检查异常,或者比被重写方法声明更加宽泛的检查型异常。例如,父类的一个方法申明了一个检查异常IOException,在重写这个方法时就不能抛出Exception,只能抛出IOException的子类异常,可以抛出非检查异常。同样的道理,如果子类中创建了一个成员变量,而该变量和父类中的一个变量名称相同,称作变量重写或属性覆盖。但是此概念一般很少有人去研究它,因为意义不大。
==============================================================================================================================================================================================
方法重载(Overloading)
方法重载是让类以统一的方式处理不同类型数据的一种手段。调用方法时通过传递给它们的不同个数和类型的参数来决定具体使用哪个方法,这就是多态性。
所谓方法重载是指在同一个类中,多个方法的方法名相同,但是参数列表不同。参数列表不同指的是参数个数、参数类型或者参数的顺序不同。
构造方法也可以重载!!!
方法重载注意事项:
当Java调用一个重载方法是,参数与调用参数匹配的方法被执行。在使用重载要注意以下的几点:
1.在使用重载时只能通过不同的参数列表,必须具有不同的参数列表。
2.不能通过访问权限、返回类型、抛出的异常进行重载。
3.方法的异常类型和数目不会对重载造成影响。
4.可以有不同的返回类型,只要参数列表不同就可以了。
5.可以有不同的访问修饰符。
6.可以抛出不同的异常。
7.每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。
重载的规则:
被重载的方法必须改变参数列表;
被重载的方法可以改变返回类型;
被重载的方法可以改变访问修饰符;
被重载的方法可以声明新的或更广的检查异常;
方法能够在同一个类中或者在一个子类中被重载。
访问权限修饰符的区别与范围
所有依赖静态类型来决定方法执行版本的分派动作,都称为静态分派。静态分派的最典型应用表 现就是方法重载。静态分派发生在编译阶段,因此****确定静态分派的动作实际上不是由虚拟机来执行**** *的*,这点也是为何一些资料选择把它归入“解析”而不是“分派”的原因。
需要注意==Javac编译器虽然能确定出方法的重载版本,但在很多情况下这个重载版本并不是“唯 一”的,往往只能确定一个“相对更合适的”版本。==这种模糊的结论在由0和1构成的计算机世界中算是个 比较稀罕的事件,产生这种模糊结论的主要原因是字面量天生的模糊性,它不需要定义,所以字面量 就没有显式的静态类型,它的静态类型只能通过语言、语法的规则去理解和推断。
可见变长参数的重载优先级是最低的
编译期间选择静态分派目标的过程,这个过程也是Java语言实现方法重载的本 质。
总结:
重写与重载之间的区别:
方法重载:
1、同一个类中
2、方法名相同,参数列表不同(参数顺序、个数、类型)
3、方法返回值、访问修饰符任意
4、与方法的参数名无关
方法重写:
1、有继承关系的子类中!!!
2、方法名相同,参数列表相同(参数顺序、个数、类型),方法返回值相同
3、访问修饰符,访问范围需要大于等于父类的访问范围
4、与方法的参数名无关
方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。
发生在同一个类中,方法名相同参数列表不同(参数类型不同、个数不同、顺序不同),与方法返回值和访问修饰符无关,即重载的方法不能根据返回类型进行区分
发生在父子类中,方法名、参数列表必须相同,返回值小于等于父类,抛出的异常小于等于父类,访问修饰符大于等于父类(里氏代换原则);如果父类方法访问修饰符为private则子类中就不是重写
两同 方法名、参数列表必须相同,
两小 返回值小于等于父类,抛出的异常小于等于父类
一大 访问修饰符大于等于父类(里氏替换原则)
多态:多态是同一个行为具有多个不同表现形式或形态的能力。
单分派与多分派
方法的接收者与方法的参数统称为方法的宗量,单分派是根据一个宗量对
目标方法进行选择,多分派则是根据多于一个宗量对目标方法进行选择。
Java语言是一门静态多分派、动态单分派的语言。
方法参数的传递
方法的参数传递机制:
形参是基本数据类型
传递数据值
实参是引用数据类型
传递地址值
特殊的类型:String、包装类等对象不可变性
堆和栈的概念和区别:
在说堆和栈之前,我们先说一下JVM(虚拟机)内存的划分:
高亮的部分是线程所共享的,反之不共享
我们重点来说一下堆和栈:
栈内存:
栈内存首先是一片内存区域,存储的都是局部变量,凡是定义在方法中的都是局部变量(方法外的是全局变量),for循环内部定义的也是局部变量,是先加载函数才能进行局部变量的定义,所以方法先进栈,然后再定义变量,变量有自己的作用域,一旦离开作用域,变量就会被释放。栈内存的更新速度很快,因为局部变量的生命周期都很短。
堆内存:
存储的是数组和对象(其实数组就是对象),凡是new建立的都是在堆中,堆中存放的都是实体(对象),实体用于封装数据,而且是封装多个(实体的多个属性),如果一个数据消失,这个实体也没有消失,还可以用,所以堆是不会随时释放的,但是栈不一样,栈里存放的都是单个变量,变量被释放了,那就没有了。堆里的实体虽然不会被释放,但是会被当成垃圾,Java有垃圾回收机制不定时的收取。
所以堆与栈的区别很明显:
-
栈内存存储的是局部变量而堆内存存储的是实体;
-
栈内存的更新速度要快于堆内存,因为局部变量的生命周期很短;
-
栈内存存放的变量生命周期一旦结束就会被释放,而堆内存存放的实体会被垃圾回收机制不定时的回收。
答案:
i = 1
str = hello
num = 200
arr = [2, 2, 3, 4, 5]
my.a = 11
当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递?
是值传递。Java 语言的方法调用只支持参数的值传递。当一个对象实例作为一个参数被传递到方法中时,==参数的值就是对该对象的引用。==对象的属性可以在被调用过程中被改变,但对对象引用的改变是不会影响到调用者的
为什么Java中只有值传递?
首先回顾一下在程序设计语言中有关将参数传递给方法(或函数)的一些专业术语。
按值调用(call by value)表示方法接收的是调用者提供的值,
而按引用调用(call by reference)表示方法接收的是调用者提供的变量地址。
一个方法可以修改传递引用所对应的变量值,而不能修改传递值调用所对应的变量值。 它用来描述各种程序设计语言(不只是Java)中方法参数传递方式。
Java程序设计语言总是采用按值调用。
也就是说,方法得到的是所有参数值的一个拷贝,
也就是说,方法不能修改传递给它的任何参数变量的内容。
值传递(pass by value)是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
引用传递(pass by reference)是指在调用函数时将实际参数的地址直接传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。
所以,值传递和引用传递的区别并不是传递的内容。而是实参到底有没有被复制一份给形参。
所以说,Java中其实还是值传递的,只不过对于对象参数,值的内容是对象的引用。
无论是值传递还是引用传递,其实都是一种求值策略(Evaluation strategy)。在求值策略中,还有一种叫做按共享传递(call by sharing)。其实Java中的参数传递严格意义上说应该是按共享传递。
按共享传递,是指在调用函数时,传递给函数的是实参的地址的拷贝(如果实参在栈中,则直接拷贝该值)。在函数内部对参数进行操作时,需要先拷贝的地址寻找到具体的值,再进行操作。如果该值在栈中,那么因为是直接拷贝的值,所以函数内部对参数进行操作不会对外部变量产生影响。如果原来拷贝的是原值在堆中的地址,那么需要先根据该地址找到堆中对应的位置,再进行操作。因为传递的是地址的拷贝所以函数内对值的操作对外部变量是可见的。
简单点说,Java中的传递,是值传递,而这个值,实际上是对象的引用。
递归和迭代
//实现f(n):求n步台阶,一共有几种走法
public int f(int n) {
if (n < 1) {
throw new RuntimeException(n + "不能小于1");
}
if (n == 1 || n == 2) {
return n;
}
return f(n - 2) + f(n - 1);
}
public int loop(int n) {
if (n < 1) {
throw new RuntimeException(n + "不能小于1");
}
if (n == 1 || n == 2) {
return n;
}
int one = 2;//初始化为走到第二级台阶的走法
int two = 1;//初始化为走到第一级台阶的走法
int sum = 0;
for (int i = 3; i <= n; i++) {
//最后跨2步 + 最后跨一步的走法
sum = two + one;
two = one;
one = sum;
}
return sum;
}
小结:
方法调用自身称为递归,利用变量的原值推出新值称为迭代。
递归
优点:大问题转化为小问题,可以减少代码量,同时代码精简,可读性好;
缺点:递归调用浪费了空间,而且递归太深容易造成堆栈的溢出。
迭代
优点:代码运行效率好,因为时间只因循环次数增加而增加,而且没有额外的空间开销;
缺点:代码不如递归简洁,可读性好
局部变量与成员变量的区别
答案:
2,1,5
1,1,5
就近原则
变量的就近原则指尽可能在靠近第一次使用变量的位置声明和定义该变量。
变量的分类
成员变量:类变量、实例变量
局部变量
非静态代码块的执行:
每次创建实例对象都会执行方法的调用规则:调用一次执行一次
当没有明确标识变量属于某种变量时,通常使用就近原则。
局部变量与成员变量的区别:
-
声明的位置
局部变量:方法体{}中,形参,代码块{}中
成员变量:类中方法外
类变量:有static修饰
实例变量:没有static修饰 -
修饰符
局部变量:final
成员变量:public、protected、private、final、static、volatile、transient -
值存储的位置
局部变量:栈
实例变量:堆
类变量:方法区 -
作用域:
局部变量:从声明处开始,到所属的}结束
实例变量:在当前类中“this.”(有时this.可以缺省),在其他类中“对象名.”访问
类变量:在当前类中“类名.”(有时类名.可以省略),在其他类中“类名.”或“对象名.”访问 -
生命周期
局部变量:每一个线程,每一次调用执行都是新的生命周期,每一个线程也是独立的
实例变量:随着对象的创建而初始化,随着对象的被回收而消亡,每一个对象的实例变量是独立的
类变量:随着类的初始化而初始化,随着类的卸载而消亡,该类的所有对象的类变量是共享的
重名问题
当局部变量与xx变量重名时,如何区分:
局部变量与实例变量重名
在实例变量前面加“this.”
局部变量与类变量重名
在类变量前面加“类名.”
注意如果 缺省this和类名 则会采用最近原则策略
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OGuBTUFR-1673796690799)(https://gitee.com/Code_Farming_Liu/image/raw/master/img/20201018185930.png)]
Java 位运算
带符号的移位
即 补码进行操作 之后将补码还原为原码 在求出真值
JDK1.5之后的三大版本
-
Java SE
Java SE(J2SE,Java 2 Platform Standard Edition,标准版) Java SE 以前称为 J2SE。它允许开发和部署在桌面、服务器、嵌入式环境和实时环境中使用的 Java 应用程序。Java SE 包含了支持 Java Web 服务开发的类,并为Java EE和Java ME提供基础。
-
Java EE
Java EE 以前称为 J2EE。企业版本帮助开发和部署可移植、健壮、可伸缩且安全的服务器端Java 应用程序。Java EE 是在 Java SE 的基础上构建的,它提供 Web 服务、组件模型、管理和通信 API,可以用来实现企业级的面向服务体系结构(service-oriented architecture,SOA)和 Web2.0应用程序。2018年2月,Eclipse 宣布正式将 JavaEE 更名为 JakartaEE
-
Java ME
Java ME 以前称为 J2ME。Java ME 为在移动设备和嵌入式设备(比如手机、PDA、电视机顶盒和打印机)上运行的应用程序提供一个健壮且灵活的环境。Java ME 包括灵活的用户界面、健壮的安全模型、许多内置的网络协议以及对可以动态下载的连网和离线应用程序的丰富支持。基于 Java ME 规范的应用程序只需编写一次,就可以用于许多设备,而且可以利用每个设备的本机功能。
JVM、JRE和JDK的关系
https://blog.csdn.net/weixin_30750335/article/details/98007257
-
JVM
Java Virtual Machine是Java虚拟机,Java程序需要运行在虚拟机上,不同的平台有自己的虚拟机,因此Java语言可以实现跨平台。
-
JRE==(如果想要运行一个开发好的Java程序,计算机中只需要安装JRE即可。)==
Java Runtime Environment包括Java虚拟机和Java程序所需的核心类库等。核心类库主要是java.lang包:包含了运行Java程序必不可少的系统类,如基本数据类型、基本数学函数、字符串处理、线程、异常处理类等,系统缺省加载这个包
-
JDK
Java Development Kit是提供给Java开发人员使用的,其中包含了Java的开发工具,也包括了JRE。所以安装了JDK,就无需再单独安装JRE了。其中的开发工具:编译工具(javac.exe),打包工具(jar.exe)等
-
什么是跨平台性?原理是什么?
所谓的跨平台性,就是java语言编写的程序,一次编译后,可以在多个系统的平台运行
实现的原理:Java程序是通过java虚拟机在系统平台上运行的,只要该系统可以安装相应的java虚拟机,该系统就可以运行java程序。
一次编译 到处运行
Java 语言的特点
-
简单易学
-
面向对象
-
封装
-
继承
-
多态
成员变量 静态方法 编译看左 运行看左 其他 编译看左边 运行看右边
-
抽象
-
-
平台无关性
平台是由操作系统和处理器所构成每个平台都会形成自己独特的机器指令与平台无关是指软件的运行不因操作系统、处理器的变化而无法运行或出现运行错误。
-
支持网络编程
-
支持多线程
-
稳定性
-
安全性
什么是字节码?采用字节码的最大好处是什么?
字节码
Java源代码经过虚拟机编译器编译后产生的文件(即扩展为.class的文件),它不面向任何特定的处理器,只面向虚拟机。
好处
Java中引入了虚拟机的概念,即在机器和编译程序之间加入了一层抽象的虚拟机器。这台虚拟的机器在任何平台上都提供给编译程序一个的共同的接口。编译程序只需要面向虚拟机,生成虚拟机能够理解的代码,然后由解释器来将虚拟机代码转换为特定系统的机器码执行。在Java中,这种供虚拟机理解的代码叫做字节码(即扩展为.class的文件),它不面向任何特定的处理器,只面向虚拟机。每一种平台的解释器是不同的,但是实现的虚拟机是相同的。Java源程序经过编译器编译后变成字节码,字节码由虚拟机解释执行,虚拟机将每一条要执行的字节码送给解释器,解释器将其翻译成特定机器上的机器码,然后在特定的机器上运行,这就是上面提到的Java的特点的编译与解释并存的解释。
Java源代码---->编译器---->jvm可执行的Java字节码(即虚拟指令)---->jvm---->jvm中解释器----->机器可执行的二进制机器码---->程序运行。
Java和 c++的区别
- 都是面向对象的语言,都支持封装、继承和多态
- Java不提供指针来直接访问内存,程序内存更加安全
- Java的类是单继承的,C++支持多重继承;虽然Java的类不可以多继承,但是接口可以多继承。
- Java有自动内存管理机制,不需要程序员手动释放无用内存
Oracle JDK 和 OpenJDK 的对比
- OracleJDK版本将每三年发布一次,而OpenJDK版本每三个月发布一次;
- OpenJDK 是一个参考模型并且是完全开源的,而Oracle JDK是OpenJDK的一个实现,并不是完全开源的;
- Oracle JDK 比 OpenJDK 更稳定。OpenJDK和Oracle JDK的代码几乎相同,但Oracle JDK有更多的类和一些错误修复。因此,如果您想开发企业/商业软件,我建议您选择Oracle JDK,因为它经过了彻底的测试和稳定。某些情况下,有些人提到在使用OpenJDK 可能会遇到了许多应用程序崩溃的问题,但是,只需切换到Oracle JDK就可以解决问题;
- 在响应性和JVM性能方面,Oracle JDK与OpenJDK相比提供了更好的性能;
- Oracle JDK不会为即将发布的版本提供长期支持,用户每次都必须通过更新到最新版本获得支持来获取最新版本;
- Oracle JDK根据二进制代码许可协议获得许可,而OpenJDK根据GPL v2许可获得许可。
Java数据类型
char 2字节 范围 0~65535 java中的char数据类型一定是无符号的
Java程序开发步骤
- 编写源文件
- 编译源文件
- 运行程序
Java的风格
- Allmans(独行)(代码量较小)
- Kernighan(行尾)(代码量较大)
标识符和关键字
标识符:
- 标识符 由字母 数字 下划线 美元符号组成 长度不受限制
- 数字不能开头
- 标识符不能是关键字
- 标识符不能是 true false null true false null 不是关键字
关键字
具有特殊意思的单词
switch 是否能作用在 byte 上,是否能作用在 long 上,是否能作用在 String 上
- 在 Java 5 以前,switch(expr)中,expr 只能是 byte、short、char、int。
- 从 Java5 开始,Java 中引入了枚举类型,expr 也可以是 enum 类型,
- 从 Java 7 开始,expr 还可以是字符串(String),
- 但是长整型(long)在目前所有的版本中都是不可以的。
Math.round(11.5) 等于多少?Math.round(-11.5)等于多少
Math.round(11.5)的返回值是 12,
Math.round(-11.5)的返回值是-11。
四舍五入的原理是在参数上加 0.5 然后进行下取整。
Java语言采用何种编码方案?有何特点?
Java语言采用Unicode编码标准,Unicode(标准码),它为每个字符制订了一个唯一的数值,因此在任何的语言,平台,程序都可以放心的使用。
最多可以识别65536个字符 字符集的前128个字符正好是ASCII 编码
访问修饰符
定义:
Java中,可以使用访问修饰符来保护对类、变量、方法和构造方法的访问。Java 支持 4 种不同的访问权限。
分类
-
private
在同一类内可见。使用对象:变量、方法。 注意:不能修饰类(外部类)
-
default
(即缺省(友好修饰符),什么也不写,不使用任何关键字): 在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。
-
protected
受保护的修饰符 对同一包内的类和所有子类可见。使用对象:变量、方法。 注意:不能修饰类(外部类)。
-
public
public : 对所有类可见。使用对象:类、接口、变量、方法
为什么String是不可变得
1)字符串池(String pool)的需求 在Java中,当初始化一个字符串变量时,如果字符串已经存在,就不会创建一个新的字符串变量,而是返回存在字符串的引用。 例如: String s1=“abcd”; String s2=“abcd”; 这两行代码在堆中只会创建一个字符串对象。如果字符串是可变的,改变另一个字符串变量,就会使另一个字符串变量指向错误的值。
2)缓存字符串hashcode码的需要 字符串的hashcode是经常被使用的,字符串的不变性确保了hashcode的值一直是一样的,在需要hashcode时,就不需要每次都计算,这样会很高效。
3)出于安全性考虑 字符串经常作为网络连接、数据库连接等参数,不可变就可以保证连接的安全性。
final finally finalize区别
final
-
被final修饰的类不可以被继承
https://blog.csdn.net/Java_I_ove/article/details/76946598?utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control
-
被final修饰的方法不可以被重写
-
被final修饰的变量不可以被改变,被final修饰不可变的是变量的引用,而不是引用指向的内容,引用指向的内容是可以改变的
-
final可以修饰类、变量、方法,修饰类表示该类不能被继承、修饰方法表示该方法不能被重写、修饰变量表
示该变量是一个常量不能被重新赋值。 -
https://blog.csdn.net/qq1028951741/article/details/53418852
finally
finally一般作用在try-catch代码块中,在处理异常的时候,通常我们将一定要执行的代码方法finally代码块
中,表示不管是否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码。
finalize
finalize是一个方法,属于Object类的一个方法,而Object类是所有类的父类,该方法一般由垃圾回收器来调
用,当我们调用System.gc() 方法的时候,由垃圾回收器调用finalize(),回收垃圾,一个对象是否可回收的
最后判断==(这个对象可以趁这个时机挣脱死亡的命运)。==
是Object的一个方法,设计的目的是保证对象在被垃圾收集前完成特定的资源回收。不推荐使用,使用不当会影响系统性能,甚至导致死锁。Java平台目前在逐步使用java.lang.ref.Cleaner来替换原有的finalize
自我救赎
对象覆写了finalize()方法(这样在被判死后才会调用此方法,才有机会做最后的救赎);
在finalize()方法中重新引用到"GC Roots"链上(如把当前对象的引用this赋值给某对象的类变量/成员变量,重新建立可达的引用)
this与super的区别
super:
它引用当前对象的直接父类中的成员(用来访问直接父类中被隐藏的父类中成员数据或函数,基类与派生类中有相同成员定义时如:super.变量名 super.成员函数据名(实参)
this:
它代表当前对象名(在程序中易产生二义性之处,应使用this来指明当前对象;如果函数的形参与类中的成员数据同名,这时需用this来指明成员变量名)
super()和this()类似,区别是,super()在子类中调用父类的构造方法,this()在本类内调用本类的其它构造方法。
super()和this()均需放在构造方法内第一行。
尽管可以用this调用一个构造器,但却不能调用两个。
this和super不能同时出现在一个构造函数里面,因为this必然会调用其它的构造函数,其它的构造函数必然也会有super语句的存在,所以在同一个构造函数里面有相同的语句,就失去了语句的意义,编译器也不会通过。
this()和super()都指的是对象,所以,均不可以在static环境中使用。包括:static变量,static方法,static语句块。
从本质上讲,this是一个指向本对象的指针, 然而super是一个Java关键字。
面向对象和面向过程的区别
面向过程:
优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发,性能是最重要的因素。
缺点:没有面向对象易维护、易复用、易扩展
面向对象:
优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护
缺点:性能比面向过程低
面向过程是具体化的,流程化的,解决一个问题,你需要一步一步的分析,一步一步的实现。
面向对象是模型化的,你只需抽象出一个类,这是一个封闭的盒子,在这里你拥有数据也拥有解决问题的方法。需要什么功能直接使用就可以了,不必去一步一步的实现,至于这个功能是如何实现的,管我们什么事?我们会用就可以了。
面向对象的底层其实还是面向过程,把面向过程抽象成类,然后封装,方便我们使用的就是面向对象了。
抽象类和接口的对比
https://www.cnblogs.com/dolphin0520/p/3811437.html
抽象类是用来捕捉子类的通用特性的。接口是抽象方法的集合。
从设计层面来说,抽象类是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。
相同点
- 接口和抽象类都不能实例化
- 都位于继承的顶端,用于被其他实现或继承
- 都包含抽象方法,其子类都必须覆写这些抽象方法
不同点
备注:Java8中接口中引入默认方法和静态方法,以此来减少抽象类和接口之间的差异。
现在,我们可以为接口提供默认实现的方法了,并且不用强制子类来实现它。
接口和抽象类各有优缺点,在接口和抽象类的选择上,必须遵守这样一个原则:
行为模型应该总是通过接口而不是抽象类定义,所以通常是优先选用接口,尽量少用抽象类。
选择抽象类的时候通常是如下情况:需要定义子类的行为,又要为子类提供通用的功能。
局部内部类和匿名内部类访问局部变量的时候,为什么变量必须要加上final?
是因为生命周期不一致, 局部变量直接存储在栈中,当方法执行结束后,非final的局部变量就被销毁。而局部内部类对局部变量的引用依然存在,如果局部内部类要调用局部变量时,就会出错。加了final,可以确保局部内部类使用的变量与外层的局部变量区分开,解决了这个问题。
== 与 equals的区别
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-psZc56Px-1673796690802)(…/…/…/typora/image/image-20210623233710601.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4PmWO95C-1673796690802)(…/…/…/typora/image/image-20210623233724399.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5eUmqsVx-1673796690803)(…/…/…/typora/image/image-20210623233744409.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ElnZNnRX-1673796690804)(…/…/…/typora/image/image-20210623233824170.png)]
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A5cOuJO7-1673796690805)(…/…/…/typora/image/image-20210623234004865.png)]
总结:所以说对于==,当数据类型是8大基础类型时,比较的是(栈中)数值是否相等,当数据类型是引用类型时,比较的是对象的引用地址是否相等。这是通过jvm来自动判断的。
总结:因为我们在对字符串比较的时候,往往关注的就是值一样不一样,所以java这样重写也是 很有必要的,所以我们一直推荐判断字符串相等用equals方法,而不是用==来判断。equals方法具体产生什么样的效果,完全看子类是怎么重写的,比如Date类,重写了equals方法,只要两个时间的getTime()时间戳一样,那么就相等,这也是符合我们的认知的。
hashCode 与 equals (重要)
hashCode()介绍
hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个**哈希码的作用是确定该对象在哈希表中的索引位置。**hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode()函数。
散列表存储的是键值对(key-value),
它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)
为什么要有 hashCode?
当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashcode 值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 equals()方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。(摘自我的Java启蒙书《Head first java》第二版)。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。
hashCode()与equals()的相关规定
如果两个对象相等,则hashcode一定也是相同的
两个对象相等,对两个对象分别调用equals方法都返回true
两个对象有相同的hashcode值,它们也不一定是相等的
因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖
hashCode() 的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)
为什么重写equals需要重写hashcode方法?
为了保证当两个对象通过equals()方法比较相等时,那么他们的hashCode值也一定要保证相等。
比如hashset将两个equals相同的对象存进去,按道理来说,equals方法相同这两个对象可以看为同一个对象,不可能都存进去,但他们的hashCode不同,HashSet的add方法实质是将添加的元素作为键存进map集合中,map集合再根据hashCode来计算这个元素存在数组的位置,但他们hashCode不同,所以存的位置不同,所以可能都能存进去,这就出现问题了
IO流
InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。
按照流的流向分,可以分为输入流和输出流;
按照操作单元划分,可以划分为字节流和字符流;
按照流的角色划分为节点流和处理流。
BIO,NIO,AIO 有什么区别?
BIO:
简答
Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低。
详细
BIO (Blocking I/O): 同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。在活动连接数不是特别高(小于单机1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的 I/O 并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。
NIO:
简答
Non IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 Channel(通道)通讯,实现了多路复用。
详细
NIO (New I/O): NIO是一种同步非阻塞的I/O模型,在Java 1.4 中引入了NIO框架,对应 java.nio 包,提供了 Channel , Selector,Buffer等抽象。NIO中的N可以理解为Non-blocking,不单纯是New。
它支持面向缓冲的,基于通道的I/O操作方法。
NIO提供了与传统BIO模型中的 Socket 和 ServerSocket 相对应的 SocketChannel 和 ServerSocketChannel 两种不同的套接字通道实现,
两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;
非阻塞模式正好与之相反。
对于低负载、低并发的应用程序,可以使用同步阻塞I/O来提升开发速率和更好的维护性;
对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发
简答
Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO 的操作基于事件和回调机制。
详细
AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改进版 NIO 2,它是异步非阻塞的IO模型。异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。AIO 是异步IO的缩写,虽然 NIO 在网络操作中,提供了非阻塞的方法,但是 NIO 的 IO 行为还是同步的。对于 NIO 来说,我们的业务线程是在 IO 操作准备好时,得到通知,接着就由这个线程自行进行 IO 操作,IO操作本身是同步的。查阅网上相关资料,我发现就目前来说 AIO 的应用还不是很广泛,Netty 之前也尝试使用过 AIO,不过又放弃了。
反射
JAVA反射机制是在运行状态中,
-
对于任意一个类,都能够知道这个类的所有属性和方法;
-
对于任意一个对象,都能够调用它的任意一个方法和属性;
这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
反射也就是我们可以通过获取它的类,从而获取到类里面的成员变量、方法及构造方法,并可以使用他们做一些奇奇怪怪的事情。
Activity activity = new Activity();
Class<? extends Activity> activityClass = activity.getClass();
也可以这样
Class<? extends Activity> activityClass = MainActivity.class;
甚至还可以这样
try {
Class activityClass = Class.forName("com.example.mydemo.MainActivity");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
我们一旦获取到他的类之后就可以继续去搞事情了,例如我们去他的一个私有成员变量,并对它的成员变量进行赋值,对于方法及构造函数的获取就不11(感觉中文“一一”看起来像破折号,下同)举例了。
Field[] fields = activityClass.getDeclaredFields();
for (Field field : fields) {
if (field.getName().equals("name")) {
//对私有成员变量赋值必须先执行这个
field.setAccessible(true);
try {
//注意这个activity,赋值时第一个参数必须传入的是实例对象
//这个acticity就是上面第一个例子中的acticity
field.set(activity, "dog");
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
这样就简单的实现了一个反射的用法,我们通过反射,不仅可以获取到一个类的私有的东西,甚至还可以获取到他的父类的东西,同时,我们无论获取什么都是一下子全部获取了,例如上面这个getDeclaredFields(),就获取了这个类里面的全部成员变量,这也就人们常说的反射会影响性能的原因。
优点:
运行期类型的判断,动态加载类,提高代码灵活度。
缺点:
性能瓶颈:反射相当于一系列解释操作,通知JVM要做的事情,性能比直接的java代码要慢很多。
应用场景:
反射是框架设计的灵魂。
在我们平时的项目开发过程中,基本上很少会直接使用到反射机制,但这不能说明反射机制没有用,实际上有很多设计、开发都与反射机制有关,例如模块化的开发,通过反射去调用对应的字节码;动态代理设计模式也采用了反射机制,还有我们日常使用的 Spring/Hibernate 等框架也大量使用到了反射机制。
举例:①我们在使用JDBC连接数据库时使用Class.forName()通过反射加载数据库的驱动程序;②Spring框架也用到很多反射机制,最经典的就是xml的配置模式。Spring 通过 XML 配置模式装载 Bean 的过程:1) 将程序内所有 XML 或 Properties 配置文件加载入内存中; 2)Java类里面解析xml或properties里面的内容,得到对应实体类的字节码字符串以及相关的属性信息; 3)使用反射机制,根据这个字符串获得某个类的Class实例; 4)动态配置实例的属性
Java 反射到底慢在哪里?
https://www.zhihu.com/question/19826278
获取反射的3种方式:
- 通过new对象实现反射机制
- 通过路径实现反射机制
- 通过类名实现反射机制
Object类常用的方法
https://blog.51cto.com/12222886/2067876
TCP/IP协议和三次握手 4次挥手
网络协议:
在计算机网络要做到有条不紊地交换数据,就必须遵守一些事先约定好的规则,比如交换数据的格式、是否需要发送一个应答信息。这些规则被称为网络协议。
为进行网络中的数据交换而建立的规则、标椎约定称为网络协议
为什么对网络协议分层?
为了使不同体系结构的计算机网络都能互联,国际标准化组织 ISO 于1977年提出了一个试图使各种计算机在世界范围内互联成网的标准框架,即著名的开放系统互联基本参考模型 OSI/RM,简称为OSI。
OSI 的七层协议体系结构的概念清楚,理论也较完整,但它既复杂又不实用,TCP/IP 体系结构则不同,但它现在却得到了非常广泛的应用。TCP/IP 是一个四层体系结构,它包含应用层,运输层,网际层和网络接口层(用网际层这个名字是强调这一层是为了解决不同网络的互连问题),不过从实质上讲,TCP/IP 只有最上面的三层,因为最下面的网络接口层并没有什么具体内容,因此在学习计算机网络的原理时往往采用折中的办法,即综合 OSI 和 TCP/IP 的优点,采用一种只有五层协议的体系结构,这样既简洁又能将概念阐述清楚,有时为了方便,也可把最底下两层称为网络接口层。
优点:
- (各层独立)简化问题难度和复杂度。由于各层之间独立,我们可以分割大问题为小问题。
- (灵活性好)当其中一层的技术变化时,只要层间接口关系保持不变,其他层不受影响。
- 促进标准化工作。分开后,每层功能可以相对简单地被描述。
- 易于实现和维护。
缺点:
功能可能出现在多个层里重复出现,产生了额外开销。
TCP/IP是一个四层的体系结构
- 应用层
- 运输层
- 网际层
- 网络接口层
五层协议体系结构:
-
应用层
应用层( application-layer )的任务是通过应用进程间的交互来完成特定网络应用。
应用层协议定义的是应用进程(进程:主机中正在运行的程序)间的通信和交互的规则。
对于不同的网络应用需要不同的应用层协议。
在互联网中应用层协议很多,如域名系统 DNS,支持万维网应用的 HTTP 协议,支持电子邮件的 SMTP 协议等等。
-
运输层
运输层(transport layer)的主要任务就是负责向两台主机进程之间的通信提供通用的数据传输服务。
应用进程利用该服务传送应用层报文。
运输层主要使用一下两种协议
- 传输控制协议-TCP:提供面向连接的,可靠的数据传输服务。
- 用户数据协议-UDP:提供无连接的,尽最大努力的数据传输服务(不保证数据传输的可靠性)。
每一个应用层(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地址。
运行在**
TCP
和UDP
**协议上:DNS(Domain Name Service,域名服务)
,用于完成地址查找,邮件转发等工作。 -
网络层
网络层的任务就是选择合适的网间路由和交换结点,确保计算机通信的数据及时传送。
在发送数据时,网络层把运输层产生的报文段或用户数据报封装成分组和包进行传送。
在 TCP/IP 体系结构中,由于网络层使用 IP 协议,因此分组也叫 IP 数据报 ,简称数据报。
互联网是由大量的异构(heterogeneous)网络通过路由器(router)相互连接起来的。
互联网使用的网络层协议是无连接的网际协议(Intert Prococol)和许多路由选择协议,因此互联网的网络层也叫做网际层或 IP 层。
-
数据链路层
数据链路层(data link layer)通常简称为链路层。两台主机之间的数据传输,总是在一段一段的链路上传送的,这就需要使用专门的链路层的协议。
在两个相邻节点之间传送数据时,数据链路层将网络层交下来的 IP 数据报组装成帧,在两个相邻节点间的链路上传送帧。每一帧包括数据和必要的控制信息(如同步信息,地址信息,差错控制等)。
在接收数据时,控制信息使接收端能够知道一个帧从哪个比特开始和到哪个比特结束。
一般的web应用的通信传输流是这样的:
发送端在层与层之间传输数据时,每经过一层时会被打上一个该层所属的首部信息。反之,接收端在层与层之间传输数据时,每经过一层时会把对应的首部信息去除。
-
物理层
在物理层上所传送的数据单位是比特。 **物理层(physical layer)的作用是实现相邻计算机节点之间比特流的透明传送,尽可能屏蔽掉具体传输介质和物理设备的差异。**使其上面的数据链路层不必考虑网络的具体传输介质是什么。“透明传送比特流”表示经实际电路传送后的比特流没有发生变化,对传送的比特流来说,这个电路好像是看不见的。
七层协议体系结构:
- 应用层
- 表示层
- 会话层
- 运输层
- 网络层
- 数据链路层
- 物理层
在互联网使用的各种协议中最重要和最著名的就是 TCP/IP 两个协议。
现在人们经常提到的 TCP/IP 并不一定是单指 TCP 和 IP 这两个具体的协议,而往往是表示互联网所使用的整个 TCP/IP 协议族。
互联网协议套件(英语:Internet Protocol Suite,缩写IPS)是一个网络通讯模型,以及一整个网络传输协议家族,为网际网络的基础通讯架构。它常被通称为TCP/IP协议族(英语:TCP/IP Protocol Suite,或TCP/IP Protocols),简称TCP/IP。因为该协定家族的两个核心协定:TCP(传输控制协议)和IP(网际协议),为该家族中最早通过的标准。
划重点:
TCP(传输控制协议)和IP(网际协议) 是最先定义的两个核心协议,所以才统称为TCP/IP协议族
TCP的三次握手四次挥手
TCP是一种面向连接的、可靠的、基于字节流的传输层通信协议,在发送数据前,通信双方必须在彼此间建立一条连接。
所谓的“连接”,其实是客户端和服务端保存的一份关于对方的信息,如ip地址、端口号等。
TCP可以看成是一种字节流,它会处理IP层或以下的层的丢包、重复以及错误问题。
在连接的建立过程中,双方需要交换一些连接的参数。这些参数可以放在TCP头部。
一个TCP连接由一个4元组构成,分别是
- 两个IP地址
- 两个端口号
一个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,两端配对。
三次握手(三报文握手)
三次握手的本质是确认通信双方收发数据的能力
首先,我让信使运输一份信件给对方,对方收到了,那么他就知道了我的发件能力和他的收件能力是可以的。
于是他给我回信,我若收到了,我便知我的发件能力和他的收件能力是可以的,并且他的发件能力和我的收件能力是可以。
然而此时他还不知道他的发件能力和我的收件能力到底可不可以,于是我最后回馈一次,他若收到了,他便清楚了他的发件能力和我的收件能力是可以的。
这,就是三次握手,这样说,你理解了吗?
为什么TCP需要进行连接
其实正如你所说的tcp也就是为了保证能够可靠的数据流而建立连接的,
而所谓的“连接”实际上是逻辑上的一种安全机制,这种机制能够保证数据包
的可靠性盒完整性,比如tcp"连接"建立的三次握手,又比如tcp中的序列号
都是为此设计的。
从物理上来讲tcp&udp一样都是被包装再ip包里面一包一包的发出去的,只不过
tcp协议本身已经实现了安全机制,从而在逻辑上成为了一种“连接”。
tcp是提供可靠性连接的,只有支持端到端的连接,才能进行可靠性传输,连接的主要功能在于记录两个端口间的通信状态,不连接则无法记录两个端口通信的状态,则无法知道丢失了哪个数据包,重复收到了哪个数据包,也无法确保数据包之间的到达顺序,还有很多增加可靠性的功能都无法应用。
第一次握手
:客户端要向服务端发起连接请求,首先客户端随机生成一个起始序列号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建立了连接。
传输控制块TCB 存储着每一个连接的重要信息。
假定TCP客户程序 为A TCP服务程序为B
在开始状态下A、B都处于CLOSED(关闭状态)
····
第一次握手:SYN=1,seq=x
A的客户进程也是创建传输TCP控制块,在打算进行TCP连接的时候,向B发送连接请求报文段,报文段中同步位标志位为1也就是 SYN=1,同时选择一个初始序号 seq=x SYN报文段(SYN=1的报文段)不能携带数据,但是要消耗一个序号,这时客户进程进入SYN-SENT(同步已发送状态)
第二次握手:SYN=1,ACK=1,seq=y,ack=x+1
B收到连接请求报文段后,如果同意连接,则向A发送确认。在确认报文段中同步位(SYN)和确认位(ACK)都设置为1,确认号为 ack=x+1,之后选择一个初始序号seq=y,这个报文段也不能携带数据,但是要消耗一个序号,这时TCP服务器进入SYN-RCVD(同步收到)状态
第三次握手:ACK=1,seq=x+1,ack=y+1
A客户进程收到B的确认后,还要对B给出确认,确认的报文段中将ACK设置为1(ACK=1)确认号 也就是ack=y+1,自己的序号则为x+1(seq=x+1)ACK报文段可以携带数据,但是如果不携带数据则不消耗序号,因为携带数据所以将seq=x+1,随后A进入ESTABLISHED (已建立连接状态)
B收到A的确认后也进入ESTABLISHED状态
为什么A 最后还要发送一次确认呢?,
这主要是为了防止已失效的连接请求报文段突然又传送到了B。因而产生错误。
已失效的连接请求报文段的产生:
正常情况:
A 发出连接请求,但是因为连接请求丢失而没有收到确认,A 重传一次连接请求。后来收到确认,建立连接。数据传输完毕之后释放连接,A发送两个连接请求,其中第一个丢失,第二个被B接收,因此没有产生 已失效的连接请求报文段
异常情况:
也就是A第一个发送的请求报文段没有被丢失,而是有可能在网络环境中滞留,导致延误到连接释放之后才到达B,这本来是已经丢掉的但是B收到这个连接请求报文段,就误认为A又一次发送新的连接请求,于是向A发送确认报文段,同意建立连接。A并没有发送新连接请求,因此就不理睬B,也不会发送数据,但是B却认为是A发送的新的连接请求已经建立,并一直等待着A发送数据,因此B的资源就被浪费了。
四次挥手
四次挥手的目的是关闭一个连接
比如客户端初始化的序列号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连接的时间要比客户端早一些。
第一次挥手 FIN=1,seq=u
数据传输结束之后,双方都可以释放连接,现在A和B都处于ESTABLISHED状态。A的进程先向器TCP发出连接释放报文段,并停止发送数据,主动关闭TCP连接。A 将终止报文段首部终止控制位FIN置1,其序号seq=u,他等于前面已经传过的数据的最后一个字节序号加1,这时A 进入了FIN-WAIT-1(终止等待1),然后等待B的确认。TCP 规定FIN报文段及时不带数据也将要消耗一个序号。
第二次挥手 ACK=1,ack=u+1,seq=v
B收到连接释放请求后立即确认,确认号为ack=u+1,ACK=1,而这个报文段自己的序号是v(seq=v)等于B前面所传输的数据的最后一个序号加1然后B进入到CLOSE-WAIT(关闭等待)状态,这时TCP服务进程通知高层应用进程,因而A到B的这个方向的连接就释放了,这个时候TCP连接处于半关闭状态,也就是说 A已经没有数据要发送了 但是B会有可能有数据要发送,此时A还要去接收。也就是B到A方向上的连接并没有被关闭,这个状态将会维持一段时间。
第三次挥手 FIN=1,ack=u+1,seq=w,ACK=1
A收到B的确认之后,进入FIN-WAIT-2,(终止等待2)状态,等待B发送的连接释放报文段。
若B已经没有要向A发送的数据,其应用进程应该通知TCP释放连接,这时B发出连接释放报文段使FIN=1,序号为w(seq=w)(在半关闭的状态下B有可能又发送了一些数据)。B还必须要重复上次发送的确认号ack=u+1,ACK=1,随后B进入LAST-ACK(最后确认状态),等待A的确认。
第四次挥手 ACK=1,ack= w + 1,seq= u + 1
A在收到B的连接释放报文段之后,必须对此做出一个确认,在确认报文段中把ACK=1 确认号为w+1(ack=w+1)而自己的序号为 u+ 1,因为A到B释放连接因此不会发送数据 而上一次发送的数据 序号为u 根据TCP的标准规定,前面发送的FIN报文段要消耗一个序号,然后进入到TIME-WAIT(时间等待)状态。注意现在TCP还没有被释放掉。必须经过时间等待计时器(TIME-WAIT timer)设置的时间2MSL后,A才能进入到CLOSED状态 时间MSL叫做最长报文段寿命 可以根据不同的实现具体的情况去设置这个MSL值,因此从A进入TIME-WAIT状态后,要经过2MSL才能进入到CLOSED状态。 B在收到A的确认报文端之后进入CLOSED(关闭)状态。才能建议下一个新的连接,当A撤销相应的传输控制块TCB之后,结束这次的TCP连接。而服务端一旦收到客户端发出的确认报文就会立马释放TCP连接,所以服务端结束TCP连接的时间要比客户端早一些。
为什么要在TIME-WAIT(时间等待状态)必须等待2MSL时间呢?两个理由
个人理解:
-
A发送的最后一个确认报文段丢失然后B进行超时重传,如果有2MSL时间 A能收到,之后A进行重传确认报文段,因此A和B都能达到CLOSED(关闭)状态,如果没有 A直接释放连接,就不会收到B重传的连接释放请求,因此就不会有A重传确认报文段,B就无法正常进入CLOSED状态。
-
经过2MS之后会使该阶段产生的数据全部从网络中消息,防止下一个连接中有旧的连接请求报文段
-
为了保证A发送的最后一个ACK报文段能够到达B。
这个ACK报文段有可能丢失,因而使处在LAST-ACK状态的B收不到对已发送的FIN + ACK报文段的确认。B会超时重传这个FIN+ ACK报文段,而A就能在2MSL时间内收到这个重传的FIN + ACK报文段。接着A重传一次确认,重新启动2MSL计时器。最后,A和B都正常进入到CLOSED状态。如果A在TIME-WAIT状态不等待-段时间,而是在发送完ACK报文段后立即释放连接,那么就无法收到B重传的FIN + ACK报文段,因而也不会再发送- -次确认报文段。这样,B就无法按照正常步骤进入CLOSED状态。
-
第二,防止上一节提到的“已失效的连接请求报文段”出现在本连接中。
A在发送完最后一个ACK报文段后,再经过时间2MSL,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失。这样就可以使下一个新的连接中不会出现这种旧的连接请求报文段。
B只要收到了A发出的确认,就进入CLOSED状态。同样,B在撤销相应的传输控制块TCB后,就结束了这次的TCP连接。我们注意到,B结束TCP连接的时间要比A早一些。
除时间等待计时器外,TCP 还设有一个保活计时器(keepalive timer)。 设想有这样的情况客户已主动与服务器建立了TCP连接。但后来客户端的主机突然出故障。显然,服务器以后就不能再收到客户发来的数据。因此,应当有措施使服务器不要再白白等待下去。这就是使用保活计时器。服务器每收到一次客户的数据,就重新设置保活计时器,时间的设置通常是两小时。若两小时没有收到客户的数据,服务器就发送一个探测报文段,以后则每隔75秒钟发送一次。若一连发送10个探测报文段后仍无客户的响应,服务器就认为客户端出了故障,接着就关闭这个连接。
为什么TCP连接的时候是3次?2次不可以吗?
因为需要考虑连接时丢包的问题,如果只握手2次,第二次握手时如果服务端发给客户端的确认报文段丢失,此时服务端已经准备好了收发数(可以理解服务端已经连接成功)据,而客户端一直没收到服务端的确认报文,所以客户端就不知道服务端是否已经准备好了(可以理解为客户端未连接成功),这种情况下客户端不会给服务端发数据,也会忽略服务端发过来的数据。
如果是三次握手,即便发生丢包也不会有问题,比如如果第三次握手客户端发的确认ack报文丢失,服务端在一段时间内没有收到确认ack报文的话就会重新进行第二次握手,也就是服务端会重发SYN报文段,客户端收到重发的报文段后会再次给服务端发送确认ack报文。
为什么TCP连接的时候是3次,关闭的时候却是4次?
因为采用的是全双工通信,只有在客户端和服务端都没有数据要发送的时候才能断开TCP。而客户端发出FIN报文时只能保证客户端没有数据发了,服务端还有没有数据发客户端是不知道的。而服务端收到客户端的FIN报文后只能先回复客户端一个确认报文来告诉客户端我服务端已经收到你的FIN报文了,但我服务端还有一些数据没发完,等这些数据发完了服务端才能给客户端发FIN报文(所以不能一次性将确认报文和FIN报文发给客户端,就是这里多出来了一次)。
为什么客户端发出第四次挥手的确认报文后要等2MSL的时间才能释放TCP连接?
这里同样是要考虑丢包的问题,如果第四次挥手的报文丢失,服务端没收到确认ack报文就会重发第三次挥手的报文,这样报文一去一回最长时间就是2MSL,所以需要等这么长时间来确认服务端确实已经收到了。
如果已经建立了连接,但是客户端突然出现故障了怎么办?
TCP设有一个保活计时器,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。
TCP四元组
唯一能够确定一个连接有4个东西:
- 服务器的IP
- 服务器的Port
- 客户端的IP
- 客户端的Port
tcp连接之半连接攻击和全连接攻击总结
https://blog.csdn.net/qq_26105397/article/details/81088188
-
半连接攻击
半连接攻击是一种攻击协议栈的攻击方式,坦白说就是攻击主机的一种攻击方式。通过将主机的资源消耗殆尽,从而导致应用层的程序无资源可用,导致无法运行。
半连接就是通过不断地构造客户端的SYN连接数据包发向服务端,等到服务端的半连接队列满的时候,后续的正常用户的连接请求将会被丢弃,从而无法连接到服务端。此为半连接攻击方式。
如何来解决半连接攻击?
可以通过拓展半连接队列的大小,来进行补救,但缺点是,不能无限制的增加,这样会耗费过多的服务端资源,导致服务端性能地下。这种方式几乎不可取。现主要通syn cookie或者syn中继机制来防范半连接攻,部位半连接分配核心内存的方式来防范。
https://netsecurity.51cto.com/art/201009/225615_all.htm
-
全连接攻击
全连接攻击是通过==消费服务端进程数和连接数,只连接而不进行发送数据的一种攻击方式。==当客户端连接到服务端,仅仅只是连接,此时服务端会为每一个连接创建一个进程来处理客户端发送的数据。但是客户端只是连接而不发送数据,此时服务端会一直阻塞在recv或者read的状态,如此一来,多个连接,服务端的每个连接都是出于阻塞状态从而导致服务端的崩溃。
如何来解决全连接攻击?
可以通过不为全连接分配进程处理的方式来防范全连接攻击,具体的情况是当收到数据之后,在为其分配一个处理线程。具体的处理方式在accept返回之前是不分配处理线程的。直到接收相关的数据之后才为之提供一个处理过程。
什么是HTTP,HTTP 与 HTTPS 的区别
HTTP 是一个在计算机世界里专门在两点之间传输文字、图片、音频、视频等超文本数据的约定和规范 超文本传送协议 面向事务的应用层协议 无状态的 HTTPS S 代表 security
- HTTPS需要带CA申请证书
- HTTP协议运行在TCP之上,HTTPS运行在SSL/TLS之上。SSL/TLS运行在TCP之上,所以传输内容都是经过加密的。
- HTTP与HTTPS采用完全不同的连接方式,端口也不一样,HTTP端口是80,HTTPS端口是443
- HTTPS可以有效防止运行商劫持,解决了防劫持的一个大问题
HTTP是应用层协议,TCP是传输层协议!
HTTP承载在TCP之上。
打个比喻,网络是路,TCP是跑在路上的车,HTTP是车上的人
Https=Http协议 + ssl
HTTP与HTTPS有什么区别;
①、https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用。
②、http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。
③、http默认是80端口,后者默认是443端口。
④、http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议
HTTP的请求流程
https://www.cnblogs.com/gzshan/p/11125038.html
HTTPS的建立连接过程
- 在使用HTTPS是需要保证服务端配置正确了对应的安全证书
- 客户端发送请求到服务端
- 服务端返回公钥和证书到客户端
- 客户端接收后会验证证书的安全性,如果通过则会随机生成一个随机数,用公钥对其加密,发送到服务端
- 服务端接受到这个加密后的随机数后会用私钥对其解密得到真正的随机数,随后用这个随机数当做私钥对需要发送的数据进行对称加密
- 客户端在接收到加密后的数据使用私钥(即生成的随机值)对数据进行解密并且解析数据呈现结果给客户
- SSL加密建立
SSL在传输层与应用层之间对网络连接进行加密。SSL协议工作在(应用层与传输层之间)。
衍生问题1:*HTTPS的加密方式是什么?*
HTTPS采用对称加密+非对称加密的方式进行加密,具体方式可以参考下图:
常用HTTP状态码
HTTP状态码表示客户端HTTP请求的返回结果、标识服务器处理是否正常、表明请求出现的错误等。
405表示 请求方式的错误
https://blog.csdn.net/huwei2003/article/details/70139062
说一说HTTP的首部字段
- Connection:浏览器想要优先使用的连接类型
- Cache-control:控制缓存的行为
- Date:创建报文的时间
- Accept:能正确接收的媒体类型:application/json、text/plain
- Accept-charset:用户支持的字符集
- Authorization:客户端认证信息,一般存token信息
- Cookie:发送给服务器的Cookie信息
- Host:服务器的域名
- Location:重定向到某个URL
- Set-Cookie:需要存在客户端的信息
Http的请求头包含什么信息
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-36eqBKjN-1673796690819)(https://gitee.com/Code_Farming_Liu/image/raw/master/img/20201018185950.png)]
http 请求包含哪几个部分(请求行、请求头、请求体)
http协议报文
1.请求报文(请求行/请求头/请求数据/空行)
请求行
请求方法字段、URL字段和HTTP协议版本
例如:GET /index.html HTTP/1.1
get方法将数据拼接在url后面,传递参数受限
请求方法:
GET、POST、HEAD、PUT、DELETE、OPTIONS、TRACE、CONNECT
请求头(key value形式)
User-Agent:产生请求的浏览器类型。
Accept:客户端可识别的内容类型列表。
Host:主机地址
请求数据
post方法中,会把数据以key value形式发送请求
空行
发送回车符和换行符,通知服务器以下不再有请求头
2.响应报文(状态行、消息报头、响应正文)
状态行
消息报头
响应正文
GET和POST区别
说到GET和POST,就不得不提HTTP协议,因为浏览器和服务器的交互是通过HTTP协议执行的,而GET和POST也是HTTP协议中的两种方法。
HTTP全称为Hyper Text Transfer Protocol,中文翻译为超文本传输协议,目的是保证浏览器与服务器之间的通信。HTTP的工作方式是客户端与服务器之间的请求-应答协议。
HTTP协议中定义了浏览器和服务器进行交互的不同方法,基本方法有4种,分别是GET,POST,PUT,DELETE。这四种方法可以理解为,对服务器资源的查,改,增,删。
GET和POST区别
-
Get是不安全的,因为在传输过程,数据被放在请求的URL中;Post的所有操作对用户来说都是不可见的。 但是这种做法也不时绝对的,大部分人的做法也是按照上面的说法来的,但是也可以在get请求加上 request body,给 post请求带上 URL 参数。
-
Get请求提交的url中的数据最多只能是2048字节,这个限制是浏览器或者服务器给添加的,http协议并没有对url长度进行限制,目的是为了保证服务器和浏览器能够正常运行,防止有人恶意发送请求。Post请求则没有大小限制。
-
Get限制Form表单的数据集的值必须为ASCII字符;而Post支持整个ISO10646字符集。
-
Get执行效率却比Post方法好。Get是form提交的默认方法。
-
GET产生一个TCP数据包;POST产生两个TCP数据包。
对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);
而对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。
-
get请求传递的参数长度有限制,而POST没有
-
post请求比get请求安全,因为数据在地址栏不可见。
-
post请求会发出两个TCP数据包
其实这样说是不严谨的:
-
get和post传递的参数本身是没有限制的,只是get请求是基于浏览器URL传参,浏览器会对URL的长度做限制,所以才会有get请求传递的参数长度有限制的说法。
-
从传输的角度来说,get请求和post请求都是不安全的,因为HTTP是明文传输,要想安全的传输就采用HTTPS。
-
POST请求会发送两个TCP数据包,这个只是某些浏览器和框架的请求方式,不是POST请求的必然行为。
-
POST和GET请求最本质的区别就是,get具有幂等性。幂等性是指:一次或多次请求一个资源应该产生相同的副作用。
-
Get是从服务器上获得数据,而Post则是向服务器传递数据的。
-
Get是不安全的,很可能你的一些操作会被第三方看到,而Post的所有操作多用户来说是不可见的。
-
Get传输的数据量小,主要是因为它受约于URL长度的限制,而Post可以传输大量的数据,所以我们在传文件的时候会用Post。
-
Get只能进行url编码,而post支持多种编码方式
-
get比post效率要高
什么是对称加密与非对称加密
对称密钥加密是指加密和解密使用同一个密钥的方式,这种方式存在的最大问题就是密钥发送问题,即如何安全地将密钥发给对方;
公钥的密码体制产生原因: 对称密钥的密码体制的密钥分配问题,二是对于数字签名的需求
任何加密算法的安全性取决于密钥的长度,以及攻破密文的计算量
数字签名的特点:
- 报文鉴别
- 报文完整性
- 不可否认
而非对称加密是指使用一对非对称密钥,即公钥和私钥,公钥可以随意发布,但私钥只有自己知道。发送密文的一方使用对方的公钥进行加密处理,对方接收到加密信息后,使用自己的私钥进行解密。
由于非对称加密的方式不需要发送用来解密的私钥,所以可以保证安全性;但是和对称加密比起来,非常的慢
什么是HTTP2
HTTP2 可以提高了网页的性能。
在 HTTP1 中浏览器限制了同一个域名下的请求数量(Chrome 下一般是六个),当在请求很多资源的时候,由于队头阻塞当浏览器达到最大请求数量时,剩余的资源需等待当前的六个请求完成后才能发起请求。
HTTP2 中引入了多路复用的技术,这个技术可以只通过一个 TCP 连接就可以传输所有的请求数据。多路复用可以绕过浏览器限制同一个域名下的请求数量的问题,进而提高了网页的性能。
**HTTP/1.0:**每次请求都打开一个新的TCP连接,收到响应之后立即断开连接。
HTTP/1.1:
- 在请求头中新增了range头域,允许只请求资源的某一部分
- HTTP/1.1的请求头和响应头必须有HOST头域,用来区分同一物理主机的不用虚拟服务器
- 长连接,一个TCP连接可以发送多个HTTP请求,减少了建立和关闭连接的消耗和延迟
- **缺点:**线头堵塞、基于文本协议、请求和响应头信息非常大,无法压缩,单向请求
HTTP/2.0:
- 采用二进制协议解析
- 压缩头部,减少了需要传输的头部大小,并且通讯的双方都保存一份头部,避免重复的头部传输,减少了传输的大小
- 多路复用,允许并发的发送多个HTTP请求,每个请求不需要等待其他请求或响应,避免了线头阻塞的问题。这样如果某个请求任务耗时严重,不会影响到其他连接正常的执行,极大提高了传输性能。
- 服务端推送,会把客户端需要的css/img伴随index.html一同推送到客户端,省去了客户端的重复请求,需要使用时直接从缓存中获取。
- **缺点:**如果TCP连接中出现了丢包的现象,会导致整个TCP连接重传
HTTP/3.0:
- HTTP/1.1、HTTP/2.0的缺点都是因为底层协议TCP导致的,所以Google 基于 UDP 协议推出了一个的 QUIC 协议,并且使用在了 HTTP/3 上。
- HTTP/3.0有避免包堵塞、快速重启会话的优点
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的目的是为了减轻服务器的压力,减少频繁的查询数据库,使服务器更加健壮。
Token 是在服务端产生的。如果前端使用用户名/密码向服务端请求认证,服务端认证成功,那么在服务端会返回 Token 给前端。前端可以在每次请求的时候带上 Token 证明自己的合法地位
session与token区别
- session机制存在服务器压力增大,CSRF跨站伪造请求攻击,扩展性不强等问题;
- session存储在服务器端,token存储在客户端
- token提供认证和授权功能,作为身份认证,token安全性比session好;
- session这种会话存储方式方式只适用于客户端代码和服务端代码运行在同一台服务器上,token适用于项目级的前后端分离(前后端代码运行在不同的服务器下)
Servlet是线程安全的吗
单例多线程 Servlet不是线程安全的,多线程并发的读写会导致数据不同步的问题。
解决的办法是 尽量不要定义name属性,而是要把name变量分别定义在doGet()和doPost()方法内。虽然使用synchronized(name){}语句块可以解决问题,但是会造成线程的等待,不是很科学的办法。 尽量不定义成员变量
注意:多线程的并发的读写Servlet类属性会导致数据不同步。但是如果只是并发地读取属性而不写入,则不存在数据不同步的问题。因此Servlet里的只读属性最好定义为final类型的。
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方法中,这样就不需要每次处理客户端的请求都要初始化与销毁资源。
如果客户端禁止 cookie 能实现 session 还能用吗?
Cookie 与 Session,一般认为是两个独立的东西,Session采用的是在服务器端保持状态的方案,而Cookie采用的是在客户端保持状态的方案。
但为什么禁用Cookie就不能得到Session呢?
因为Session是用Session ID来确定当前对话所对应的服务器Session,而Session ID是通过Cookie来传递的,禁用Cookie相当于失去了Session ID,也就得不到Session了。
假定用户关闭Cookie的情况下使用Session,其实现途径有以下几种:
- 手动通过URL传值、隐藏表单传递Session ID。
- 用文件、数据库等形式保存Session ID,在跨页过程中手动调用。
什么是DNS?
DNS是域名解析系统,最初,由于ip长且难记,通过ip访问网站不方便,所以出现了域名解析系统,将域名转化为对应的ip地址。DNS主要采用了UDP进行通讯,主机向本地域名服务器查询一般递归查询,本地域名向根域名服务器查询采用迭代查询。
递归查询:所谓的递归查询就是:如果主机所询问的本地域名服务器不知道被查询的域名的IP地址,那么本地域名服务器就以DNS客户的身份,向其他根域名服务器继续发出查询请求报文(即替该主机继续查询),而不是让该主机自己进行下一步的查询。因此,递归查询返回的查询结果或者是所要查询的IP地址,或者是报错,表示无法查询到所需要的IP地址。
迭代查询:当根域名服务器收到本地域名服务器收到本地域名服务器发出的迭代查询请求报文时,要么给出所要查询的IP地址,要么告诉本地域名服务器进行后续的查询(而不是替本地域名服务器进行后续的查询)。
什么是CDN?
CDN工作原理
CDN是内容分发网络,尽可能避开互联网上有可能影响数据传出速度和稳定性的瓶颈和环节,使传输内容更快、更稳定。CDN只是对某个具体的域名的加速。
过程:
-
用户向浏览器提供要访问的域名;
-
浏览器调用域名解析库对域名进行解析,由于CDN对域名解析过程进行了调整,所以解析函数库得到的是该域名对应的CNAME记录(由于现在已经是使用了CDN服务,CNAME为CDN服务商域名),为了得到实际IP地址,浏览器需要再次对获得的CNAME域名进行解析以得到实际的IP地址;在此过程中,使用的全局负载均衡DNS解析,如根据地理位置信息解析对应的IP地址,使得用户能就近访问。(CDN服务来提供最近的机器)
-
此次解析得到CDN缓存服务器的IP地址,浏览器在得到实际的IP地址以后,向缓存服务器发出访问请求;
-
缓存服务器根据浏览器提供的要访问的域名,通过Cache内部专用DNS解析得到此域名的实际IP地址,再由缓存服务器向此实际IP地址提交访问请求;
-
缓存服务器从实际IP地址得得到内容以后,一方面在本地进行保存,以备以后使用,二方面把获取的数据返回给客户端,完成数据服务过程;
-
客户端得到由缓存服务器返回的数据以后显示出来并完成整个浏览的数据请求过程。
-
当用户点击网站页面上的URL内容,经过本地DNS解析,会将域名的解析权交给CNAME指向的CDN专用DNS服务器。
-
CDN的DNS服务器将CDN的全局均衡设备IP地址返回给用户
-
用户向CDN的全局负载均衡设备发起请求
-
CDN全局均衡设备根据用户IP地址,以及用户请求的内容URL,选择一台用户所属区域的区域均衡设备,并将请求转发到这台设备。
-
区域负载均衡设备根据用户IP、请求的URL为用户选择一台 合适的缓存服务器提供内容。
-
全局负载均衡设备把服务器的IP返回给用户
-
用户向CDN缓存服务器发起请求,
-
缓存服务器响应用户请求,将用户所需内容传送到用户终端。
CDN工作原理
①当用户点击网站页面上的内容URL,经过本地DNS系统解析,DNS系统会最终将域名的解析权交给CNAME指向的CDN专用DNS服务器。
②CDN的DNS服务器将CDN的全局负载均衡设备IP地址返回用户。
③用户向CDN的全局负载均衡设备发起内容URL访问请求。
④CDN全局负载均衡设备根据用户IP地址,以及用户请求的内容URL,选择一台用户所属区域的区域负载均衡设备,告诉用户向这台设备发起请求。
⑤区域负载均衡设备会为用户选择一台合适的缓存服务器提供服务,选择的依据包括:根据用户IP地址,判断哪一台服务器距用户最近;根据用户所请求的URL中携带的内容名称,判断哪一台服务器上有用户所需内容;查询各个服务器当前的负载情况,判断哪一台服务器尚有服务能力。基于以上这些条件的综合分析之后,区域负载均衡设备会向全局负载均衡设备返回一台缓存服务器的IP地址。
⑥全局负载均衡设备把服务器的IP地址返回给用户。
⑦用户向缓存服务器发起请求,缓存服务器响应用户请求,将用户所需内容传送到用户终端。如果这台缓存服务器上并没有用户想要的内容,而区域均衡设备依然将它分配给了用户,那么这台服务器就要向它的上一级缓存服务器请求内容,直至追溯到网站的源服务器将内容拉到本地。
DNS服务器根据用户IP地址,将域名解析成相应节点的缓存服务器IP地址,实现用户就近访问。使用CDN服务的网站,只需将其域名解析权交给CDN的GSLB设备,将需要分发的内容注入CDN,就可以实现网站内容加速。
什么是UDP?
UDP全称用户数据报协议,是运输层协议。具有以下特点:
- 无连接:发送数据之前不需要建立连接 减少开销和发送数据之前的时延
- 面向报文:应用层传过来多少数据,原封不动的向下传递 因此需要选择合适的报文长度
- 尽最大努力交付:不可靠连接 主机不需要维持复杂的连接状态
- 首部开销较小:首部仅有8个字节
- 可以实现一对一、一对多、多对多之间的通讯
- 没有拥塞控制
什么是TCP?
TCP全称传输控制协议,也是运输层协议。TCP是面向连接的、可靠的字节流服务。具有以下特点:
- 面向连接的运输层协议
- 一条TCP连接只有两个端口 只能是点对点
- TCP提供全双工的通信,并且接收端和发送端都设有缓存
- 面向字节流的服务 字节流指的是流入到进程或从进程流出的字节序列
- 可靠传输、流量控制、拥塞控制
**可靠传输:**已字节为单位的滑动窗口、超时重传
**流量控制:**利用滑动窗口实现的流量控制,告诉发送方不要太快,要让接收方来的及接受
**拥塞控制:**采用了四种算法:慢开始、拥塞避免、快重传、快恢复
**衍生问题1:**流量控制和拥塞控制的区别
- 流量控制是一个端到端的问题,是接收端告诉发送端发送数据的速率,以便使接收端来的及接受
- 拥塞控制是一个全局性的过程,涉及所有主机,所有路由器以及与降低网络传输性能有关的所有因素
Tcp(传输控制协议)与Udp(用户数据报协议)的区别
TCP是一种面向连接的、可靠的、基于字节流的传输层通信协议,是专门为了在不可靠的网络中提供一个可靠的端对端字节流而设计的,面向字节流。
UDP(用户数据报协议)是iso参考模型中一种无连接的传输层协议,提供简单不可靠的非连接传输层服务,面向报文
区别:
1) TCP是面向连接的,可靠性高;UDP是基于非连接的,可靠性低 面向报文的
2) 由于TCP是连接的通信,需要有三次握手、重新确认等连接过程,会有延时,实时性差,同时过程复杂,也使其易于攻击;UDP没有建立连接的过程,因而实时性较强,也稍安全
3) 在传输相同大小的数据时,TCP首部开销20字节;UDP首部开销8字节,TCP报头比UDP复杂,故实际包含的用户数据较少。TCP在IP协议的基础上添加了序号机制、确认机制、超时重传机制等,保证了传输的可靠性,不会出现丢包或乱序,而UDP有丢包,故TCP开销大,UDP开销较小
4) 每条TCP连接只能时点到点的;UDP支持一对一、一对多、多对一、多对多的交互通信
应用场景选择
对实时性要求高和高速传输的场合下使用UDP;在可靠性要求低,追求效率的情况下使用UDP;
需要传输大量数据且对可靠性要求高的情况下使用TCP
补充
tcp是socket建立连接的,一个服务器socket和和一个客户端socket建立通信,udp是广播模式,不需要建立连接
TCP与IP的区别
-
TCP:又叫传输控制协议(Transmission Control Protocal)是一种面向连接的、端对端的、可靠的、基于IP的传输层协议。主要特点是3次握手建立连接,4次挥手断开连接。
-
IP:又叫因特网协议(Internet Protocol),IP协议位于网络层,IP协议规定了数据传输时的基本单元(数据包)和格式,IP协议还定义了数据包的递交办法和路由选择。
总结:整个网络中的传输流程是:IP层接收由更低层(网络接口层例如以太网设备驱动程序)发来的数据包,并把该数据包发送到更高层—TCP层;相反,IP层也把从TCP接收来的数据包传送到更低层。
TCP和IP的关系是:IP提供基本的数据传送,而高层的TCP对这些数据包做进一步加工,如提供端口号等等。
从输入URL到页面加载完成都发生了什么?
重要
https://blog.csdn.net/qq_40959677/article/details/94873075?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control&dist_request_id=1328741.27106.16169191275911073&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control
- 判断浏览器缓存是否可用,如果可用直接获取缓存
- 进行DNS解析,获得对应ip
- 建立TCP连接
- 发送HTTP请求
- 服务端收到请求之后,返回响应
- 浏览器渲染构建DOM树
- 断开TCP连接
1、浏览器的地址栏输入URL并按下回车。
2、浏览器查找当前URL是否存在缓存,并比较缓存是否过期。
3、DNS解析URL对应的IP。
4、根据IP建立TCP连接(三次握手)。
5、HTTP发起请求。
6、服务器处理请求,浏览器接收HTTP响应。
7、渲染页面,构建DOM树。
8、关闭TCP连接(四次挥手)。
说完整个过程的几个关键点后我们再来展开的说一下。
一、URL
我们常见的RUL是这样的:http://www.baidu.com,这个域名由三部分组成:协议名、域名、端口号,这里端口是默认所以隐藏。除此之外URL还会包含一些路径、查询和其他片段,例如:http://www.tuicool.com/search?kw=%E4%。我们最常见的的协议是HTTP协议,除此之外还有加密的HTTPS协议、FTP协议、FILe协议等等。URL的中间部分为域名或者是IP,之后就是端口号了。通常端口号不常见是因为大部分的都是使用默认端口,如HTTP默认端口80,HTTPS默认端口443。说到这里可能有的面试官会问你同源策略,以及更深层次的跨域的问题,我今天就不在这里展开了。
二、缓存
说完URL我们说说浏览器缓存,HTTP缓存有多种规则,根据是否需要重新向服务器发起请求来分类,我将其分为强制缓存,对比缓存。
强制缓存判断HTTP首部字段:cache-control,Expires。
Expires是一个绝对时间,即服务器时间。浏览器检查当前时间,如果还没到失效时间就直接使用缓存文件。但是该方法存在一个问题:服务器时间与客户端时间可能不一致。因此该字段已经很少使用。
cache-control中的max-age保存一个相对时间。例如Cache-Control: max-age = 484200,表示浏览器收到文件后,缓存在484200s内均有效。 如果同时存在cache-control和Expires,浏览器总是优先使用cache-control。
对比缓存通过HTTP的last-modified,Etag字段进行判断。
last-modified是第一次请求资源时,服务器返回的字段,表示最后一次更新的时间。下一次浏览器请求资源时就发送if-modified-since字段。服务器用本地Last-modified时间与if-modified-since时间比较,如果不一致则认为缓存已过期并返回新资源给浏览器;如果时间一致则发送304状态码,让浏览器继续使用缓存。
Etag:资源的实体标识(哈希字符串),当资源内容更新时,Etag会改变。服务器会判断Etag是否发生变化,如果变化则返回新资源,否则返回304。
三、DNS域名解析
我们知道在地址栏输入的域名并不是最后资源所在的真实位置,域名只是与IP地址的一个映射。网络服务器的IP地址那么多,我们不可能去记一串串的数字,因此域名就产生了,域名解析的过程实际是将域名还原为IP地址的过程。
1. 首先浏览器先检查本地hosts文件是否有这个网址映射关系,如果有就调用这个IP地址映射,完成域名解析。
2. 如果没找到则会查找本地DNS解析器缓存,如果查找到则返回。
3. 如果还是没有找到则会查找本地DNS服务器,如果查找到则返回。
4. 最后迭代查询,按根域服务器 ->顶级域,.cn->第二层域,hb.cn ->子域,www.hb.cn的顺序找到IP地址。
完整的HTTP请求包含请求起始行、请求头部、请求主体三部分。
浏览器是怎么渲染页面的
浏览器是边解析边渲染,首先浏览器解析HTML文件构建DOM树,然后会解析CSS文件构建CSS 规则树,最后根据DOM树和CSS规则树构建渲染树,等到渲染树构建完成之后,浏览器开始布局渲染树并将其绘制到屏幕,这个过程要尽量减少回流和重绘。
JS的解析是由浏览器中的JS引擎完成的,JS是单线程,文档中的JS加载并且解析完成之后,才会继续HTML的渲染,因为JS可能会改变DOM结构,所以要把script标签放到body后面,这里也可以使用defer和async。defer表示延迟执行,等到HTML解析完毕并且script加载完成才执行,async表示异步引入,如果加载好了就会立即执行。CSS文件不会影响JS的加载,但是会影响JS的解析。
从输入域名,到拿到首页的所有细节过程
[https://blog.csdn.net/qq_39380590/article/details/82432364?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522160405138619725222455214%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=160405138619725222455214&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_v2~rank_v28-1-82432364.pc_first_rank_v2_rank_v28&utm_term=%E4%B8%BB%E5%9F%9F%E5%90%8D%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%9C%A8%E6%8E%A5%E6%94%B6%E5%88%B0%E5%9F%9F%E5%90%8D%E8%AF%B7%E6%B1%82%E5%90%8E%E9%A6%96%E5%85%88%E6%9F%A5%E8%AF%A2%E7%9A%84%E6%98%AF%E4%BB%80%E4%B9%88&spm=1018.2118.3001.4449]
详细说一下跨域
跨域详解及其常见的解决方式
只有浏览器具有同源策略,同源策略是指“协议+域名+端口”相同,否则就是跨域,即使2个不同的域名指向同一个服务端也是跨域。服务器与服务器之间不存在跨域。
解决跨域的方案有:
JSONP:
利用<script>
表示没有跨域限制的漏洞,网页可以获得从其他来源动态产生的JSON
数据。JSONP
请求一定需要对方的服务器支持才可以。**优缺点:**简单兼容性好,但是仅支持GET方法具有局限性。CORS:
需要后端设置Access-Control-Allow-Origin
字段,表示哪些域名可以访问资源。CORS
默认不会发生cookie
,只有Access-Control-Allow-Credentials
为true
,表示服务器允许该请求包含cookie
信息。postMessage:
允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本文档、多窗口、跨域消息传递。websocket:
是H5的一个持久化协议,实现了客户端与浏览器的全双工通信。- 代理
其中CORS分为简单请求和复杂请求。对于简单请求,浏览器直接发出CORS
请求,就是在头信息中增加一个Origin
字段,来说明请求来自哪个源,如果不在许可范围内,则响应头中没有Access-Conctrol-Allow-Origin
,浏览器会报错。复杂请求会在正式通信之前,增加一次HTTP请求(options
请求方法),称之为“预检”请求。浏览器先会询问服务器,当前网站所在的域名是否在服务器的许可名单之内,只有得到肯定的答复,浏览器才会发出正式的请求,否则就报错
什么是XSS攻击和CSRF攻击
https://freessl.wosign.com/1336.html
**XSS:**跨站脚本攻击是一种代码注入攻击。攻击者通过在目标网站上注入恶意脚本,使之在用户的浏览器上运行。
XSS攻击的分类:
- 存储型XSS:攻击者将恶意代码提交到目标网站的数据库中
- 反射型XSS:攻击者构造出特殊的URL,其中包含恶意代码,用户打开带有恶意代码的 URL 时,网站服务端将恶意代码从 URL 中取出,拼接在 HTML 中返回给浏览器。
- DOM型XSS:攻击者构造出特殊的 URL,其中包含恶意代码。用户浏览器接收到响应后解析执行,前端 JavaScript 取出 URL 中的恶意代码并执行。
**预防XSS攻击:**改为纯前端渲染,把代码和数据分隔开、对输入的内容做长度控制 、避免内联事件
**CSRF:**跨站请求伪造,攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求。利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证,达到冒充用户对被攻击的网站执行某项操作的目的。
**预防CSRF攻击:**阻止不明外域的访问(同源检测) 、提交时要求附加本域才能获取的信息(CSRF Token、双重cookie验证)
SQL注入是比较常见的网络攻击方式之一,它不是利用操作系统的BUG来实现攻击,而是针对程序员编写时的疏忽,通过SQL语句,实现无账号登录,甚至篡改数据库。
二:SQL注入攻击的总体思路
1:寻找到SQL注入的位置
2:判断服务器类型和后台数据库类型
3:针对不同的服务器和数据库特点进行SQL注入攻击
三:SQL注入攻击实例
[](javascript:void(0)😉
String sql = "select * from user_table where username=
' "+userName+" ' and password=' "+password+" '";
--当输入了上面的用户名和密码,上面的SQL语句变成:
SELECT * FROM user_table WHERE username=
'’or 1 = 1 -- and password='’
"""
--分析SQL语句:
--条件后面username=”or 1=1 用户名等于 ” 或1=1 那么这个条件一定会成功;
--然后后面加两个-,这意味着注释,它将后面的语句注释,让他们不起作用,这样语句永远都--能正确执行,用户轻易骗过系统,获取合法身份。
--这还是比较温柔的,如果是执行
SELECT * FROM user_table WHERE
username='' ;DROP DATABASE (DB Name) --' and password=''
--其后果可想而知…
"""
四:如何防御SQL注入
注意:但凡有SQL注入漏洞的程序,都是因为程序要接受来自客户端用户输入的变量或URL传递的参数,并且这个变量或参数是组成SQL语句的一部分,对于用户输入的内容或传递的参数,我们应该要时刻保持警惕,这是安全领域里的「外部数据不可信任」的原则,纵观Web安全领域的各种攻击方式,大多数都是因为开发者违反了这个原则而导致的,所以自然能想到的,就是从变量的检测、过滤、验证下手,确保变量是开发者所预想的。
1、检查变量数据类型和格式
如果你的SQL语句是类似where id={$id}这种形式,数据库里所有的id都是数字,那么就应该在SQL被执行前,检查确保变量id是int类型;如果是接受邮箱,那就应该检查并严格确保变量一定是邮箱的格式,其他的类型比如日期、时间等也是一个道理。总结起来:只要是有固定格式的变量,在SQL语句执行前,应该严格按照固定格式去检查,确保变量是我们预想的格式,这样很大程度上可以避免SQL注入攻击。
比如,我们前面接受username参数例子中,我们的产品设计应该是在用户注册的一开始,就有一个用户名的规则,比如5-20个字符,只能由大小写字母、数字以及一些安全的符号组成,不包含特殊字符。此时我们应该有一个check_username的函数来进行统一的检查。不过,仍然有很多例外情况并不能应用到这一准则,比如文章发布系统,评论系统等必须要允许用户提交任意字符串的场景,这就需要采用过滤等其他方案了。
2、过滤特殊符号
对于无法确定固定格式的变量,一定要进行特殊符号过滤或转义处理。
3、绑定变量,使用预编译语句
MySQL的mysqli驱动提供了预编译语句的支持,不同的程序语言,都分别有使用预编译语句的方法
实际上,绑定变量使用预编译语句是预防SQL注入的最佳方式,使用预编译的SQL语句语义不会发生改变,在SQL语句中,变量用问号?表示,黑客即使本事再大,也无法改变SQL语句的结构
1.1:预编译语句是什么
通常我们的一条sql在db接收到最终执行完毕返回可以分为下面三个过程:
-
- 词法和语义解析
- 优化sql语句,制定执行计划
- 执行并返回结果
我们把这种普通语句称作Immediate Statements。
但是很多情况,我们的一条sql语句可能会反复执行,或者每次执行的时候只有个别的值不同(比如query的where子句值不同,update的set子句值不同,insert的values值不同)。
如果每次都需要经过上面的词法语义解析、语句优化、制定执行计划等,则效率就明显不行了。
所谓预编译语句就是将这类语句中的值用占位符替代,可以视为将sql语句模板化或者说参数化,一般称这类语句叫Prepared Statements或者Parameterized Statements
预编译语句的优势在于归纳为:一次编译、多次运行,省去了解析优化等过程;此外预编译语句能防止sql注入。
当然就优化来说,很多时候最优的执行计划不是光靠知道sql语句的模板就能决定了,往往就是需要通过具体值来预估出成本代价。
加密相关
https://blog.csdn.net/Jack__iT/article/details/86490908
对称加密和非对称加密区别
https://blog.csdn.net/qq_29689487/article/details/81634057
TCP粘包,拆包及解决方法
http请求报文格式和响应报文格式
WebSocket的实现原理
https://www.jianshu.com/p/3444ea70b6cb
https://blog.csdn.net/qq_35623773/article/details/87868682
DDOS分布式拒绝服务攻击
分布式拒绝服务攻击可以使很多的计算机在同一时间遭受到攻击,使攻击的目标无法正常使用,分布式拒绝服务攻击已经出现了很多次,导致很多的大型网站都出现了无法进行操作的情况
分布式拒绝服务攻击方式在进行攻击的时候,可以对源IP地址进行伪造,这样就使得这种攻击在发生的时候隐蔽性是非常好的,同时要对攻击进行检测也是非常困难的,因此这种攻击方式也成为了非常难以防范的攻击。
分布式拒绝服务攻击(英文意思是Distributed Denial of Service,简称DDoS)是指处于不同位置的多个攻击者同时向一个或数个目标发动攻击,或者一个攻击者控制了位于不同位置的多台机器并利用这些机器对受害者同时实施攻击。由于攻击的发出点是分布在不同地方的,这类攻击称为分布式拒绝服务攻击,其中的攻击者可以有多个。
它利用网络协议和操作系统的一些缺陷,采用欺骗和伪装的策略来进行网络攻击,使网站服务器充斥大量要求回复的信息,消耗网络带宽或系统资源,导致网络或系统不胜负荷以至于瘫痪而停止提供正常的网络服务。
它利用网络协议和操作系统的一些缺陷,采用欺骗和伪装的策略来进行网络攻击,使网站服务器充斥大量要求回复的信息,消耗网络带宽或系统资源,导致网络或系统不胜负荷以至于瘫痪而停止提供正常的网络服务。
UDP如何实现可靠传输
https://www.jianshu.com/p/6c73a4585eba
UDP不属于连接协议,具有资源消耗少,处理速度快的优点,所以通常音频,视频和普通数据在传送时,使用UDP较多,因为即使丢失少量的包,也不会对接受结果产生较大的影响。
传输层无法保证数据的可靠传输,只能通过应用层来实现了。实现的方式可以参照tcp可靠性传输的方式,只是实现不在传输层,实现转移到了应用层。
综合阐述http1.0/1.1/2和https
https://blog.csdn.net/weixin_37719279/article/details/81388358
nat协议
NAT(Network Address Translation,网络地址转换)是1994年提出的。当在专用网内部的一些主机本来已经分配到了本地IP地址(即仅在本专用网内使用的专用地址),但现在又想和因特网上的主机通信(并不需要加密)时,可使用NAT方法。
这种方法需要在专用网(私网IP)连接到因特网(公网IP)的路由器上安装NAT软件。装有NAT软件的路由器叫做NAT路由器,它至少有一个有效的外部全球IP地址(公网IP地址)。这样,所有使用本地地址(私网IP地址)的主机在和外界通信时,都要在NAT路由器上将其本地地址转换成全球IP地址,才能和因特网连接。
NAT不仅能解决IP地址不足的问题,而且还能够有效地避免来自网络外部的攻击,隐藏并保护网络内部的计算机。
1.宽带分享:这是 NAT 主机的最大功能。
2.安全防护:NAT 之内的 PC 联机到 Internet 上面时,他所显示的 IP 是 NAT 主机的公共 IP,所以 Client 端的 PC 当然就具有一定程度的安全了,外界在进行 portscan(端口扫描) 的时候,就侦测不到源Client 端的 PC 。
NAT的实现方式有三种,即静态转换Static Nat、动态转换Dynamic Nat和端口多路复用OverLoad。
静态****转换是指将内部网络的私有IP地址转换为公有IP地址,IP地址对是一对一的,是一成不变的,某个私有IP地址只转换为某个公有IP地址。借助于静态转换,可以实现外部网络对内部网络中某些特定设备(如服务器)的访问。
动态转换是指将内部网络的私有IP地址转换为公用IP地址时,IP地址是不确定的,是随机的,所有**被授权访问上Internet的私有IP地址可随机转换为任何指定的合法IP地址。**也就是说,只要指定哪些内部地址可以进行转换,以及用哪些合法地址作为外部地址时,就可以进行动态转换。动态转换可以使用多个合法外部地址集。当ISP提供的合法IP地址略少于网络内部的计算机数量时。可以采用动态转换的方式。
端口多路复用(Port address Translation,PAT)==是指改变外出数据包的源端口并进行端口转换,即端口地址转换(PAT,Port Address Translation).==采用端口多路复用方式。内部网络的所有主机均可共享一个合法外部IP地址实现对Internet的访问,从而可以最大限度地节约IP地址资源。同时,又可隐藏网络内部的所有主机,有效避免来自internet的攻击。因此,目前网络中应用最多的就是端口多路复用方式。
ALG(Application Level Gateway),即应用程序级网关技术:传统的NAT技术只对IP层和传输层头部进行转换处理,但是一些应用层协议,在协议数据报文中包含了地址信息。为了使得这些应用也能透明地完成NAT转换,NAT使用一种称作ALG的技术,它能对这些应用程序在通信时所包含的地址信息也进行相应的NAT转换。
如果协议数据报文中不包含地址信息,则很容易利用传统的NAT技术来完成透明的地址转换功能,通常我们使用的如下应用就可以直接利用传统的NAT技术:HTTP、TELNET、FINGER、NTP、NFS、ARCHIE、RLOGIN、RSH、RCP等
工作原理
借助于NAT,私有(保留)地址的"内部"网络通过路由器发送数据包时,私有地址被转换成合法的IP地址,一个局域网只需使用少量IP地址(甚至是1个)即可实现私有地址网络内所有计算机与Internet的通信需求。
Nat-工作流程1
NAT将自动修改IP报文的源IP地址和目的IP地址,Ip地址校验则在NAT处理过程中自动完成。有些应用程序将源IP地址嵌入到IP报文的数据部分中,所以还需要同时对报文的数据部分进行修改,以匹配IP头中已经修改过的源IP地址。否则,在报文数据部分嵌入IP地址的应用程序就不能正常工作。
Java异常架构与异常关键字
Java异常简介
Java异常是Java提供的一种识别及响应错误的一致性机制。
Java异常机制可以使程序中异常处理代码和正常业务代码分离,保证程序代码更加优雅,并提高程序健壮性。在有效使用异常的情况下,异常能清晰的回答what, where, why这3个问题:异常类型回答了“什么”被抛出,异常堆栈跟踪回答了“在哪”抛出,异常信息回答了“为什么”会抛出。
非检查型异常:继承自RuntimeException或Error的是非检查型异常
检查型异常:继承Exception的则是检查型异常(编译时就可发现,需要try-catch或把异常交给上级方法处理,总之需要提前处理)
运行时异常:继承自RuntimeException的是运行时异常
spring声明式事务管理默认对非检查型异常和运行时异常进行事务回滚,而对检查型异常则不进行回滚操作。
1. Throwable
Throwable 是 Java 语言中所有错误与异常的超类。
Throwable 包含两个子类:Error(错误)和 Exception(异常),它们通常用于指示发生了异常情况。
Throwable 包含了其线程创建时线程执行堆栈的快照,它提供了 printStackTrace() 等接口用于获取堆栈跟踪数据等信息。
2. Error(错误)
定义:Error 类及其子类。程序中无法处理的错误,表示运行应用程序中出现了严重的错误。
特点:此类错误一般表示代码运行时 JVM 出现问题。通常有 Virtual MachineError(虚拟机运行错误)、NoClassDefFoundError(类定义错误)等。比如 OutOfMemoryError:内存不足错误;StackOverflowError:栈溢出错误。此类错误发生时,JVM 将终止线程。
这些错误是不受检异常,非代码性错误。因此,当此类错误发生时,应用程序不应该去处理此类错误。按照Java惯例,我们是不应该实现任何新的Error子类的!
3. Exception(异常)
程序本身可以捕获并且可以处理的异常。Exception 这种异常又分为两类:运行时异常和编译时(受查)异常。
运行时异常
定义:RuntimeException 类及其子类,表示 JVM 在运行期间可能出现的异常。
特点:Java 编译器不会检查它。也就是说,当程序中可能出现这类异常时,倘若既"没有通过throws声明抛出它",也"没有用try-catch语句捕获它",还是会编译通过。比如NullPointerException空指针异常、ArrayIndexOutBoundException数组下标越界异常、ClassCastException类型转换异常、ArithmeticExecption算术异常。此类异常属于不受检异常,一般是由程序逻辑错误引起的,在程序中可以选择捕获处理,也可以不处理。虽然 Java 编译器不会检查运行时异常,但是我们也可以通过 throws 进行声明抛出,也可以通过 try-catch 对它进行捕获处理。如果产生运行时异常,则需要通过修改代码来进行避免。例如,若会发生除数为零的情况,则需要通过代码避免该情况的发生!
RuntimeException 异常会由 Java 虚拟机自动抛出并自动捕获(就算我们没写异常捕获语句运行时也会抛出错误!!),此类异常的出现绝大数情况是代码本身有问题应该从逻辑上去解决并改进代码。
编译时异常
定义: Exception 中除 RuntimeException 及其子类之外的异常。
特点: Java 编译器会检查它。如果程序中出现此类异常,比如 ClassNotFoundException(没有找到指定的类异常),IOException(IO流异常),要么通过throws进行声明抛出,要么通过try-catch进行捕获处理,否则不能通过编译。在程序中,通常不会自定义该类异常,而是直接使用系统提供的异常类。该异常我们必须手动在代码里添加捕获语句来处理该异常。
4. 受检异常与非受检异常
Java 的所有异常可以分为受检异常(checked exception)和非受检异常(unchecked exception)。
受检异常
==编译器要求必须处理的异常。==正确的程序在运行过程中,经常容易出现的、符合预期的异常情况。一旦发生此类异常,就必须采用某种方式进行处理。除 RuntimeException 及其子类外,其他的 Exception 异常都属于受检异常。编译器会检查此类异常,也就是说当编译器检查到应用中的某处可能会此类异常时,将会提示你处理本异常——要么使用try-catch捕获,要么使用方法签名中用 throws 关键字抛出,否则编译不通过。
非受检异常
编译器不会进行检查并且不要求必须处理的异常,也就说当程序中出现此类异常时,即使我们没有try-catch捕获它,也没有使用throws抛出该异常,编译也会正常通过。该类异常包括运行时异常(RuntimeException及其子类)和错误(Error)。
声明异常
通常,应该捕获那些知道如何处理的异常,将不知道如何处理的异常继续传递下去。传递异常可以在方法签名处使用 throws 关键字声明可能会抛出的异常。
注意
- 非检查异常(Error、RuntimeException 或它们的子类)不可使用 throws 关键字来声明要抛出的异常。
- 一个方法出现编译时异常,就需要 try-catch/ throws 处理,否则会导致编译错误。
抛出异常
如果你觉得解决不了某些异常问题,且不需要调用者处理,那么你可以抛出异常。
throw关键字作用是在方法内部抛出一个Throwable
类型的异常。任何Java代码都可以通过throw语句抛出异常。
自定义异常
习惯上,定义一个异常类应包含两个构造函数,
一个无参构造函数和一个带有详细描述信息的构造函数(Throwable 的 toString 方法会打印这些详细信息,调试时很有用)
public class MyException extends Exception {
public MyException(){ }
public MyException(String msg){
super(msg);
}
// ...
}
若 finally 中也包含 return 语句,finally 中的 return 会覆盖前面的 return.
Java异常常见面试题
异常和错误的区别
Exception:
1.可以是可被控制也有不可控制;
2.表示一个由程序员导致的错误;
3.应该在应用程序级被处理;
Error:eg:java虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。
1.总是不可控制的;
2.经常用来表示系统错误或者底层资源错误;
3.如果可能的话,应该在系统级被捕捉;
补充:可控异常:NoSuchFieldException,IOException,(编译时就可发现,需要try-catch或把异常交给上级方法处理,总之需要提前处理)
运行时异常:例如除数为0(不能被编译器检测到)
Error 类型的错误通常为虚拟机相关错误,如系统崩溃,内存不足,堆栈溢出等,编译器不会对这类错误进行检测,JAVA 应用程序也不应对这类错误进行捕获,一旦这类错误发生,通常应用程序会被终止,仅靠应用程序本身无法恢复;
Exception 类的错误是可以在应用程序中进行捕获并处理的,通常遇到这种错误,应对其进行处理,使应用程序可以继续正常运行。
2. 运行时异常和一般异常(受检异常)区别是什么?
运行时异常包括 RuntimeException 类及其子类,表示 JVM 在运行期间可能出现的异常。 Java 编译器不会检查运行时异常。
受检异常是Exception 中除 RuntimeException 及其子类之外的异常。 Java 编译器会检查受检异常。
RuntimeException异常和受检异常之间的区别:是否强制要求调用者必须处理此异常,如果强制要求调用者必须进行处理,那么就使用受检异常,否则就选择非受检异常(RuntimeException)。一般来讲,如果没有特殊的要求,我们建议使用RuntimeException异常。
JVM 是如何处理异常的?
在一个方法中如果发生异常,这个方法会创建一个异常对象,并转交给 JVM,该异常对象包含异常名称,异常描述以及异常发生时应用程序的状态。创建异常对象并转交给 JVM 的过程称为抛出异常。可能有一系列的方法调用,最终才进入抛出异常的方法,这一系列方法调用的有序列表叫做调用栈。
JVM 会顺着调用栈去查找看是否有可以处理异常的代码,如果有,则调用异常处理代码。当 JVM 发现可以处理异常的代码时,会把发生的异常传递给它。如果 JVM 没有找到可以处理该异常的代码块,JVM 就会将该异常转交给默认的异常处理器(默认处理器为 JVM 的一部分),默认异常处理器打印出异常信息并终止应用程序。
throw 和 throws 的区别是什么?
Java 中的异常处理除了包括捕获异常和处理异常之外,还包括声明异常和拋出异常,可以通过 throws 关键字在方法上声明该方法要拋出的异常,或者在方法内部通过 throw 拋出异常对象。
throws 关键字和 throw 关键字在使用上的几点区别如下:
- throw 关键字用在方法内部,只能用于抛出一种异常,用来抛出方法或代码块中的异常,受查异常和非受查异常都可以被抛出。
- throws 关键字用在方法声明上,可以抛出多个异常,用来标识该方法可能抛出的异常列表。一个方法用 throws 标识了可能抛出的异常列表,调用该方法的方法中必须包含可处理异常的代码,否则也要在方法签名中用 throws 关键字声明相应的异常。
NoClassDefFoundError 和 ClassNotFoundException 区别?
NoClassDefFoundError 是一个 Error 类型的异常,是由 JVM 引起的,不应该尝试捕获这个异常。
引起该异常的原因是 JVM 或 ClassLoader 尝试加载某类时在内存中找不到该类的定义,该动作发生在运行期间,即编译时该类存在,但是在运行时却找不到了,可能是变异后被删除了等原因导致;
ClassNotFoundException 是一个受查异常,需要显式地使用 try-catch 对其进行捕获和处理,或在方法签名中用 throws 关键字进行声明。当使用 Class.forName, ClassLoader.loadClass 或 ClassLoader.findSystemClass 动态加载类到内存的时候,通过传入的类路径参数没有找到该类,就会抛出该异常;另一种抛出该异常的可能原因是某个类已经由一个类加载器加载至内存中,另一个加载器又尝试去加载它。
try-catch-finally 中哪个部分可以省略?
答:catch 可以省略
原因
更为严格的说法其实是:**try只适合处理运行时异常,try+catch适合处理运行时异常+普通异常。**也就是说,如果你只用try去处理普通异常却不加以catch处理,编译是通不过的,因为编译器硬性规定,普通异常如果选择捕获,则必须用catch显示声明以便进一步处理。而运行时异常在编译时没有如此规定,所以catch可以省略,你加上catch编译器也觉得无可厚非。
理论上,编译器看任何代码都不顺眼,都觉得可能有潜在的问题,所以你即使对所有代码加上try,代码在运行期时也只不过是在正常运行的基础上加一层皮。但是你一旦对一段代码加上try,就等于显示地承诺编译器,对这段代码可能抛出的异常进行捕获而非向上抛出处理。如果是普通异常,编译器要求必须用catch捕获以便进一步处理;如果运行时异常,捕获然后丢弃并且+finally扫尾处理,或者加上catch捕获以便进一步处理。
至于加上finally,则是在不管有没捕获异常,都要进行的“扫尾”处理。
常见的 RuntimeException 有哪些?
- ClassCastException(类转换异常)
- IndexOutOfBoundsException(数组越界)
- NullPointerException(空指针)
- ArrayStoreException(数据存储异常,操作数组时类型不一致)
- 还有IO操作的BufferOverflowException异常
Java常见异常有哪些
java.lang.IllegalAccessError:违法访问错误。当一个应用试图访问、修改某个类的域(Field)或者调用其方法,但是又违反域或方法的可见性声明,则抛出该异常。
java.lang.InstantiationError:实例化错误。当一个应用试图通过Java的new操作符构造一个抽象类或者接口时抛出该异常.
java.lang.OutOfMemoryError:内存不足错误。当可用内存不足以让Java虚拟机分配给一个对象时抛出该错误。
java.lang.StackOverflowError:堆栈溢出错误。当一个应用递归调用的层次太深而导致堆栈溢出或者陷入死循环时抛出该错误。
java.lang.ClassCastException:类造型异常。假设有类A和B(A不是B的父类或子类),O是A的实例,那么当强制将O构造为类B的实例时抛出该异常。该异常经常被称为强制类型转换异常。
java.lang.ClassNotFoundException:找不到类异常。当应用试图根据字符串形式的类名构造类,而在遍历CLASSPAH之后找不到对应名称的class文件时,抛出该异常。
java.lang.ArithmeticException:算术条件异常。譬如:整数除零等。
java.lang.ArrayIndexOutOfBoundsException:数组索引越界异常。当对数组的索引值为负数或大于等于数组大小时抛出。
java.lang.IndexOutOfBoundsException:索引越界异常。当访问某个序列的索引值小于0或大于等于序列大小时,抛出该异常。
java.lang.InstantiationException:实例化异常。当试图通过newInstance()方法创建某个类的实例,而该类是一个抽象类或接口时,抛出该异常。
java.lang.NoSuchFieldException:属性不存在异常。当访问某个类的不存在的属性时抛出该异常。
java.lang.NoSuchMethodException:方法不存在异常。当访问某个类的不存在的方法时抛出该异常。
java.lang.NullPointerException:空指针异常。当应用试图在要求使用对象的地方使用了null时,抛出该异常。譬如:调用null对象的实例方法、访问null对象的属性、计算null对象的长度、使用throw语句抛出null等等。
java.lang.NumberFormatException:数字格式异常。当试图将一个String转换为指定的数字类型,而该字符串确不满足数字类型要求的格式时,抛出该异常。
java.lang.StringIndexOutOfBoundsException:字符串索引越界异常。当使用索引值访问某个字符串中的字符,而该索引值小于0或大于等于序列大小时,抛出该异常。
String、StringBuffer、StringBuilder的区别
原文链接:https://blog.csdn.net/jin970505/article/details/79647406
6.String str = new String(“abc”)创建了多少个对象?
这个问题在很多书籍上都有说到比如《Java程序员面试宝典》,包括很多国内大公司笔试面试题都会遇到,大部分网上流传的以及一些面试书籍上都说是2个对象,这种说法是片面的。
如果有不懂得地方可以参考这篇帖子:
http://rednaxelafx.iteye.com/blog/774673/
首先必须弄清楚创建对象的含义,创建是什么时候创建的?这段代码在运行期间会创建2个对象么?毫无疑问不可能,用javap -c反编译即可得到JVM执行的字节码内容:
很显然,new只调用了一次,也就是说只创建了一个对象。
而这道题目让人混淆的地方就是这里,这段代码在运行期间确实只创建了一个对象,即在堆上创建了”abc”对象。而为什么大家都在说是2个对象呢,这里面要澄清一个概念 该段代码执行过程和类的加载过程是有区别的。在类加载的过程中,确实在运行时常量池中创建了一个”abc”对象,而在代码执行过程中确实只创建了一个String对象。
因此,这个问题如果换成 String str = new String(“abc”)涉及到几个String对象?合理的解释是2个。
个人觉得在面试的时候如果遇到这个问题,可以向面试官询问清楚”是这段代码执行过程中创建了多少个对象还是涉及到多少个对象“再根据具体的来进行回答。
String是不可变长度的,StringBuffer和StringBuilder是可变长度的,但StringBuffer是线程安全的,StringBuilder是线程不安全但执行比StringBuffe快
一、Java String 类——String字符串常量
字符串广泛应用 在Java 编程中,在 Java 中字符串属于对****象,Java 提供了 String 类来创建和操作字符串。
需要注意的是,String的值是不可变的,这就导致每次对String的操作都会生成新的String对象,这样不仅效率低下,而且大量浪费有限的内存空间。我们来看一下这张对String操作时内存变化的图:
二、Java StringBuffer 和 StringBuilder 类——StringBuffer字符串变量、StringBuilder字符串变量
当对字符串进行修改的时候,需要使用 StringBuffer 和 StringBuilder 类。
和 String 类不同的是,StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且==不产生新的未使用对象。==
StringBuffer 类在 Java 5 中被提出,它和 StringBuffer 之间的最大不同在于 StringBuilder 的方法不是线程安全的(不能同步访问)。
由于 StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下建议使用 StringBuilder 类。然而在应用程序要求线程安全的情况下,则必须使用 StringBuffer 类。
三者的继承结构
三者的区别:
(1)字符修改上的区别(主要,见上面分析)
(2)初始化上的区别,String可以空赋值,后者不行,报错
①String
String s = null;
String s = “abc”;
②StringBuffer
StringBuffer s = null; //结果警告:Null pointer access: The variable result can only be null at this location
StringBuffer s = new StringBuffer();//StringBuffer对象是一个空的对象
StringBuffer s = new StringBuffer(“abc”);//创建带有内容的StringBuffer对象,对象的内容就是字符串”
如果要操作少量的数据用 String;
(2)多线程操作字符串缓冲区下操作大量数据 StringBuffer;
(3)单线程操作字符串缓冲区下操作大量数据 StringBuilder。
String的equals
第一步
判断两个String地址是否相同,相同则返回true
第二步
依次进行三个判断,有一个不符合就false
判断要比较的字符串(equals的方法参数)是否为字符串
比较两个字符串长度,不相等返回false
则依次比较两个字符串中的字符,若有一个不相等,就返回false
String的常用方法
1、int length(); 语法:字符串变量名.length(); 返回值为 int 类型。得到一个字符串的字符个数(中、英、空格、转义字符皆为字符,计入长度)
2、char charAt(值); 语法 :字符串名.charAt(值); 返回值为 char 类型。从字符串中取出指定位置的字符
3、char toCharArray(); 语法 :字符串名.toCharArray(); 返回值为 char 数组类型。将字符串变成一个字符数组
4、int indexOf(“字符”) 语法 :字符串名.indexOf(“字符”);字符串名.indexOf(“字符”,值);查找一个指定的字符串是否存在,返回的是字符串的位置,如果不存在,则返回-1 。
5、 in lastIndexOf(“字符”) 得到指定内容最后一次出现的下标
6、toUpperCase(); toLowerCase();字符串大小写的转换
7、String[] split(“字符”) 根据给定的正则表达式的匹配来拆分此字符串。形成一个新的String数组。
8、boolean equals(Object anObject) 语法 :字符串变量名.wquals(字符串变量名); 返回值为布尔类型。所以这里用 if 演示。比较两个字符串是否相等,返回布尔值
9、trim(); 去掉字符串左右空格
10、String substring(int beginIndex,int endIndex) 截取字符串 不包括最后endIndex
11、boolean equalsIgnoreCase(String) 忽略大小写的比较两个字符串的值是否一模一样,返回一个布尔值
12、boolean contains(String) 判断一个字符串里面是否包含指定的内容,返回一个布尔值
13、boolean startsWith(String) 测试此字符串是否以指定的前缀开始。返回一个布尔值
14、boolean endsWith(String) 测试此字符串是否以指定的后缀结束。返回一个布尔值
15、String replaceAll(String,String) 将某个内容全部替换成指定内容,
16、String repalceFirst(String,String) 将第一次出现的某个内容替换成指定的内容
17、**replace(char oldChar,char newChar);**新字符替换旧字符,也可以达到去空格的效果一种。
如何反转字符串
①利用 StringBuffer 或 StringBuilder 的 reverse 成员方法:
public static void main(String[] args) {
String s = "abc123";
String reverse =new StringBuilder(s).reverse().toString();
System.out.println(reverse);
}
②利用 String 的 toCharArray 方法先将字符串转化为 char 类型数组,然后将各个字符进行重新拼接:
public static void main(String[] args) {
String s = "abc123";
char[] chars = s.toCharArray();
String reverse = "";
for (int i = chars.length - 1; i >= 0; i--) {
reverse += chars[i];
}
System.out.println(reverse);
}
③利用 String 的 CharAt 方法取出字符串中的各个字符:
public static void main(String[] args) {
String s = "abc123";
String reverse = "";
int length = s.length();
for (int i = 0; i < length; i++) {
reverse = s.charAt(i) + reverse;
}
System.out.println(reverse);
}
计算一个字符串中每一个字符出现的次数
分析:
1.使用Scanner获取用户输入的字符串
2.创建Map集合,key是字符串中的字符,value是字符的个数
3.遍历字符串,获取每一个字符
4.使用获取到的字符,去Map集合判断key是否存在
public class CaiNiao{
public static void main(String[] args){
//1.使用Scanner获取用户输入的字符串
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个字符串:");
String str = sc.next();
//2.创建Map集合,key是字符串中的字符,value是字符的个数
HashMap<Character.Integer> map = new HashMap<>();
//3.遍历字符串,获取每一个字符
for(char c : str.toCharArray()){
//4.使用获取到的字符,去Map集合判断key是否存在
if(map.containsKey(c)){
//key存在
Integer value = map.get(c);
value++;
map.put(c,value);
}else{
//key不存在
map.put(c,1);
}
}
//5.遍历Map集合,输出结果
for(Character key : map.keySet(){
Integer value = map.get(key);
System.out.println(key+"="+value);
}
}
}
实例化对象的方式
1.使用new关键字
2.使用Class类的newInstance方法
3.使用Constructor类的newInstance方法
4.使用clone方法
5.使用反序列化
Java的内存模型
虽然java程序所有的运行都是在虚拟机中,涉及到的内存等信息都是虚拟机的一部分,但实际也是物理机的,只不过是虚拟机作为最外层的容器统一做了处理。虚拟机的内存模型,以及多线程的场景下与物理机的情况是很相似的,可以类比参考。
Java内存模型的主要目标是定义程序中变量的访问规则。即在虚拟机中将变量存储到主内存或者将变量从主内存取出这样的底层细节。需要注意的是这里的变量跟我们写java程序中的变量不是完全等同的。这里的变量是指实例字段,静态字段,构成数组对象的元素,但是不包括局部变量和方法参数(因为这是线程私有的)。这里可以简单的认为主内存是java虚拟机内存区域中的堆,局部变量和方法参数是在虚拟机栈中定义的。但是在堆中的变量如果在多线程中都使用,就涉及到了堆和不同虚拟机栈中变量的值的一致性问题了。
Java内存模型中涉及到的概念有:
- 主内存:java虚拟机规定所有的变量(不是程序中的变量)都必须在主内存中产生,为了方便理解,可以认为是堆区。可以与前面说的物理机的主内存相比,只不过物理机的主内存是整个机器的内存,而虚拟机的主内存是虚拟机内存中的一部分。
- 工作内存:java虚拟机中每个线程都有自己的工作内存,该内存是线程私有的为了方便理解,可以认为是虚拟机栈。可以与前面说的高速缓存相比。线程的工作内存保存了线程需要的变量在主内存中的副本。虚拟机规定,线程对主内存变量的修改必须在线程的工作内存中进行,不能直接读写主内存中的变量。不同的线程之间也不能相互访问对方的工作内存。如果线程之间需要传递变量的值,必须通过主内存来作为中介进行传递。
*这里需要说明一下:主内存、工作内存与java内存区域中的java堆、虚拟机栈、方法区并不是一个层次的内存划分。这两者是基本上是没有关系的,上文只是为了便于理解,做的类比*
Java常见的IO
什么是内部类?内部类的种类?内部类的作用?
一、什么是内部类
定义: 将一个类定义在一个类或者一个方法里面,这样的类称着内部类
二、内部类的种类
内部类的种类有4种:
1、成员内部类
成员内部类是最普通的一种内部类,成员内部类可以访问外部类所有的属性和方法。但是外部类要访问成员内部类的属性和方法,必须要先实例化成员内部类。
注意: 成员内部类里面不能包含静态的属性和方法
public class OutClass {
public void test1() {
}
private void test2() {
}
private static void test3() {
}
class InnerClass {//成员内部类
private String testStrInner = "";
private void testInner() {
test1();
test2();
test3();//成员内部类可以访问外部类所有的属性和方法。静态方法直接访问。
}
}
}
2、静态内部类
静态内部类就是在成员内部类多加了一个 static 关键字。静态内部类只能访问外部类的静态成员变量和方法(包括私有静态)
public class OutClass {
private static String s = "";
public void test1() {
}
private void test2() {
}
private static void test3() {
}
static class InnerClass {//静态内部类
private static String testStrInner = "";
private static void testInner() {
test3();
String ss = s;
}
}
}
3、匿名内部类
匿名内部类:顾名思义就是没有名字的类。什么时候用到匿名内部类呢?在Android中最常见得回调监听事件 就是匿名内部类
一般遇到下面这种情况都会考虑到使用匿名内部类:
当一个内部类需要继承或者实现,而且只使用一次的时候,可以考虑使用匿名内部类。调用的时候直接使用父类的无参构造,并重写父类方法。如下代码:
//父类 Animal
public class Animal {
public void bellow() {//动物吼叫的类型
System.out.println("动物吼叫");
}
}
如果现在只是需要一个 狗(Dog)的吼叫类型。一般我们会写一个Dog类继承Animal ;然后重写
bellow()方法;最后实例化Dog类,调用bellow()方法。但是此时我们可以使用内部类,因为只用一次,没有其他地方调用,没必要再去写一个class类。代码如下
class Demo {
public static void main(String[] args) {
Demo demo = new Demo();
demo.getDogBellow(new Animal(){//匿名内部类,重写父类方法。当然接口也是一样
@Override
public void bellow() {
System.out.println("狗 汪汪汪。。。。");
}
});
}
public void getDogBellow(Animal animal){
animal.bellow();
}
}
4、局部内部类
局部内部类就是定义在代码块内的一个内部类。比如在方法里面定义一个内部类,就是局部内部类。
局部内部类的作用范围仅仅就在它所在的代码块里。局部内部类不能被public ,protected,private以及static修饰,但是可以被final修饰。
public class Animal {
public static void bellow() {
String bellowStr = "动物吼叫";
System.out.println(bellowStr);
final class Dog {//局部内部类
String dogBellowStr = bellowStr + ";狗 :汪汪汪";
public void dogBellow() {
System.out.println(dogBellowStr);
}
}
}
}
三、内部类的作用
- 1、内部类可以很好的实现隐藏。
非内部类是不可以使用 private和 protected修饰的,但是内部类却可以,从而达到隐藏的作用。同时也可以将一定逻辑关系的类组织在一起,增强可读性。 - 2、间接的实现多继承。
每个内部类都能独立地继承自一个(接口的)实现,所以无论外部类是否已经继承了某个(接口的)实现,对于内部类都没有影响。如果没有内部类提供的可以继承多个具体的或抽象的类的能力,一些设计与编程问题就很难解决。所以说内部类间接的实现了多继承。
泛型
https://segmentfault.com/a/1190000014120746
cookie 和 session
4.cookie的简介:
会话是由一组请求与响应组成,是围绕着一件相关的事情所进行的请求与响应。所以这些请求与响应之间一定是需要有数据传递的,即是需要进行会话状态跟踪的。然而HTTP协议是一种无状态协议,在不同的请求间是无法进行数据传递的。此时就需要一种可以进行请求间数据传递的会话跟踪技术。而cookie就是一种这样的技术。
Cookie是由服务器生成,保存在客户端的一种信息载体。这个载体中存放着用户访问该站点的会话状态信息。只要Cookie没有被清空,或都Cookie没有失效,那么,保存在其中的会话状态就有效。
用户在提交第一次请求后,由服务器生成Cookie,并将其封装到响应头中,以响应头的形式发送给客户端。客户端接收到这个响应后,将Cookie保存到客户端。当客户端再次发送同类请求(资源路径相同的请求)后,在请求中会携带保存在客户端的Cookie数据,发送到服务器,由服务器对会话进行跟踪。
5.不同的浏览器,其Cookie的保存位置及查看方式是不同的。因此删除了某一浏览器下的Cookie,不会影响到其他浏览器中的Cookie
在火狐
6.Cookie相当于键值对,有name和Cookie
7.Cookie存在于浏览器的缓存中
8.设置Cookie的有效期。这个值为一个整型值,单位为秒。
该值大于0,表示将Cookie存放到客户端的硬盘
该值小于0,与不设置效果相同,会将Cookie存放到浏览器的缓存
该值等于0,表示Cookie一生成,马上失效
9.Cookie的禁用:
浏览器是可以禁用Cookie的。所谓禁用Cookie是指客户端浏览器不接收服务器发送来的Cookie。不过,现在的很多网站,若浏览器禁用了Cookie,则将无法使用
Session
1.session的定义:
Session即会话,是web开发中的一种会话状态跟踪技术。但是Cookie 也是一种会话跟踪技术。不同的是Cookie是将会话状态保存在了客户端,而session则是将会话状态保存在了服务器端。
2.会话:当用户打开浏览器,从发出第一次请求开始,一直到最终关闭浏览器,就表示第一次会话的完成
3.Session并不是JavaWeb开发所特有的,而是整个Web开发中所实用的技术。在JavaWeb开发中,Session是以javax.servlet.http.HttpSession的接口对象的形式出出现的
4.request.getSession():如果当前请求有Session,就返回当前的Session,如果
当前的请求没有Session,就创建一个Session
5.request域:在同一请求下,可以接受数据传递。如果跨的是两个Servlet ,一个Servlet到另一个Servlet是通过请求转发过来的。在一个Servlet的request域中放入属性值,在另一个Servlet中就能读取到。Session是跨请求的,只要是在同一次会话里面,在一个请求里面放入Session属性,在另一个请求里面也能获取到,可以实现跨请求的数据传递。
public void setAttribute(String name,Object value)
该方法用于向Session的域属性空间中存入指定名称,指定值的域属性
•public Object getAttribute(String name)
该方法用于从Session的域属性空间中读取指定名称为域属性值
•public void removeAttribute(String name)
该方法用于从Session的域属性空间中删除指定名称的域属性
\6. 通过地址栏访问,使用doGet():就是在地址中直接改变Servlet的url
7.对于request的getSession()的用法:
一般情况下,若要向Session中写入数据,则需使用getSession(true),即getSession()方法。意义是:有旧的Session就用旧的Session,没有旧的Session就创建一个新的Session
若要从Session中读取数据,则需要使用getSession(false),意义是;有旧的Session就使用旧的Session,没有旧的就返回null。因为要读取数据,只有旧的Session中才有可能存在你要查找的数据。
8.Session的工作原理(面试)
在服务器中系统会为每个会话维护一个Session(创建一个Session对象)。不同的会话,对应不同的Session.
(1)Session列表:
服务器对当前应用中的Session是以Map的形式进行管理的,这个Map称为Session列表。该Map的key为一个32位长度的随机串,这个随机串称为JSessionID,value则为Session对象的引用。
当用户第一次提交请求的时候,服务端Servlet中执行到request.getSession()方法后,会自动生成一个Map.Entry(键值对)对象,key为一个根据某种算法新生成的JSessionID,value为新创建的HttpSession对象。
(2)服务器生成并发送Cookie
在将Session写入Session列表中,系统还会自动将“JSESSIONID”作为name,这个32位长度的随机串作为value,以Cookie的形式存放到响应报头中,并随着响应,将该Cookie发送到客户端
(3)客户端接收到这个Cookie后,将其存放到浏览器的缓存中。即,只要客户端浏览器不关闭,浏览器缓存中的Cookie就不会消失。
当用户提交第二次请求时,会将缓存中的这个Cookie,伴随着请求的头部信息,一块发送到服务端。
(4)从Session列表中查找
服务端从请求中读取到客户端发送来的Cookie,并根据Cookie的JSsessionid的值,从Map中查找响应key所对应的value,即session对象。然后,对该Session对象的域属性进行读写操作
总结;当发出第一次请求的时候,服务器会生成一个32位的随机串,再创建一个Session对象 ,然后往Session列表中中放属性,再把32位的随机串包装成一个Cookie发送给客户端浏览器,此Cookie的name叫做JSessionID,value就是这个32位长度的随机字符串,客户端再把这个Cookie存放到浏览器缓存,然后客户端又发出一次请求,把 JSessionID放到请求的头部信息中,发送给服务器。服务器接受到Cookie以后,会拿到JSessionID的值,即32位长度的串,服务器接收到这个串之后从Session列表中寻找,找到key就是找到key所对应的value,即存放域属性的Session
9.Session的失效:
web开发中引入的Session超时的概念,Session的失效就是指Session的超时。若某个Session在指定的时间范围内一直未被访问,那么Session将超时,即将失效。
在web.xml中可以通过标签设置Session的超时时间,单位为分钟。默认Session的超时时间为30分钟。需要再次强调的是,这个时间并不是从Session被创建开始计时的生命周期时长,而是从最后一次被访问开始计时,在指定的时长内一直未被访问的时长。
若未到超时时限,也可以通过代码提前使Session失效。
HttpSession中的方法Invalide(),使得Session失效
httpSession.invalidate()使Session失效,解绑所有绑定在Session上面的对象
10.浏览器据I化的结束就是Session的失效
11.超链接的URL重写
12.用户登录之后都会把用户登录的信息保存在Session域中。所以要检查用户是否登陆,可以判断Session中是否包含有用户登录的信息
13.使用Filter拦截器,要在web工程下,有一个admin
ClassLoader的重要方法
https://blog.csdn.net/qq1003400592/article/details/80808232
java异常
https://blog.csdn.net/dnxyhwx/article/details/6975087?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-2.control&dist_request_id=&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-2.control
Java基础之—反射(非常重要)
https://blog.csdn.net/sinat_38259539/article/details/71799078
爬虫IP被封的6个解决办法
https://blog.csdn.net/myli_binbin/article/details/99694563
深入 java debug 原理及远程remote调试详解
https://blog.csdn.net/ywlmsm1224811/article/details/98611454?utm_medium=distribute.pc_relevant_bbs_down.none-task–2allfirst_rank_v2rank_v29-1.nonecase&depth_1-utm_source=distribute.pc_relevant_bbs_down.none-task–2allfirst_rank_v2rank_v29-1.nonecase
当我们在 idea 或者 eclipse 中以 debug 模式启动运行类,就可以直接调试了,这其中的原理令人不解,下面就给大家介绍一下
客户端(idea 、eclipse 等)之所以可以进行调试,是由于客户端 和 服务端(程序端)进行了 socket 通信,通信过程如下:
1、先建立起了 socket 连接
2、将断点位置创建了断点事件通过 JDI 接口传给了 服务端(程序端)的 VM,VM 调用 suspend 将 VM 挂起
3、VM 挂起之后将客户端需要获取的 VM 信息返回给客户端,返回之后 VM resume 恢复其运行状态
4、客户端获取到 VM 返回的信息之后可以通过不同的方式展示给客户
Java编程:String 类中 hashCode() 方法详解
https://zhibo.blog.csdn.net/article/details/53770830?utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control
成员变量和局部变量的区别(理解)
String中的hashCode方法
https://blog.csdn.net/u012501054/article/details/88863045
那为什么这里用31,而不是其它数呢?《Effective Java》是这样说的:之所以选择31,是因为它是个奇素数,如果乘数是偶数,并且乘法溢出的话,信息就会丢失,因为与2相乘等价于移位运算。使用素数的好处并不是很明显,但是习惯上都使用素数来计算散列结果。31有个很好的特性,就是用移位和减法来代替乘法,可以得到更好的性能:31*i==(i<<5)-i。现在的JVM可以自动完成这种优化。
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
IDEA打包可执行jar
https://cloud.tencent.com/developer/article/1764737
模拟实战排查堆内存溢出(java.lang.OutOfMemoryError: Java heap space)问题
JAVA注解
> 面试问的不多,但是在使用框架开发时会经常使用,但东西太多了,这里只是简单介绍下概念。
Annotation
注解可以看成是java中的一种标记记号,用来给java中的类,成员,方法,参数等任何程序元素添加一些额外的说明信息,同时不改变程序语义。注解可以分为三类:基本注解,元注解,自定义注解
- 标准注解
- @Deprecated:该注解用来说明程序中的某个元素(类,方法,成员变量等)已经不再使用,如果使用的话的编译器会给出警告。
- @SuppressWarnings(value=“”):用来抑制各种可能出现的警告。
- @Override:用来说明子类方法覆盖了父类的方法,保护覆盖方法的正确使用
- 元注解(元注解也称为元数据注解,是对注解进行标注的注解,元注解更像是一种对注解的规范说明,用来对定义的注解进行行为的限定。例如说明注解的生存周期,注解的作用范围等)
- @Target(value=“ ”):该注解是用来限制注解的使用范围的,即该注解可以用于哪些程序元素。
- @Retention(value=“ ”):用于说明注解的生存周期
- @Documnent:用来说明指定被修饰的注解可以被javadoc.exe工具提取进入文档中,所有使用了该注解进行标注的类在生成API文档时都在包含该注解的说明。
- @Inherited:用来说明使用了该注解的父类,其子类会自动继承该注解。
- @Repeatable:java1.8新出的元注解,如果需要在给程序元素使用相同类型的注解,则需将该注解标注上。
- 自定义注解:用@Interface来声明注解。
网络攻击
01SSRF概念 SSRF为服务端请求伪造(Server-Side Request Forgery),指的是攻击者在未能取得服务器所有权限时。
利用服务器漏洞以服务器的身份发送一条构造好的请求给服务器所在内网。SSRF攻击通常针对外部网络无法直接访问的内部系统。
02SSRF的原理
很多web应用都提供了从其他的服务器上获取数据的功能。使用指定的URL,web应用便可以获取图片,下载文件,读取文件内容等。SSRF的实质是利用存在缺陷的web应用作为代理攻击远程和本地的服务器。一般情况下, SSRF攻击的目标是外网无法访问的内部系统,黑客可以利用SSRF漏洞获取内部系统的一些信息(正是因为它是由服务端发起的,所以它能够请求到与它相连而与外网隔离的内部系统)。SSRF形成的原因大都是由于服务端提供了从其他服务器应用获取数据的功能且没有对目标地址做过滤与限制。
03SSRF的主要攻击方式
攻击者想要访问主机B上的服务,但是由于存在***或者主机B是属于内网主机等原因导致攻击者无法直接访问主机B。而服务器A存在SSRF
漏洞,这时攻击者可以借助服务器A来发起SSRF攻击,通过服务器A向主机B发起请求,从而获取主机B的一些信息。
csrf
跨站请求伪造(英语:Cross-site request forgery),也被称为 one-click attack 或者 session riding,通常缩写为 CSRF
或者 XSRF, 是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。跟跨网站脚本(XSS)相比,
XSS 利用的是用户对指定网站的信任,CSRF 利用的是网站对用户网页浏览器的信任。
几种IO模型
https://cloud.tencent.com/developer/article/1684951