Java2019秋招面试整理

19 篇文章 0 订阅
12 篇文章 0 订阅

文章目录

JavaSE

Jre 和 Jdk 的区别?

JRE:(Java Runtime Environment),Java运行时环境(JRE)。它包括Java虚拟机、Java核心类库和支持文件。
JDK:(Java Development Kit Java)开发工具包。Java开发工具包(JDK)是完整的Java软件开发包,包含了JRE,编译器和其他的工具(比如:JavaDoc,Java调试器),可以让开发者开发、编译、执行Java应用程序
简单而言:使用 JDK 开发完成的 java 程序,交给 JRE 去运行。

java 虚拟机 JVM

Java Virtual Machine ,简称 JVM;
它是运行所有 Java 程序的抽象计算机,是 Java 语言的运行环境,它是 Java 最具吸引力的特性之一,JVM 读取并处理编译过的与平台无关的字节码(class)文件。
Java 编译器针对 JVM 产生 class 文件,因此是独立于平台的。
Java 解释器负责将 JVM 的代码在特定的平台上运行。 Java 虚拟机是不跨平台的.
在这里插入图片描述

JVM体系结构:

类装载子系统 、执行引擎子系统 、本地方法接口 、运行时数据区
运行数据区:JVM的内存,包括堆,栈,方法区,程序计数器,本地方法栈

Java字节码是在JRE中执行(JRE:Java运行环境) JVM是JRE中的核心组成部分,承担分析和执行字节码的工作。
JVM通过类加载器加载Java应用,并通过Java API进行执行

GC垃圾回收站机制

主要作用是回收程序中不被使用的内存。在使用C/C++语言进行程序开发时,开发人员必须非常仔细地管理好内存的分配和释放,如果忘记或错误地释放内存往往会导致陈序运行不正常甚至程序崩溃。为了减少开发人员的工作,也为了提高系统的安全性和稳定性,Java语言提供了垃圾回收器来自动检测对象的作用域,可自动地把不被使用的存储空间释放掉。

具体而言,垃圾回收器要负责完成三个任务:分配内存,确保被引用对象的内存不被错误地回收以及回收不被使用的对象的内存空间

Java中的队和栈

基本数据类型的变量以及对象的引用变量其内存都分配在栈上,变量除了作用域就会自动释放

而引用类型的变量,其内存分配在堆上或者常量池(例如字符创长亮和基本数据类型常量)中,需要通过new等方式进行创建

堆内存是用来存放运行时创建的对象,一般来说,通过new关键字创建的对象都存放在堆内存中,

Java 程序运行机制

编译: javac 文件名.文件后缀名运行: java 类名
Java 程序的组成:Java 源文件,字节码文件。
源文件由编译器编译成字节码(.class)---->字节码由java虚拟机解释运行(.java)。

Java 成员变量和局部变量

局部变量:不是声明在类体括号里面的变量;局部变量使用前必须初始化值; 局部变量没有默认初始化值; 局部变量的作用域是从定义开始到定义它的代码块结束;
成员变量:在方法体外,类体内声明的变量,又称字段(Field)或全局变量;(其实 Java 中没有全局变量,由于 Java 是面向对象语言,所有变量都是类成员)成员变量的作用域是整个类中; 我的总结:注意成员变量和局部变量的区别

局部变量是定义在方法中的变量,出了该方法就不能访问该变量了… 成员变量是在类中定义,并且在类的成员方法中都能访问的变量

变量的初始化

在类中定义的成员变量如果你没有初始化java会自动帮你初始化,如果是数字会自动初始化成0,字符会初始化成’o’,对象引用会初始化成null.

如果你定义的是局部变量就必须初始化了,否则编译会报错

引用数据类型

类(class)
接口(interface)
数组
枚举(enum)
注解(Annotation)

基本数据类型

byte:1字节 -2的8----2的8次方-1次方 默认值0
boolean:1字节 false true 默认值false
short:2字节 -2的16次方----2的十六次方 默认值0
char:2字节 ‘\u0000’~’\uffff’(16进制数) 默认值’\u0000’
int:4字节 -2的32次方—2的32次方 默认值0
float:4字节 -3.4E38—3.4E38 默认值0.0f
long:8字节 -2的64次方—2的64次方-1 默认值0L
double:8字节 -1.7E308—1.7E308 默认值0.0d

基本数据类型转换之向上转型和向下转换

向上转换:整型,字符型,浮点型的数据在混合运算中相互转换,转换时遵循以下原则:容量小的类型可自动转换为容量大的数据类型;
byte,short,char → int → long → float → double
byte,short,char 之间不会相互转换,他们在计算时首先会转换为 int 类型。 boolean 类型是不可以转换为其他基本数据类型。

向下转换:整型,字符型,浮点型的数据在混合运算中相互转换,转换时遵循以下原则:容量大的类型可强制类型转换为容量小的数据类型;
double → float → long → int → byte,short,char
byte,short,char 之间不会相互转换,他们在计算时首先会转换为 int 类型。 boolean 类型是不可以转换为其他基本数据类型。

类型转化小转大,自动!自动类型转换(也叫隐式类型转换)
类型转化大转小,强转!强制类型转换(也叫显式类型转换)

方法的重载(Overload)

概念:在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型或参数顺序不同即可。
存在的原因:
屏蔽了一个对象的同一类方法由于参数不同所造成的差异。

操作数组的工具类 Arrays

使用数组工具类可以节省时间,提高效率

什么叫面向对象?

面向对象(Object-Oriented,简称 OOP)就是一种常见的程序结构设计方法。
面向对象思想的基础是将相关的数据和方法放在一起,组合成一种新的复合数据类型,然后使用新创建的复合数据类型作为项目的基础。

面向对象是一个很抽象的概念,它相对面向过程而言。过程与对象都是一种解决问题的思想。面向过程:强调的是功能的行为,一种过程,先干啥,再干啥; 面向对象:将功能封装到对象里,强调的是具备某功能的对象; 按照面向对象的思想,可以把任何的东西看做对象!

面向过程:强调的是具体的功能实现;(执行者)
面向对象:强调的是具备功能的对象。(管理者)

类是一组事物共有特征和功能的描述。类是对于一组事物的总体描述,是按照面向对象技术进行设计时最小的单位,也是组成项目的最基本的模块。
类是抽象的,对象是具体的,实实在在的。

定义类,其实就是定义类里面的对象
对象包含:状态(属性)、功能、行为(方法) 通过类来描述对象;
状态--------成员变量;功能、行为——方法;

构造方法

构造方法:用来构造类的实例(每一个类都默认有一个无参的构造方法,得使用 new 调用)
字段:类或对象所包含的数据,对类状态的一种描述;
方法:类或对象的特征或行为
作用:给类中的字段进行初始化,可以用来创建对象。
特点:方法名与类名相同不用定义返回值类型不需要写 return 语句
我的总结:注意:默认构造方法的特点无参的构造函数。多个构造方法是以重载的形式存在的。
构造方法的重载:(需要哪个就去适配哪个,调用哪个)

可以private修饰构造方法

static关键字

随着类的加载而加载优先于对象存在被所有对象所共享可以直接被类名调用
1.静态属性
static修饰类的成员和方法后,成为类成员和类方法,该类所有实例的共享,不依赖于某个对象而存在,实际上就是一个共享的变量,被多个对象共享,可以通过类名直接调用,只能修饰成员变量
2.静态方法中只能访问静态变量和局部变量,不能访问类的属性,不能被覆盖
3.静态块初始化
static{}
先执行静态块或静态属性的初始化,然后执行实例化对象。
被static修饰的变量称为静态变量,静态变量属于整个类,而局部变量属于方法,只在该方法内有效,所以static不能修饰局部变量

static块保证执行一次:
类加载时执行,类是由类加载器读取,类加载器带有一个缓冲区把读取到的static缓存起来,在虚拟机运行期间,一个类只能被加载一次

4.声明内部类
只能修饰内部类
静态方法中不可以使用 this,super 关键字主方法

静态变量时属于类的,一个类只有一份,设计模式这两个的单例模式就是通过静态实现的,静态方法没有this。
静态的引入会对类的构造顺序造成影响,,当第一次使用某个类时,会先初始化类的静态变量然后执行静态初始化块,之后才按照类的构造顺序进行构造,静态的数据只构造一次

this 关键字

this 表示当前对象。谁调用了方法,谁就是当前对象。
构造器中相互调用,但是此时 this([参数])必须写在构造方法第一行。

类和类成员访问权限

类访问权限:public,default 被任何包的类访问;本包类访问
类成员访问权限:

成员修饰符类自己相同包不同包的子类任意类
publicYYYY
protectedYYYN
defaultYYNN
privateYNNN

封装

将类的某些信息隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的方法来实现对隐藏信息的操作和访问。

把描述对象的状态用字段表示,描述对象的行为用方法表示,把字段和方法定义在一个类中,并保证外界不能任意更改其内部的字段值,也不允许任意调动其内部的功能方法。

优点:只能通过规定的方法访问数据。 隐藏类的实例细节,方便修改和实现。
把数据项和方法隐藏在对象的内部,把方法的实现内容隐藏起来。

Java提供构造方法,析构方法(与构造函数相反),方法重载,设置访问控制权限等措施对类进行封装

继承

继承是类与类的一种关系,子类拥有父类的所有属性和方法(除了private修饰的属性不能拥有)从而实现了实现代码的复用。由继承派生类
方法的重写(全部重写为覆盖):重新实现基类中的方法。
继承的初始化顺序:
1、初始化父类再初始化子类
2、先执行初始化对象中属性,再执行构造方法中的初始化。

特点:
1、提高了代码的复用性。
2、让类与类之间产生关系,有了这个继承关系才有了多态的特性。
3、Java 语言中只支持单继承(有别于 C 语言)。因为多继承容易带来安全隐患(父类多了,功能相同的话,就会出现调用不确定性吗,覆写一个方法,到底覆写的谁的?)。
ps:接口可以实现多继承
4、Java 支持多层继承,object 是每个类的超类,实现树形结构。

super 关键字和调用父类构造方法

super表示父类对象的默认引用
如果子类要调用父类被覆盖的实例方法,可用 super 作为调用者调用父类被覆盖的实例方法。

使用 super 调用父类方法
使用 super 调用父类的构造方法调用构造方法

本类中调用另一个重载构造方法用 this(参数列表)
子类构造方法调用父类构造方法用 super(参数列表)

子类调用父类的构造方法时:super 必须放在第一句

Java 在执行子类的构造方法前会先调用父类无参的构造方法,其目的是为了对继承自父类的成员做初始化操作。子类在创建对象的时候,默认调用父类的无参构造方法,要是子类构造方法中显示指定调用父类其他构造方法,就调用指定的父类构造方法,取消调用父类无参构造方法。

多态

指同一个实体同时具有多种形式

编译时的类型由声名该变量时使用的类型决定,运行时的类型由实际赋给变量的对象决定。如果编译时类型和运行时类型不同,就出现多态。

父类的引用变量可以指向子类的实例对象,而程序调用的方法在运行期才动态绑定,就是引用变量所指向的是真正实例对象的方法,也就是内存里正在运行的那个对象的方法,而不是引用变量的类型中定义的方法。

1.引用多态   
父类的引用可以指向本类的对象;编译时多态
父类的引用可以指向子类的对象;运行时多态
2.方法多态
根据上述创建的两个对象:本类对象和子类对象,同样都是父类的引用,当我们指向不同的对象时,它们调用的方法也是多态的。
创建本类对象时,调用的方法为本类方法;
创建子类对象时,调用的方法为子类重写的方法或者继承的方法;
使用多态的时候要注意:如果我们在子类中编写一个独有的方法(没有继承父类的方法),此时就不能通过父类的引用创建的子类对象来调用该方法!!!
注意: 继承是多态的基础。
Java中使用重载和重写机制实现多态。

引用变量类型转换

向上转型(子类→父类):(自动完成) 父类名称 父类对象 = 子类实例 ;
向下转型(父类→子类):(强制完成)
子类名称 子类对象 = (子类名称)父类实例 ;
对象名 instanceof 类
判断指定的变量名此时引用的真正类型是不是当前给出的类或子类;

基本类型的包装类

为实现基本数据类型到面对对象的转化,Java提供了每个基本数据类型提供了对应的包装类

字符串实际上是String类的实例对象,是引用类型,String类是final修饰的类,三种创建方式
A,java中的字符串存储在字符串常量区,不会改变,发生改变是会新创建一个对象
B,StringBuffer是线程安全的StringBuilder不是
C,StringBuilder跟StringBuffer功能相同,区别是StringBuilder不是线程安全的
D,StringBuilder和StringBuffer底层都是以字符数组存放的,可以修改内容
封装

类Double,Integer,Float,Byte,Long,Character,short,Boolean。
Java中实现了自动装包和拆包来完成基本数据类型与包装对象的转换

除了 Integer 和 Character 定义的名称和对应的基本类型差异大,其他六种都是将首字母大写就可以了。
Integer,Byte,Float,Double,Short,Long 都是 Number 类的子类。
Character 和 Boolean 都是 Object 直接子类;
8 个类都是 final 修饰的(不可被继承)。

基本类型和 String 之间的转换

String → 基本类型,除了 Character 外所有的包装类提供 parseXxx(String s)静态方法,用于把一个特定的字符串转换成基本类型变量;

基本类型 → String,String 类有静态方法 valueOf(),用于将基本类型的变量转换成 String 类型。

Object类

所有类的公共父类,一旦一个类没有显示地继承一个类则其直接父类一定是 Object。一切数据类型都可用 Object 接收

方法:

  1. public boolean equals(Object obj):对象比较

  2. public int hashCode():用于返回字符串的哈希码。

  3. public String toString():对象描述
    Object 类的 toString()方法:“对象的描述”
    建议所有类都覆写此方法,直接打印输出对象时,会调用该对象的 toString()方法。

  4. public boolean equals(Object obj) :该方法用于比较对象是否相等,而且此方法必须被重写。
    String 覆写了 Object 的 equals 方法:只比较字符的序列是否相同
    ==用于判断两个变量是否相等 基本类型
    引用类型:必须指向同一个对象,才 true 只能比较有父子或平级关系的两个对象

equals()和==比较
原始数据比较:
基本数据类型的值
引用对象比较:==比较的是内存地址和内容,覆盖后的equals比较的是对象的内容,和地址无关

new String(“1”) == new String(“1”);

代码块

代码块指的是使用"{}"括起来的一段代码,根据代码块存在的位置可以分为 4 种:
普通代码块、构造代码块、 静态代码块、 同步代码块。

代码块里变量的作用域:只在自己所在区域(前后的{})内有效

普通代码块:
普通代码块就是直接定义在方法或语句中定义的代码块

构造代码块:
直接写在类中的代码块:
优先于构造方法执行,每次实例化对象之前都会执行构造代码块。

静态代码块使用 static 修饰的构造代码块:
优先于主方法执行,优先于构造代码块执行,不管有创建多少对象,静态代码块只执行一次,可用于给静态变量赋值;

优先级顺序:静态代码块 > 构造代码块 > 普通代码块

Singleton 模式(单例模式) 饿汉式和懒汉式

目的:整个应用中有且只有一个实例,所有指向该类型实例的引用都指向这个实例。

提供私有化的构造方法,那么外界就不能构造对象

常见单例模式类型:
饿汉式单例:直接将对象定义出来初始化

懒汉式单例:只给出变量,但是不创建对象,不将其初始化

饿汉式,static 修饰,随着类的加载而加载,会损耗性能,但是方法相对简单懒汉式 第一次用的时候相对较慢,因为需要加载!线程,不安全!

final 关键字

使用final关键字做标识有“最终的”含义。

  1. final 修饰类,则该类不允许被继承。
  2. final 修饰方法,则该方法不允许被覆盖(重写),允许重载。
  3. final 修饰属性,则该类的该属性不会进行隐式的初始化,所以 该final 属性的初始化属性必须有值,或在构造方法中赋值(但只能选其一,且必须选其一,因为没有默认值!),且初始化之后就不能改了,只能赋值一次。
  4. final 修饰变量,则该变量的值只能赋一次值,在声明变量的时候才能赋值,即变为常量。

finalize方法

java允许使用finalize方法在垃圾回收器将对象从内存中清除出去之前做必要的清理工作。
该方法是由垃圾回收器在确定这个对象没有被引用时对这个对象调用的,所有类都继承。

抽象类

定义:抽象类前使用abstract关键字修饰,则该类为抽象类。
使用抽象类要注意以下几点:

  1. 抽象类是约束子类必须有什么方法(抽象方法),而并不关注子类如何实现这些方法。
  2. 抽象类应用场景:
    a. 在某些情况下,某个父类只是知道其子类应该包含怎样的方法,但无法准确知道这些子类如何实现这些方法(可实现动态多态)。
    b. 从多个具有相同特征的类中抽象出一个抽象类,以这个抽象类作为子类的模板,从而避免子类设计的随意性。
  3. 抽象类定义抽象方法,只有声明,不需要实现。抽象方法没有方法体以分号结束,抽象方法必须用abstract关键字来修饰。
  4. 包含抽象方法的类是抽象类。抽象类中可以包含普通的方法,也可以没有抽象方法。
  5. 抽象类不能直接创建,可以定义引用变量来指向子类对象,来实现抽象方法。

接口

接口可以理解为一种特殊的类,由全局常量和公共的抽象方法所组成。也可理解为一个特殊的抽象类,因为它含有抽象方法。
如果说类是一种具体实现体,而接口定义了某一批类所需要遵守的规范,接口不关心这些类的内部数据,也不关心这些类里方法的实现细节,它只规定这些类里必须提供的某些方法。(这里与抽象类相似)
接口是把多个继承类的公共对象抽象出来并封装这些公共对象的行为。接口继承描述了一个对象在什么时候可以被用来替代另一个对象

接口中的属性都是常量,即使定义时不添加public static final 修饰符,系统也会自动加上;接口中的方法都是抽象方法,即使定义时不添加public abstract修饰符,系统也会自动加上。

注意:如果要继承父类,继承父类必须在实现接口之前,即extends关键字必须在implements关键字前
  注意:
1)接口不能有构造方法,抽象类可以有。
2)接口不能有方法体,抽象类可以有。
3)接口不能有静态方法,抽象类可以有。
4)在接口中凡是变量必须是public static final,而在抽象类中没有要求。
5)接口中的成员变量和成员方法只能是public(或缺省不写也表示public)

面向接口编程

指定标准和简单工厂模式:制定一个标准,让别人去实现或者说满足它
适配器模式:只想用其中的某一个方法,用适配器作为中间的过渡

枚举类

使用 enum 声明,默认直接继承了 java.lang.Enum 类,而不是 Object 类;枚举类的对象是固定的,实例个数有限,不可以再 new( ),枚举对象后可以跟()。
枚举元素必须位于枚举类体中的最开始部分,枚举元素后要有分号与其他成员分隔。

枚举类的构造方法的权限修饰符默认是 private;
一旦枚举对象后面加上{},那么该对象实际是枚举匿名内部类对象;
所有枚举类都提供一个静态的 values()方法(返回该枚举类所有对象组成的数组),便于遍历所有枚举对象;
所有枚举类都提供一个静态的 valueOf(String name)方法, 返回枚举类中对象名等于 name 的对象。

Eg:public enum Color{
RED(), GREEN(){}, BLUE{};
}

枚举不可以 new();即便是反射也不可以!
备注:一个类如果没有构造方法,那么一定有相对应的某个方法可以获取对象!

异常

Exception 和 Error 的子类名大都是以父类名作为后缀。
Java 异常其实是对不正常情况的一种描述,并将其封装成对象;
Java 在设计异常体系时,将容易出现的异常情况都封装成了对象。

异常处理的 5 个关键字 try ,catch, finally throw, throws

finally
异常的统一出口:
不管 try 块程序是否异常,也不管哪个 catch 执行,finally 块总会执行。
try 语句块或会执行的 catch 语句块使用了 JVM 系统退出语句例外;//System.exit(1);
try 块必须和 catch 块或和 finally 同在,不能单独存在,二者必须出现一个。
不要在 finally 中使用 return 或 throw 语句,否则将会导致 try、catch 中的 return 或 throw 失
效。

String方法

  • String():初始化一个新的 String 对象,使其表示一个空字符序列,并不是返回空(不等于 null)。
  • String(StringBuffer buffer):根据 StringBuffer 对象来创建 String 对象;
  • String(StringBuilder builder):同上
  • char charAt(int index):取字符串中指定位置的字符,index 从 0 开始计算。
  • String concat(String str):连接字符串,等同于 “+”;
  • boolean contentEquals(StringBuffer buffer):若二者包含的字符序列相同时就返回 true;
  • boolean equals(Object obj):将该字符串与指定对象比较,若二者包含的字符序列相等返回 true; boolean equalsIgnoreCase(String anotherString) 将此 String 与另一个 String 比较,不考虑大小写;
  • byte[] getBytes():将该字符串转换成 byte 数组;
  • int indexOf(String str):找出 str 字符串在该字符串中第一次出现的位置;
  • int indexOf(String str, int fromIndex) 返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始;
  • int lastIndexOf(String str) 返回指定子字符串在此字符串中最后一次出现处的索引; int length():返回当前字符串长度;
  • String replace(char oldChar, char newChar) :返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。
  • String replaceAll(String regex, String replacement) 使用给定的字符串replacement 替换此字符串所有的 regex 字符串; boolean startsWith(String prefix) 测试此字符串是否以指定的前缀开始.
  • String[] split(String regex): 把字符串按指定的字符串分隔开。
  • String substring(int beginIndex) 返回一个新的字符串,从 beginIndex 开始截取,它是此字符串的一个子字符串;
  • String substring(int beginIndex, int endIndex) 返回一个新字符串,它是此字符串的一个子字符串;[begin,end)
  • char[] toCharArray() 将此字符串转换为一个新的字符数组。
  • String toLowerCase() 将此 String 中的所有字符都转换为小写;
  • String toUpperCase()转成大写;
  • static String valueOf(基本类型 obj):把基本类型值转成字符串;
  • String trim() :忽略字符串前导空白和尾部空白。

StringBuffer 与 StringBuilder

String 是不可变类,一旦 String 对象被创建,包含在对象中的字符序列是不可变的,直到对象被销毁
StringBuffer 与 StringBuilder 对象则是可变的!
好处:不用每次新建对象,效率高!

StringBuffer:(字符串缓冲区类) 是线程安全的;字符串缓冲区可实现可变字符序列。字符串缓冲区可以被多个线程安全的使用。
StringBuffer 的字符序列是可变的(通过 append 等方法操作)

StringBuffer 方法:

  • public StringBuilder()构造一个不带任何字符的 StringBuilder 对象。
  • StringBuffer(String str) :构造一个字符串缓冲区,并将其内容初始化为指定的字符串内容。以指定的字符串创建 StringBuffer 对象。
  • StringBuffer append(Object o) :将指定的任意类型对象追加到此 StringBuffer 对象。
  • StringBuffer insert(int offset, Object o) :将任意类型参数的字符串表示形式插入此序列中。
  • StringBuffer delete(int start, int end) :移除此序列的子字符串中的字符。
  • StringBuffer deleteCharAt(int index): 移除此序列指定位置的 char。

StringBuilder: 是线程不安全的,性能高点,推荐使 StringBuilder

Math 和 Random 和 UUID

Math 类
public final class Math extends Object
以下 X 表示 double,float,int, long
abs(X x):求绝对值
max(X x1,X x2):求最大值
min(X x1,X x2):求最小值
public static double random():返回带正号的 double 值,该值大于等于 0.0 且小于 1.0。和使用 new java.util.Random 一样
Math.PI;

Random 类
负责生成伪随机数;
Random() 创建一个新的随机数生成器。 int nextInt() 返回下一个伪随机数,它是此随机数生成器的序列中均匀分布的 int 值。 int nextInt(int n) 返回一个伪随机数,它是取自此随机数生成器序列的、在 0(包括)和指定值 n(不包括)之间均匀分布的 int 值。

UUID 类:
用唯一标识符 (UUID) 的类。 UUID 表示一个 128 位的值。
UUID(用来标示文件名等(免得文件上传因为名字可能一样而被覆盖),可以保证全网唯一!)
UUID u = UUID.randomUUID() ;

Date 和 Calendar

Date d = new Date();
SimpleDateFormat sd = new SimpleDateFormat(“yyyy-M-d HH:mm:ss E”) ;
String s = sd.format(d);
//这个方法继承于SimpleDateFormat的父类DateFormat类!

线程技术

进程和线程

进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以有多个线程。
Java 程序的进程里有几个线程:主线程, 垃圾回收线程(后台线程)
线程和进程的区别?
(1)进程是资源的分配和调度的一个独立单元,而线程是CPU调度的基本单元,是进程的一个实体
(2)同一个进程中可以包括多个线程,并且线程共享整个进程的资源(寄存器、堆栈、上下文),一个进行至少包括一个线程。
1.进程有独立的进程空间,进程中的数据存放空间(堆空间和栈空间)是独立的。
2.线程的堆空间是共享的,栈空间是独立的,线程消耗的资源也比进程小,相互之间可以影响的。

线程安全

线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。
线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据

Java的多线程执行是抢占式的,当多个线程同时抢占一个资源进行计算时,有可能线程A运行到一半,线程B抢占线程A重新开始计算,线程B执行运算完毕后,线程A再抢占回资源进行运算,这时就会发生错误,因为线程A抢占回的资源数据已经不是它离开时的数据。这个时候可以使用锁解决并发导致的资源抢占问题。

Synchronized关键字设置这个方法为同步方法,当有多个线程希望使用这个方法时,这个关键字只允许一个线程占用此方法,其他方法处于等待状态。
在对象中创建一个ReentrantLock的实例,对需要加锁的代码段前面调用lock方法上锁,执行完毕调用unlock方法解锁。也可以使用tryLock方法,区别是tryLock可以设置你可返回或等待一段时间再返回

线程安全实例

线程安全
Hashtable,vector,StringBuffer

  • Hashtable是线程安全的,因为它的remove,put,get做成了同步方法(synchronized修饰),保证了Hashtable的线程安全性。
    每个操作数据的方法都进行同步控制之后,由此带来的问题任何一个时刻只能有一个线程可以操纵Hashtable,所以其效率比较低。key/value不可以为null值
  • Vector是线程安全的,很多方法都有同步关键字synchronized,从而保证所有的对外接口都会以 Vector对象为锁,即,在vector内部,所有的方法都不会被多线程访问。
    但是,单个方法的原子性(注:原子性,程序的原子性即不会被线程调度机制打断),并不能保证复合操作也具有原子性。

线程不安全
ConcurrentHashMap,HashMap,ArrayList,StringBuilder

  • HashMap:线程不安全(可以有null值但仅能有一个)
  • ConcurrentHashMap是线程安全的,因为ConcurrentHashMap将Map分段了,每个段进行加锁,而不是想Hashtable,SynchronizedMap是整个map加锁,这样就可以多线程访问了。
  • StringBuilder: 是线程不安全的,性能高点,推荐使 StringBuilder
线程锁

悲观锁
总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。

乐观锁
总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。

乐观锁常见的两种实现方式
乐观锁一般会使用版本号机制或CAS算法实现。
version方式:
一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。
CAS算法
即compare and swap(比较与交换),是一种有名的无锁算法。无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步

线程的创建

1、继承 Thread 类(Runnable的实现类),子类覆写父类中的 run 方法,将线程运行的代码存放在 run 中。建立子类对象的同时线程也被创建。通过调用 start 方法开启线程。
2、实现 Runnable 接口子类覆盖接口中的 run 方法。通过 Thread 类创建线程,并将实现了 Runnable 接口的子类对象作为参数传递给 Thread
类的构造函数。
Thread 类对象调用 start 方法开启线程。可使用匿名内部类来写
Thread 类中 run()和 start()方法的区别如下: run()方法:在本线程内调用该 Runnable 对象的 run()方法,可以重复多次调用; start()方法:启动一个线程,调用该 Runnable 对象的 run()方法,不能多次启动一个线程;

两种进程创建方式比较
A extends Thread:简单
不能再继承其他类了(Java 单继承) 同份资源不共享
A implements Runnable:(推荐)
多个线程共享一个目标资源,适合多线程处理同一份资源。该类还可以继承其他类,也可以实现其他接口。

实现方式,因为避免了单继承的局限性,所以创建线程建议使用第二种方式。

为什么要覆盖 run 方法呢?

Thread 类用于描述线程。该类就定义了一个功能,用于存储线程要运行的代码。该存储功能就是 run 方法.
也就是说 Thread 类中的 run 方法,用于存储线程要运行的代码。

线程的生命周期

Thread 类内部有个 public 的枚举 Thread.State,里边将线程的状态分为:

  • 新建状态:
    使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。
  • 就绪状态:
    当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。
  • 运行状态:
    如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
  • 阻塞状态:
    如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
    • 等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。

    • 同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。

    • 其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。

  • 死亡状态:
    一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。

如何停止线程:只有一种,run方法结束,控制循环

控制线程

join 方法:调用 join 方法的线程对象强制运行,该线程强制运行期间,其他线程无法运行,必须等到该线程结束后其他线程才可以运行。有人也把这种方式成为联合线程

sleep 线程休眠:
让执行的线程暂停一段时间,进入阻塞状态。

sleep()和wait()方法区别

在调用sleep()方法的过程中,线程不会释放对象锁。
而当调用wait()方法的时候,线程会放弃对象锁

sleep()是Thread线程类的静态方法,使当前正在执行的线程暂停一段时间,进入休眠等待状态,这样其他线程就可以得到执行的机会,会抛出InterruptedException异常。不会释放对象锁。

wait()是解决线程间通信问题,控制多个线程按照一定顺序轮流执行。同步代码块中的锁对象可以是任意类型的对象,Object类提供了wait(),notify(),notifyAll等方法解决线程间通信问题。
它的作用是是当前锁放弃同步锁并进入等待状态,直到其他线程进入此对象锁,并调用notify()或notifyAll方法唤醒该线程为止。三个方法的调用者都应该是同步锁对象

yield()和join()方法

yield()方法和sleep方法有点类似,可以让当前的线程暂停,但不会阻塞该线程,只是将线程转换成就绪状态,让系统的调度器重新调度一次。
join()线程插队,在线程中调用其他线程的join()方法,当前线程线程将被阻塞,直到插队的线程执行完毕

多线程安全问题的解决方法

  • 同步代码块
    synchronized(obj)
    {
    //obj 表示同步监视器,是同一个同步对象
    }

  • 同步方法:
    在方法上加上 synchronized 修饰符即可。(一般不直接在 run 方法上加!)
    静态方法的默认同步锁是当前方法所在类的.class 对象

  • 同步锁
    可重入性描述这样的一个问题:一个线程在持有一个锁的时候,它内部能否再次(多次)申请该锁。如果一个线程已经获得了锁,其内部还可以多次申请该锁成功。那么我们就称该锁为可重入锁。通过以下伪代码说明:

void methodA(){
lock.lock(); // 获取锁
methodB();
lock.unlock() // 释放锁
}

通过显示定义同步锁对象来实现同步,这种机制,同步锁应该使用 Lock 对象充当。在实现线程安全控制中,通常使用 ReentrantLock(可重入锁)。使用该对象可以显示地加锁和解锁。具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。

ReentrantLock和synchronized的区别

1:synchronized遇到异常,如果不catch,锁会被自动释放,而ReentrantLock需要手动释放。
2:synchronized是非公平锁(获取锁等待时间不同),而ReentrantLock可以构造成公平锁的形式
3:synchronized多个线程竞争一个锁,会一直尝试获取该锁,直到拿到为止,而ReentrantLock可以使用tryLock(), tryLock(long time,TimeUnit unit)方法尝试去获取锁,如果获取不到,可以执行其他逻辑,更加灵活。

synchronized修饰符
  1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;改代码块称为同步代码块
  2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
  3. 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
  4. 修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。
    volatile不会造成线程的阻塞;
    synchronized可能会造成线程的阻塞。
    valatile可以修饰变量

线程通信

wait():让当前线程放弃监视器(锁)进入等待,直到其他线程调用同一个监视器并调用 notify()或 notifyAll()为止。
notify():唤醒在同一对象监听器中调用 wait 方法的第一个线程。
notifyAll():唤醒在同一对象监听器中调用 wait 方法的所有线程。

wait()、notify()、notifyAll(),这三个方法属于 Object 不属于 Thread,这三个方法必须由同
步监视对象来调用

集合框架

Java集合如Map、Set、List等所有集合只能存放引用类型数据,它们都是存放引用类型数据的容器,不能存放如int、long、float、double等基础类型的数据。基本数据类型可以通过包装类把基本类型转为对象类型,存放引用

Java 集合框架类主要由两个接口派生出来:

  • Collection单列集合类的根接口
    • Set :不能存放重复对象
      • HashSet
      • TreeSet
    • List :可存放重复对象,有序
      • ArrayList
      • LinkedList
    • Queue :队列
    • SortedSet :可对集合数据排序
  • Map双列集合类的根接口
    • SortedMap :可对集合数据排序
    • HashMap :无序的哈希结构Map
    • TreeMap :有序的二叉树结构
    • LinkedHashMap :保证存入取出元素一致

集合框架接口:
Collection和Map接口
1.Set(集) SortSet
2.List(列表)
3.Map(映射) SortedMap

接口一维数组循环双链表平衡二叉树散列表
List列表ArrayList 不支持同步,Vector支持同步LinkedList
Set集合LinkedHashSetTreeSetHashSet
Map映射LinkedHashMapTreeMapHashMap

集合实现类
Collection-<HashSet(Set)-<TreeSet(SortSet)-<LinkedList,Vector,ArrayList(List)
HashTable HashMap(Map)-<TreeMap(SortMap)

Iterator 接口

Iterator 主要遍历 Collection 集合中的元素,也有称为迭代器

  • boolean hasNext():若被迭代的集合元素还没有被遍历,返回 true.
  • Object next():返回集合的下一个元素.
  • void remove():删除集合上一次 next()方法返回的元素。(若集合中有多个相同的元素,都可以删掉) iterator 对于集合才能用,for 不同,只要是循环都可用。
    迭代是取出集合中元素的一种方式。

因为 Collection 中有 iterator 方法,所以每一个子类集合对象都具备迭代器。迭代器在 Collcection 接口中是通用的,它替代了 Vector 类中的 Enumeration(枚举)。迭代器的 next 方法是自动向下取元素,要避免出现NoSuchElementException。迭代器的 next 方法返回值类型是 Object,所以要记得类型转换。(学到泛型就可以消除强转!)

  • Eg:Iterator iter = l.iterator();
    while(iter.hasNext()){
    System.out.println(iter.next());
    }

Set接口(元素不可以重复)

Set 是 Collection 子接口;
Set 和 Collection 基本上一样,一点除外:
Set 无法记住添加的顺序,不允许包含重复的元素。当试图添加两个相同元素进 Set 集合,添加操作失败,add()方法返回 false。
Set 判断两个对象是否相等用 equals,而不是使用 ==。也就是说两个对象 equals 比较返回 true,Set 集合是不会接受这个两个对象的。

常用子类:
HashSet:散列存放 ----hashCode()方法比较后equals比较对象相等
TreeSet:有序存放 ----compareTo(Object obj)方法比较对象相等

LinkHashSet:保证存储顺序

HashSet

HashSet 类是 Set 接口最常用的实现类,采用 hash 算法存储数据,具有良好的存储和查找功能。HashSet类是使用HashMap类的对象实现的
散列存储:不记录添加顺序;排列顺序时,顺序有可能发生变化; 线程不安全的,多个线程访问一个 HashSet 要使用同步代码
HashSet 集合元素值允许是 null,但是最多只能有一个因为 Set 集合就不可以装重复的对象!

hash(翻译为哈希,或散列)算法的功能: 保证通过一个对象快速找到另一个对象; 其算法价值体现在速度,可以保证查询快速执行;

当从 HashSet 中访问元素时,HashSet 先计算该元素的 hashCode(也就是该对象的 hashCode 方法返回值),然后直接到该 HashCode 对应的位置取出该元素; 在这里对象的 hashCode 就好比是数组里的索引,但是不是索引;

HashSet 元素添加
当向 HashSet 集合中存入一个元素时,HashSet 会调用该对象的 hashCode()方法来得到该对象的 hashCode 值,判断已经存储在集合中的对象的 hashCode 值是否与添加的对象的 hashCode 值一致:若不一致:直接添加进去;若一致,再进行 equals 方法比较,equals 方法如果返回 true,表明对象已经添加进去了,就不会再添加新的对象了,否则添加进去;== 如果我们重写了 equals 方法,也要重写 hashCode 方法,反之亦然==

HashSet 集合判断两个元素相等的标准是两个对象通过 equals 方法比较相等,并且两个对象的 hashCode 方法返回值也相等。如果需要某个类的对象保存到 HashSet 集合中,覆写该类的 equals()和 hashCode()方法,应该尽量保证两个对象通过 equals 比较返回 true 时,他们的 hashCode 返回也相等。
我的总结:
往 HashSet 集合里面存入数据,要先后调用两个方法:hashCode 方法和 equals 方法!!!

TreeSet(二叉树的方式存取数据)

TreeSet 是 SortedSet 接口唯一的实现,与 HashSet 相比额外的方法有:
Comparator comparator():返回当前 Set 使用的 Comparator,若返回 null,表示以自然顺序排序。
Object first() 返回此 set 中当前第一个(最低)元素。
Object last() 返回此 set 中当前最后一个(最高)元素。
SortedSet subSet(Object fromElement, E toElement) 返回此 set 的部子集,其元素从 fromElement(包括)到 toElement(不包括)。
SortedSet headSet(Object toElement)返回此 set 的部分子集,其元素严格小于 toElement。 SortedSet tailSet(Object fromElement) 返回此 set 的部分子集,其元素大于等于 fromElement。

存储自定义类型数据时,要实现Comparable接口的compareTo方法,自然排序,实现Comparable接口的compareTo方法,定制排序不使用compareTo方法,可以自定义比较器进行定制排序,实现Comparator接口自定义比较方法。

List 接口

List 是有序的集合,集合中每个元素都有对应的顺序序列。List 集合可使用重复元素,可以通过索引来访问指定位置的集合元素(顺序索引从 0 开始),List 集合默认按元素的添加顺序设置元素的索引,比如第一个元素的索引就是 0,好似数组。

List 作为 Collection 子接口当然拥有其所有方法,同时也有自己的方法:
void add(int index,Object e):将元素 e 添加到 List 集合中的 index 处;
boolean addAll(int index,Collection c):将集合 c 所包含的所有元素都插入在 List 集合的 index 处;
Object get(int index):返回集合 index 索引处的元素; int indexOf(Object o):返回对象 o 在 List 集合中第一次出现位置的索引; int lastIndexOf(object o):返回对象 o 在 List 集合中最后一次出现的位置索引;
Object remove(int index):删除并返回 index 索引处的元素;
Object set(int index,Object e):把集合 index 处的元素替换为 e 对象,返回以前在指定位置的元素;
List subList(int fromIndex,int toIndex):返回从所有 fromIndex(包括)到 toIndex(不包括)处所有集合元素的子集合。

ListIterator
Iterator 的子接口,专门用于操作 List 集合的输出;
List 自己还有一个 listIterator()方法,该方法返回 ListIterator 对象,ListIterator 继承了 Iterator
接口,提供了专门操作 List 的方法。在 Iterator 上额外增加的方法:支持双向输出:
boolean hasPrevious():返回该迭代器关联集合是否还有上一个元素; Object previous():返回该迭代器的上一个元素;
我的总结:这是相对更加特殊的一个接口,只用于 List 集合,可以完成逆序输出!

List 接口中常用类

Vector:线程安全,但速度慢,已被 ArrayList 替代。
ArrayList:底层基于长度可变的一维数组,线程不安全,查询速度快。
LinkedList:底层循环双链表结构,增删速度快。取出 List 集合中元素的方式: get(int index):通过脚标获取元素。 iterator():通过迭代方法获取迭代器对象。

ArrayList是线程不安全的,而 Vector 是线程安全的,但是即使这样,也不推荐使用 Vector,因为Collections 有方法可以得到线程安全的 ArrayList 对象:

Collections 类: static List synchronizedList(List list) 返回指定列表支持的同步(线程安全的)列表。

Queue 接口
继承 Collection 接口模拟队列:先进先出( FIFO);
void add(Object e):将 e 插入到队列尾部;
Object element():获取队列头部的元素;
boolean offer(Object e):将 e 插入到队列的尾部,当使用有容量限制的队列时,此方法比 add(Object e)方法更好。
Object peek():获取队列头部的元素。如果此双端队列为空,则返回 null。
Object poll():获取并删除队列头部的元素。如果此双端队列为空,则返回 null。 Object remove():获取并删除队列头部的元素。

Map 接口

映射关系,也有人称为字典,Map 集合里存在两组值,一组是 key,一组是 value。Map 里的 key 不允许重复。通过 key 总能找到唯一的 value 与之对应。
Map 里的 key 集存储方式和对应的 Set 集合中的元素存储方式一致
Map.Entry 是 Map 接口的内部接口,专门用来保存 key-value 内容
Map接口 keySet()方法用于获取键的集合,返回值Set型,size()方法获取当前映射包含的键值对数量

HashMap

HashMap是Map接口的一个实现类,通过散列的形式达到快速存取和空间控制
HashMap的本质仍然是数组,不过数组中存储的不是数据,而是一个链表的头节点。所以准确的说,其实现就是链表数组。HashMap中保存的是一个键值对,插入对象时必须提供一个键对象;查找对象时必须给定一个键对象(因此必须记住键)。键对象时不允许重复的,但是允许null空键的存在。
HashMap插入对象时,根据给定的键key计算hashcode,然后再与数组长度进行求余运算得到数组下标(key的hashcode值与HashMap容量求余)。然后与该位置上的链表中已存储的键进行比较(Key对象的equals方法),对于已存在的键,则覆盖;对于不存在的键,则添加到链表尾。如果重写Key类的equals不重写hashCode方法,且Key类的hashCode值求到的数组下标相同,就会出现同一个链表上有两个equals相等的Key类

HashTable和Properties

HashMap不支持同步,线程不安全,支持null,Hashtable支持同步,线程安全,效率不如HashMap。Hashtable还有一个子类Properties,主要用于存储字符串类型的键和值,Properties集合类对应的properties文件进行配置

LinkedHashMap

保证元素添加顺序

TreeMap集合

内部是通过二叉树的原理来保证键的唯一性,和TreeSet集合存储原理一样

流的分类(面试常考)
从不同角度分类:按流动方向的不同可以分为输入流和输出流;
按处理数据的单位不同分为字节流和字符流;
按功能的不同可分为节点流和处理流;
节点流:直接操作目标设备,例如:磁盘或一块内存区域。
处理流:通过操作节点流,从而间接完成输入或输出功能的流。
处理流是的存在是建立在一个已经存在的输入流或输出流的基础之上的。

所有流都继承于以下四种抽象流类型的某一种:(抽象流)
在这里插入图片描述

流的步骤(重点)

一、使用File类找到一个文件对象,得到IO操作的源或目标
二、通过字节流或字符流的子类创建对象,(得到IO操作的通道)
三、进行读或写的操作,(IO操作)
四、关闭输入/输出,(打完收工,注意节约资源,关掉)

由于流的操作属于资源操作,所以在操作的最后一定要关闭以释放资源

字节流

字节流主要是操作 byte(字节)的类型数据:
字节输出流:OutputStream 字节输入流:InputStream

字符流

Java 中的字符是 Unicode 编码,是双字节的,1 个字符 等于 2 个字节; 使用字节来处理字符文本就不太方便了,此时可以考虑使用字符流; 字符流主要是操作 char 的类型数据:

字符输出流:Writer 字符输入流:Reader

字节流和字符流的区别

字节流和字符流在使用上的代码结构都是非常类似的,但是其内部本身也是有区别的,因为在进行字符流操作的时候会使用到缓冲区(内存中),而字节流操作的时候是不会使用到缓冲区的。

字节→字符转换流

OutputStreamWriter:把字节输出流对象转成字符输出流对象
InputStreamReader:把字节输入流对象转成字符输入流对象

FileWriter 和 FileReader 分别是 OutputStreamWriter 和 InputStreamReader 的直接子类,而不是 Writer 和 Reader 的直接子类,区别于 FileInputStream 和 InputStream。

无论使用字节流还是字符流实际上在内存中最终都是通过字节的形式来操
作流的。所以并没有字符流转换字节流。
Eg:
//构建一个字节输出流对象
OutputStream out = new FileOutputStream("");
//把字节输出流转成字符输出流
Writer w = new OutputStreamWriter(out);
//然后的操作和使用字符输出流的操作一样

//构建一个字节输入流对象
InputStream is = new FileInputStream("");
//把字节输入流转成字符输入流
Reader r = new InputStreamReader(is);
//然后的操作和使用字符输入流的操作一样

RandomAccessFile

具有读写文件功能,而且可以随机地从文件的任何位置开始执行读写数据的操作

Scanner(简单文本扫描器)

Scanner(File source) 构造一个新的 Scanner,它生成的值是从指定文件扫描的。
备注:实现了Iterable接口

Scanner sc = new Scanner(System.in);

对象串行化

序列化是一种用来处理对象流的机制,是将对象的状态信息转换为可以存储的格式化(字节流)过程,以保持其状态的机制。器目的是为了将对象保存到磁盘中,或允许在网络中直接传输对象。通过反序列化可以将IO流中的字节序列恢复成Java对象

将一个对象存放到某种类型的永久存储器上称为“保持”。如果一个对象可以被存放到磁盘上或者发送到另一台机器的磁盘上,这个对象就被称为“可保持的”。

java中提供Serializable接口来进行对象串行化(对象序列化)。作为标记表示实现这个接口的类可以串行化,可以被保持。文件输入输出流类和线程类等流,由于数据在不断地改变,不会被串行化

一个对象只要实现了Serilizable接口,这个对象就可以被序列化,java的这种序列化模式为开发者提供了很多便利,我们可以不必关系具体序列化的过程,只要这个类实现了Serilizable接口,这个类的所有属性和方法都会自动序列化。 总之,java 的transient关键字为我们提供了便利,你只需要实现Serilizable接口,将不需要序列化的属性前添加关键字transient,序列化对象的时候,这个属性就不会序列化到指定的目的地中。

transient关键字

java语言的关键字,变量修饰符,如果用transient声明一个实例变量,当对象存储时,它的值不需要维持。换句话来说就是,用transient关键字标记的成员变量不参与序列化过程。

作用:

Java的serialization提供了一种持久化对象实例的机制。当持久化对象时,可能有一个特殊的对象数据成员,我们不想用serialization机制来保存它。为了在一个特定对象的一个域上关闭serialization,可以在这个域前加上关键字transient。当一个对象被序列化的时候,transient型变量的值不包括在序列化的表示中,然而非transient型的变量是被包括进去的。

1)一旦变量被transient修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问。
2)transient关键字只能修饰变量,而不能修饰方法和类。注意,本地变量是不能被transient关键字修饰的。变量如果是用户自定义类变量,则该类需要实现Serializable接口。
3)被transient关键字修饰的变量不再能被序列化,一个静态变量不管是否被transient修饰,均不能被序列化。

NIO

JDK1.4后提供的一系列改进的用于处理输入输出的新功能,用于替代传统的I/O而出现的。NIO采用内存映射文件的方式来处理输入输出,它将文件或文件的一段区域映射到内存中,这样就可以像访问内存一样访问文件。

在标准I/O中,使用的是字节流和字符流,而在NIO中,使用的是通道(Channel)和缓存区(Buffer)。数据总是从通道读到缓冲区,或从缓冲区写到通道。

三大核心

Buffer,Channel,Selector(多线程相关选择器)。Buffer可以看成是一个容器,器本质是一个数组缓冲区,读入或写到Channel中的所有对象都会先放到Buffer中。Channerl是对传统的输入输出的模拟,在NIO中,所有的数据都需要通过通道流的形式传输。Selector(选择器)用于监听多个通道的时间(例如:连接打开,数据到达等,主要用于多线程处理。

Channel 是一个接口对象,雷士与传统的流对象,可以异步的执行IO读写操作,读写操作是双向的,既可以从Channel中读取数据,又可以写数据到Channel中,而流的操作通常是单向的。Channe只能与Buffer进行交互,程序不能直接读取Channel中的数据

NIO与IO区别
  1. Java IO和NIO之间第一个最大的区别是,IO是面向流的,NIO是面向缓冲区的。 Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。 Java NIO的缓冲导向方法略有不同。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。但是,还需要检查是否该缓冲区中包含所有您需要处理的数据。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据。

  2. Java IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取,而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。

  3. 选择器(Selectors)
    Java NIO的选择器允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器,然后使用一个单独的线程来“选择”通道:这些通道里已经有可以处理的输入,或者选择已准备写入的通道。这种选择机制,使得一个单独的线程很容易来管理多个通道。

网络编程

网络模型

OSI 参考模型
TCP/IP 参考模型
在这里插入图片描述

TCP/IP模型

分为四层分别为 网络接口层、网络层、传输层、应用层

协议名
应用层DNS(域名系统)HTTP(超文本传输协议)FTP(文件传输协议)Telnet(远程终端协议)SMTP(简单邮件传输协议)…
传输层TCP(传输控制协议)UDP(用户数据报协议)…
网络层IP(网络协议)ARP(地址解析协议)ICMP(控制信息协议)…
网络接口层以太网…

HTTP协议

HTTP协议是指计算机通信网络中两台计算机之间进行通信所必须共同遵守的规定或规则,超文本传输协议(HTTP)是一种通信协议,它允许将超文本标记语言(HTML)文档从Web服务器传送到客户端的浏览器

网络通讯要素

  • IP 地址端口号
    IP 地址: InetAddress
    网络中设备的标识不易记忆,可用主机名
    本地回环地址:127.0.0.1 主机名:localhost

    端口号
    用于标识进程的逻辑地址,不同进程的标识
    有效端口:0~65535,其中 0~1024 系统使用或保留端口。备注:不是所谓的物理端口!

  • 传输协议
    传输协议
    通讯的规则
    常见协议:TCP,UDP

TCP协议

面向连接的可靠字节流服务。
传输数据之前在不同主机的传输端口之间建立一条链路。
提供可靠的传输控制协议,即传输前建立逻辑连接,然后传输数据,最后释放连接。
传输层连接的建立采用了三次握手机制:

第一次握手:客户端向服务器端发送连接请求包SYN,等待服务器回应;
第二次握手:服务器端收到客户端连接请求包SYN后,将客户端的请求包放入到自己的未连接队列,此时服务器需要发送两个包给客户端;
(1)向客户端发送确认自己收到其连接请求的确认包ACK,向客户端表明已知道了其连接请求
(2)向客户端发送连接询问请求包SYN,询问客户端是否已经准备好建立连接,进行数据通信;
第三次握手:客户端收到服务器的包后,知道了服务器同意建立连接,此时需要发送连接已建立的消息给服务器;
向服务器发送连接建立的确认包ACK,告诉服务器,我们之间已经建立了连接,可以进行数据通信。
服务器收到后,此时服务器与客户端进入建立状态,开始进行数据传送。
在这里插入图片描述

三次握手
第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。

第二次握手:服务器收到syn包,必须确认客户的ACK(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;

第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。

四次挥手
1)客户端进程发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入FIN-WAIT-1(终止等待1)状态。 TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。
2)服务器收到FIN连接释放报文,发出ACK确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。
3)客户端收到服务器的ACK确认请求报文后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。
4)服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。
5)客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2∗∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。
6)服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCP后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。

服务端与客户端状态

服务端状态:CLOSED -> LISTEN -> SYN_RCVD -> ESTABLISHED -> CLOSE_WAIT -> LAST_ACK -> CLOSED
客户端状态:CLOSED -> SYN_SENT -> ESTABLISHED -> FIN_WAIT_1 -> FIN_WAIT_2 -> TIME_WAIT -> CLOSED

为什么连接的时候是三次握手,关闭的时候却是四次握手?

答:因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,“你发的FIN报文我收到了”。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。

总结
TCP:
1、建立连接,形成传输数据的通道
2、在连接中进行大量数据的传输
3、通过三次握手完成连接、是可靠协议
4、必须建立连接,效率会稍低
例子:电话通话,必须连接,对方同意才可以发送数据(不然就等待),不能丢失数据。

UDP协议

面向进程的无连接传输服务
面向无连接的传输层协议,该协议使得数据传输的速度得到大幅度的提高。视频聊天语音聊天基本都是用 UPD 协议。不提供数据包分组,组装,面向事务简单的不可靠的传送服务。

总结
UDP:
1、将数据源和目的地封装到数据包中,不需要建立连接
2、每个数据包的大小限制在 64k 以内
3、因无连接,是不可靠协议,可能丢包
4、不需要建立连接,速度快
例子:聊天、对讲机就是 UDP 的,面向无连接(不管在不在,知不知道,只管发送,求速度),丢数据也不管。速度快。数据被分成包。

InetAddress 与 Socket

InetAddress:构造方法私有,不能直接创建对象。
InetAddress getByName(String host):在给定主机名的情况下确定主机的 ip 地址。
InetAddress getLocalHost():返回本地主机。
InetAddress[] getAllByName(String host) ip.getHostAddress(), ip.getHostName()

Socket
Socket 就是为网络服务提供的一种机制。通信的两端都有 Socket。网络通信其实就是 Socket 间的通信。数据在两个 Socket 间通过 IO 传输。

UDP 传输

①:只要是网络传输,必须有 socket 。
②:数据一定要封装到数据包中,数据包中包括目的地址、端口、数据等信息。
直接操作 udp 不可能,对于 java 语言应该将 udp 封装成对象,易于我们的使用,这个对象就是 DatagramSocket. 封装了 udp 传输协议的 socket 对象。
因为数据包中包含的信息较多,为了操作这些信息方便,也一样会将其封装成对象。这个数据包对象就是:DatagramPacket.通过这个对象中的方法,就可以获取到数据包中的各种信息。
DatagramSocket 具备发送和接受功能,在进行 udp 传输时,需要明确一个是发送端,一个是接收端。

udp的发送端:
①:建立udp的socket服务,创建对象时如果没有明确端口,系统会自动分配一个未被使用的端口。
②:明确要发送的具体数据。
③:将数据封装成了数据包。
④:用socket服务的send方法将数据包发送出去。
⑤:关闭资源。
udp的接收端:
①:创建udp的socket服务,必须要明确一个端口,作用在于,只有发送到这个端口的数据才是这个接收端可以处理的数据。
②:定义数据包,用于存储接收到数据。
③:通过socket服务的接收方法将收到的数据存储到数据包中。
④:通过数据包的方法获取数据包中的具体数据内容,比如ip、端口、数据等等。
⑤:关闭资源。

TCP 传输

两个端点的建立连接后会有一个传输数据的通道,这通道称为流,而且是建立在网络基础上的流,称之为 socket 流。该流中既有读取,也有写入。
tcp 的两个端点:一个是客户端,一个是服务端。客户端:对应的对象,Socket 服务端:对应的对象,ServerSocket
TCP 客户端:
①:建立 tcp 的 socket 服务,最好明确具体的地址和端口。这个对象在创建时,就已经可以对指定 ip 和端口进行连接(三次握手)。
②:如果连接成功,就意味着通道建立了,socket 流就已经产生了。只要获取到 socket 流中的读取流和写入流即可,只要通过 getInputStream 和 getOutputStream 就可以获取两个流对象。
③:关闭资源。

反射机制

简单的来说,反射机制指的是程序在运行时能够获取自身的信息。在 java 中,只要给定类的名字,那么就可以通过反射机制来获得类的所有信息。
通过getClass forName .class方法获取一个对象的反射类(字节码对象)
例子:spring的IOC(控制反转)/DI(依赖注入)

反射就是得到元数据(字节码对象)的行为;
备注:一个类在虚拟机中只有一份字节码

为什么要用反射机制?

这就涉及到了动态与静态的概念,
静态编译:在编译时确定类型,绑定对象,即通过。
动态编译:运行时确定类型,绑定对象。

动态编译最大限度发挥了 java 的灵活性,体现了多态的应用,有以降低类之间的藕合性。

耦合性:是对模块间关联程度的度量。模块间的耦合度是指模块之间的依赖关系,包括控制关系、调用关系、数据传递关系、

一句话,反射机制的优点就是可以实现动态创建对象和编译,体现出很大的灵活性,特别是在 J2EE 的开发。
它的缺点是对性能有影响。使用反射基本上是一种解释操作,这类操作总是慢于只直接执行相同的操作。

JavaBean

JavaBean 是一种特殊的 Java 类,主要用于传递数据信息,这种 java 类中的方法主要用于访问私有的字段,且方法名符合某种命名规则。

JavaBean是可序列化的,实现了serializable接口
具有一个无参构造器
有按照命名规范的set和gett,is(可以用于访问布尔类型的属性)方法

JavaBean是一种Java语言写成的可重用组件,其他Java类可以通过自省机制(反射机制)发现和操作这些JavaBean的属性

JavaBean 的实例对象通常称之为值对象(Value Object,简称 VO)。这些信息在类中用私有字段来存储,如果读取或设置这些字段的值,则需要通过一些相应的方法来访问。

POJO

一个简单的Java(纯洁)类,这个类没有实现/继承任何特殊的java接口或者类,不遵循任何主要java模型,约定或者框架的java对象。在理想情况下,POJO不应该有注解。

pojo和javabean的比较

pojo的格式用于数据的临时传递,它只能装在数据,作为数据存储的载体,而不具有业务逻辑处理的能力。
而javabean虽然数据的获取与pojo一样,但是javabean当中可以有其它的方法

注解(Annotation)

注解相当于一种标记,在程序中加了注解就等于为程序打上了某种标记,没加,则等于没有某种标记。

注解在目前而言最主流的应用:代替配置文件
注解优点:开发效率高 成本低
注解缺点:耦合性大 并且不利于后期维护

类加载器

类加载器就是加载类的工具

类加载器作用:将.class 文件中的内容加载进内存进行处理,处理完后的结果就是字节码。

默认类加载器:
1)Java 虚拟机中可安装多个类加载器,系统默认的有三个主要的,每个类负责加载特定位
置的类:BootStrap、ExtClassLoader、AppClassLoader
2)BootStrap–顶级类加载器:
类加载器本身也是 Java 类,因为它是 Java 类,本身也需要加载器加载,显然必须有第一个类加载器而不是 java 类的,这正是 BootStrap。它是嵌套在 Java 虚拟机内核中的,已启动即出现在虚拟机中,是用 c++写的一段二进制代码。所以不能通过 java 程序获取其名字,获得的只能是 null。

动态代理

代理类实现InvocationHandler接口,并实现了接口中的invoke()方法,在其中创建一个代理方法,参数为被代理对象
方法返回为生成的代理后的对象 Proxy.newProxyInstance(
classLoader,Interface[] arr , InvocationHandler
);
代理对象类拥有被代理对象类的所有方法,调用方法时调用代理类中的invoke方法去处理,对被代理对象类的中方法进行增强

在这里插入图片描述

面试汇总

&和&&的区别。 (1分)

&是位运算符,表示 按位与 运算,&&是逻辑运算符,表示 逻辑与 运算。
&&效率更高(左侧表达式为false时不再执行运算)

"=="和equals方法究竟有什么区别?(3分)

equals()和==比较
原始数据比较:
基本数据类型的值
引用对象比较:==比较的是内存地址和内容,覆盖后的equals比较的是对象的内容,和地址无关

==操作符专门用来比较两个变量的值是否相等,也就是用于比较变量所对应的内存中所存储的数值是否相同,要比较两个基本类型的数据或两个引用变量是否相等,只能用==操作符。

在实际开发中,我们经常要比较传递进行来的字符串内容是否相等,字符串的比较基本上都是使用equals方法
如果一个类没有自己定义equals方法,那么它将继承Object类的equals方法,Object类的equals方法的实现代码如下:
boolean equals(Object o){
return this==o;
}
这说明,如果一个类没有自己定义equals方法,它默认的equals方法(从Object 类继承的)就是使用==操作符,也是在比较两个变量指向的对象是否是同一对象,这时候使用equals和使用==会得到同样的结果,如果比较的是两个独立的对象则总返回false。

如果你编写的类希望能够比较该类创建的两个实例对象的内容是否相同,那么你必须覆盖equals方法,由你自己写代码来决定在什么情况即可认为两个对象的内容是相同的。

重写equals方法时,为什么要重写 hashCode方法。

这一点主要是考虑和集合类协同工作的需要。一般集合为加快存取速度,通常使用类hashtable的方式存取对象,
hashCode() && equals() 则是判断待查找元素与集合中某个元素相等的依据。 而java中默认的hashCode是
由对象的内存地址生成的, 如果重写了equals 而 重写 hashCode, 则会造成“A和B相等,A加入集合后,用B查询集合却查不到”的悖论。

简述List及Set的区别?(2分)

1.实现List接口的集合类特点:有序、可重复。
2.实现Set接口的集合类特点:无序、不可重复。

解释一下什么是servlet?(2分)

答:Servlet是一种服务器端的Java应用程序,具有独立于平台和协议的特性,可以生成动态的Web页面。 它担当客户请求(Web浏览器或其他HTTP客户程序)与服务器响应(HTTP服务器上的数据库或应用程序)的中间层。

Servlet是位于Web 服务器内部的服务器端的Java应用程序,与传统的从命令行启动的Java应用程序不同,Servlet由Web服务器进行加载,该Web服务器必须包含支持Servlet的Java虚拟机

servlet有良好的生存期的定义,包括加载和实例化、初始化、处理请求以及服务结束。这个生存期由javax.servlet.Servlet接口的init,service和destroy方法表达。

JSP和Servlet有哪些相同点和不同点,他们之间的联系是什么?(3分)

JSP被Tomcat编译后是"类servlet",最后再编译成.class。Servlet和JSP最主要的不同点在于,Servlet的应用逻辑是在Java文件中,并且完全从表示层中的HTML里分离开来。而JSP的情况是Java和HTML可以组合成一个扩展名为.jsp的文件(简化HTML开发)。JSP侧重于图,Servlet主要用于控制逻辑。

相同点:JSP和Servlet本质上都是Java类。

不同点:JSP侧重于视图,Servlet主要用于控制逻辑
Servlet中没有内置对象,Jsp中的内置对象都是必须通过HttpServletRequest对象,HttpServletResponse对象以及HttpServlet对象获得。

联 系:JSP是Servlet技术的扩展,本质上就是Servlet的简易方式。

多线程有几种实现方法?同步有几种实现方法? (3分)

多线程有两种实现方法,分别是继承Thread类与实现Runnable接口
同步的实现方面有两种,分别是synchronized,wait与notify
wait():使一个线程处于等待状态,并且释放所持有的对象的lock。
sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉InterruptedException异常。
notify():唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级。
Allnotity():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争。

hibernate的inverse属性的作用? (3分)

hibernate.cfg.xml配置文件中有这么一个属性inverse,它是用来指定关联的控制方的(关系维护者)
例如Customer和Linkman在保存时,两方都会维护外键关系,关系维护两次造成冗余,多余的维护关系语句显然是客户这一端在维护关系(联系人一端通过外键已经维护关系)
作用:性能优化,提高关系维护的属性

多的一方(LinkMan)不能放弃维护关系,因为外键字段就在多的一方
原则:无论怎么放弃,总有一方必须要维护关系
一对多关系中:只能一的一方放弃维护,多的一方不能放弃维护

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值