//Java优点 1.跨平台 2.面向对象编程语言 3.分布式计算 //Java运行机制 - 编程Java程序 - 编译Java文件 - JVM读取字节码文件运行程序
Java的三大体系
- JavaEE(J2EE) - JavaSE(J2SE) - JavaME(J2ME)
配置Java环境
环境变量 JRE(Java Runtime Environment)Java运行环境 + JDK(Java Development Kit)Java开发工具包 //关系 JDK = JRE + Java类库 + Java工具 JRE = JVM + Java核心类库
开发
//编译
javac HelloWorld.java
//运行
java HelloWorld
//开发工具
1.Eclipse
2.IDEA
代码规范
Java基本程序设计结构注释
通俗易懂的语言对代码进行描述解释,方便阅读 //单行注释
关键字
Java语言预先定义好的,有指定意义的标识符
abstract 非访问修饰符。用于类和方法:抽象类不能用于创建对象(要访问它,必须从另一个类继承)。抽象方法只能在抽象类中使用,并且它没有主体。主体由子类(继承自)提供 assert 用于调试 boolean 只能存储真值和假值的数据类型 break 从循环或switch块中断开 byte 可以存储-128和127之间的整数的数据类型 case 在switch语句中标记代码块 catch 捕获由try语句生成的异常 char 用于存储单个字符的数据类型 class 定义一个类 continue 继续到循环的下一个迭代 const 定义一个常量。不在使用中改用final default 指定switch语句中的默认代码块 do 与while一起使用以创建do-while循环 double 可以存储1.7e−308 到 1.7e+308 else 用于条件语句中 enum 声明枚举(不可更改)类型 exports 导出包含模块的包。Java9中的新特性 extends 扩展类(表示一个类是从另一个类继承的) final 用于类、属性和方法的非访问修饰符,使它们不可更改(无法继承或重写) finally 与异常一起使用,无论是否存在异常都将执行的代码块 float 可以存储3.4e−038 到 3.4e+038 for 创建for循环 goto 不在使用中,没有任何功能 if 一个条件语句 implements 实现一个接口 import 用于导入包、类或接口 instanceof 检查对象是特定类的实例还是接口的实例 int 可以存储从-2147483648到2147483647的整数的数据类型 interface 用于声明仅包含抽象方法的特殊类型的类 long 可以存储从-9223372036854775808到9223372036854775808的整数的数据类型 module 声明一个模块。Java9中的新特性 native 指定方法不是在同一Java源文件中实现的(而是在另一种语言中实现的) new 创建新对象 package 声明一个包 private 用于属性、方法和构造函数的访问修饰符,使它们只能在声明的类中访问 protected 用于属性、方法和构造函数的访问修饰符,使它们可以在同一个包和子类中访问 public 用于类、属性、方法和构造函数的访问修饰符,使它们可以被任何其他类访问 requires 指定模块内所需的库。Java9中的新特性 return 已完成方法的执行,并可用于从方法返回值 short 可以存储从-32768到32767的整数的数据类型 static 用于方法和属性的非访问修饰符。无需创建类的对象即可访问静态方法/属性 strictfp 限制浮点计算的精度和舍入 super 指超类(父)对象 switch 选择要执行的多个代码块之一 synchronized 非访问修饰符,指定一次只能由一个线程访问方法 this 指方法或构造函数中的当前对象 throw 创建自定义错误 throws 指示方法可能引发的异常 transient 非访问修饰符,指定属性不是对象持久状态的一部分 try 创建try…catch语句 var 声明一个变量。Java10中的新特性 void 指定方法不应具有返回值 volatile 指示属性不是本地缓存的线程,并且总是从"主内存"中读取 while 创建while循环
数据类型
- 基本数据类型:
- 数值类型:byte,short,int,long,float,double
- 非数值类型:char,boolean
- 引用数据类型:String
字符串
数据类型转换
- 自动转换
- 强制转换
变量和常量
- 数据类型
- 变量名
- 变量值
运算符
-
算数运算符
-
复合算数运算符
-
自增自减运算符
-
关系运算符
-
逻辑运算符
-
条件用算符
-
位运算符
-
进制转换
-
//<< 左边乘上2的右边次方 System.out.println(1<<3); //8 //>> 左边除以2的右边次方 System.out.println(8>>3); //1 System.out.println(1<<3>>1); //1
-
控制流程
//循环
//for
for (int i = 0; i < ; i++) {
}
//foreach
for (Student student:list) {
}
//dowhile
do {
}while ()
//while
while (){
}
//switch
switch ( ){
case 1:
break;
}
//判断
if (boolean) {
}else {
}
if (boolean) {
}else if(){
}else{
}
数组
数组就是一种可存储大量数据类型相同的变量的数据结构,具有相同数据类型的数据集合
//数组基本要素
- 数组名称
- 数组元素
- 元素下标
- 数据类型
//等号左边在栈中,右边在堆中
//声明数组
int[] array;
//分配内存空间
array = new int[6];
//给数组赋值
array[0] = 1;
array[1] = 2;
array[2] = 3;
array[3] = 4;
array[4] = 5;
array[5] = 6;
int[] array2 = {1, 2, 3, 4, 5, 6};
int[] array3 = new int[]{1, 2, 3, 4, 5, 6};
//常见错误
-数据类型不匹配
-边声明边赋值,代码写在同一行,不能换行
-数组下标月结
//常用操作及方法
-求数组的最大值
-求数组的最小值
-在数组的指定位置插入数据
-对数组进行排序
二维数组
对象和类面向对象编程思想:将程序模块化的思想。
什么是面向对象?
- 面向对象编程思想诞生之前,程序开发采用的是面向过程的结构化编程方式,是一种面向功能划分的软件结构。最小粒度细化到方法这一层。
- 面向过程注重的是每一个步骤,面向对象关注点在于整件事情的模块化结构。
类和对象的关系
每个对象都有特定的特征:1、属性。2、方法。
属性指的是对象的静态特征,方法用来描述对象的动态特征。
对象是用来描述客观存在的一个实体,改实体是由一组属性和方法构成。
类是与对象紧密结合的另外一个概念,类是产生对象的模版,所有的对象都是通过类来创建的。
二者的关系∶类是对象的抽象化描述,这些对象具有相同的特征和动作(属性和方法)。
对象是类的具体实例。
Java程序是以类位组织单元,程序运行时的主体是通过类创建的具体对象。
返回值
Java关于返回值的定义分为两类∶有返回值和无返回值,有返回值的方法需要在方法定义时指定返回值的数据类型,并在方法体中用return将结果返回给外部调用者,加法运算。
如果一个方法不需要进行返回操作,将返回值类型定义为void。
参数列表是指外部在调用该方法时需要传入到方法内部进行运算的数据。
构造函数、构造方法、构造器
构造函数是一种特殊的方法,普通方法是用来描述某个动作的,构造方法是用来创建对象的。
- 方法名必须与类名一致。
- 不需要定义返回值类型。
构造函数可分为有参构造和无参构造,有参构造是指带参数的构造函数,无参构造是指没有参数的构造函数.任何一个类都默认自带一个无参构造函数,如果手动在类中定义一个有参构造,则会覆盖默认的无参构造。
this 关键字
this用来指代当前类的实例化对象,通过this 可以调用当前类的属性和方法,比如在有参构造中,通过 this将外部传入的值赋给当前类的实例化对象。
this除了可以在类中访问属性也可以在类中调用方法,类中的方法可以分为两类︰构造方法、普通方法。
用this调用这两类方法的语法也不同,调用构造函数的语法是this(参数列表),不能在普通方法中使用ths调用构
造函数。
用this 调用普通方法, this.方法名(参数列表),可以在构造函数中使用,也可以在普通方法中使用。
成员变量和局部变量
变量的作用域是指在程序中可以通过变量名来访问该变量的范围,变量的作用域由变量被声明时所在位置决定的,Java中根据不同的作用域可以将变量分为成员变量和局部变量。
局部变量:如果一个变量在方法中声明,则该变量是局部变量。
成员变量︰如果一个变量在方法外,类中声明,则该变量是成员变量。
1、成员变量和局部变量的区别在于作用域不同,成员变量的作用域在整个类中,类中的每个方法都可以访问该变量,局部变量的作用域只在定义该变量的方法中,出了方法体就无法访问。
2、成员变量和局部变量的初始值也不同,局部变量不会赋初始值,成员变量会赋初始值,具体的值是由成员变量的数据类型决定的。
封装是指将类的属性隐藏在内部,外部不能直接访问和修改
封装的核心思想就是尽可能把属性都隐藏在内部,对外提供方法来访问,我们可以在这些方法中添加逻辑处理来实现过滤,以屏蔽错误数据的赋值。
封装的步骤:
- 修改属性(成员变量)的访问权限为私有,使得外部不能直接访问。提供外部可以直接调用的方法。
- 在该方法中加入对于属性的逻辑控制,避免出现逻辑上的错误。
什么是访问权限
访问权限是指该属性可以被直接访问的范围,是在属性定义时设定的,访问权限的可选项一共有4种: public、private、默认(不写).protected,区别在于作用域范围不同。
static
static表示静态或者全局,可以用来修饰成员变量和成员方法以及代码块。|
使用static修饰的成员变量和成员方法独立于该类的任何一个实例化对象,访问时不依赖于该类的对象,而是直接通过类去访问。可以理解为被该类的所有实例对象所共用,所以说是全局的。
继承继承是用来描述类之间的关系的,即一个类继承(拥有)另外一个类中的属性和方法,被继承的类叫做父类,继承父类的类叫做子类。
java中继承是单继承。一个子类只能有一个直接父类
子类访问父类
创建一个子类对象的时候,会默认先创建一个父类对象,无论是通过有参构造或是无参构造来创建子类对象,都是通过无参构造来创建父类对象的。
可以通过super关键字让子类创建对象时调用父类的有参构造。
子类可以访问父类的构造方法、普通方法、成员变量, 都是通过super关键字来完成,具体语法;
构造方法: super(参数列表) 普通方法:super.方法名(参数列表) 成员变量: super.成员变量名
在子类的构造方法中,可以通过super访问父类的构造方法和普通方法。
在子类的普通方法中,只能通过super访问父类的普通方法。
子类的访问权限
访问权限修饰符:public、protected、默认修饰符、private。
| 同一个类中 | 同包 | 子类(不同包) | 不同包 | |
|---|---|---|---|---|
| public | 可以访问 | 可以访问 | 可以访问 | 可以访问 |
| protected | 可以访问 | 可以访问 | 可以访问 | 不能访问 |
| 默认修饰符 | 可以访问 | 可以访问 | 不能访问 | 不能访问 |
| private | 可以访问 | 不能访问 | 不能访问 | 不能访问 |
包: package,用来管理Java文件,一个项目中不可避免会出现同名的Java类,为了防止产生冲突,可以把同名的Java类分别放入不同的包中。
包的命名规范︰包名由小写字母组成,不能以.开头或结尾,可以包含数字,但不能以数字开头,使用.来分层。包的命名方式一般采用网络域名的反向输出,如com.company.test/com.company.entity.
方法重写
子类在继承父类方法的基础上,对父类方法重新定义并覆盖的操作叫做方法重写。构造方法不能被重新,方法重写的规则:
-
父子类的方法名相同。
-
父子类的方法参数列表相同。
-
子类方法的返回值与父类方法返回值类型相同或者是其子类。
-
子类方法的访问权限不能小于父类
方法重写VS方法重载
位置:方法重写在子类中对父类方法进行重写,方法重载是在同一个类中。方法名:方法重写相同,方法重载相同。
参数列表:方法重写相同,方法重载不同。
返回值:方法重写相同或是其子类,方法重载没有要求。访问权限:方法重写不能小于父类,方法重载没有要求。
一个事物具有多种表现形态,在Java程序中,定义一个方法,在具体的生成环境中根据不同的需求呈现不同的业务逻辑。
多态的具体使用有两种形式:
- 定义方法时形参类型为父类,实际调用方法时传入子类类型的参数。
- 定义方法时返回值类型为父类,实际调用方法时返回子类对象。以上两种形式的基本原理都是父类引用可以指向子类对象。
如果一个方法只有方法的声明而没有具体的方法实现,这个方法就叫做抽象方法,Java 中的抽象方法需要使用abstract关键字来修饰。
一旦类中定义了抽象方法,则该类就成了抽象类,需要在类定义处添加abstract关键字来修饰。
抽象类与普通类的区别是抽象类不能被实例化,抽象方法与普通方法的区别是抽象方法没有方法体。
抽象类中可以没有抽象方法,但是包含了抽象方法的类必须定义为抽象类。即我们可以在抽象类中定义普通方法,但是在普通类中不能定义抽象方法。
如果父类是抽象类,一旦子类继承了该抽象父类,则子类必须对父类的抽象类方法进行重写,否则程序报错
如果子类也是抽象类,则可以不用重写父类的抽象方法
ObjectObject 是Java官方提供的类,存放在java.lang 包中,该类是所有类的直接父类或者间接父类,无论是Java提供的类还是开发者自定义的类,都是Object的直接子类或间接子类,Java中的任何一个类都会继承Object中的public和 protected方法。
hashCode( ); getClass(); equals (null) ; clone( ) ; tostring( ); notify ( ); notifyAll()) ; wait( ); wait (1000L); wait (1000L,100);
Object类中经常被子类重写的方法
1、public String toString() 以字符串的形式返回对象的信息 2、public boolean equals(Object obj) 判断两个对象是否相等 3、public native int hashCode() 返回对象的散列码
包装类
包装类是Java提供的一组类,专门用来创建8种基本数组类型对应的对象,一共8个包装类,存放在java.lang包中
| byte | Byte | short | short |
|---|---|---|---|
| int | lnteger | long | Long |
| float | Float | double | Double |
| char | Character | boolean | Boolean |
装箱和拆箱
包装类特有名词,装箱指基本数据类型转换成对应的包装类对象,拆箱指包装类对象转换成对应的基本数据类型
- 装箱
-
public Type(type value)
-
public Type(String value)/public Type(char value)
-
valueOf(type value)静态方法
每一个包装类都有一个valueOf(type value)方法
-
valueOf(String value)/valueOf(char value)专门为Character转换使用的,其他的7个包装类都可以使用valueOf(String value)。
需要注意的是Boolean.valueOf(String value)方法中,当value为"true"时,Boolean的值为true,否则 Boolean 的值为false。
- 拆箱
- *Value() 每个包装类都有Value()方法,通过该方法可以将包装类转为基本数据类型
- parse*(String value) 除了Character类以外的每一个包装类都有一个静态方法可以将字符串类型转为基本数据类型。
什么是接口
接口是由抽象类衍生出来的一个概念,并由此产生了一种编程方式:面向接口编程
面向接口编程就是将程序中的业务模块进行分离,以接口的形式去对接不同的业务模块。
面向接口编程的优点︰当用户需求变更时,只需要切换不同的实现类,而不需要修改串联模块的接口,减少对系统的影响。
- 能够最大限度实现解耦合,降低程序的耦合性。
- 使程序易于扩展。
- 有利于程序的后期维护。
如何使用接口
接口在Java 中时独立存在的一种结构,和类相似,我们需要创建一个接口文件,Java 中用class 关键字来标识类.用interface来标识接口.
//基本语法:
public interface 接口名{
public 返回值 方法名 (参数列表)
}
接口其实就是一个抽象类,极度抽象的抽象类。
抽象类:一个类中一旦存在没有具体实现的抽象方法时,那么该类就必须定义为抽象类,同时抽象类允许存在非抽象方法。
但是接口完全不同,接口中不能存在非抽象方法,接口中必须全部是抽象方法
因为接口中必须全部都是抽象方法,所以修饰抽象方法的关键字abstract可以省略
接口中允许定义成员变量,但是有如下要求:
- 不能定义private和protected修饰的成员变量,只能定义public和默认访问权限修饰符修饰的成员变量。
- 接口中的成员变量在定义时就必须完成初始化。
- 接口中的成员变量都是静态常量,即可以直接通过接口访问,同时值不能被修改
使用接口时,不能直接实例化接口对象,而必须实例化其实现类对象,实现类本身就是一个普通的Java类
通过implements 关键字来指定实现类具体要实现的接口,在实现类的内部需要对接口的所有抽象方法进行实现,同时要求访问权限修饰符、返回值类型、方法名和参数列表必须完全一致。
接口和继承,Java只支持单继承,但是接口可以多实现((一个实现类可以同时实现多个接口)
Java中的错误大致可以分为两类:
一类是编译时错误,一般是指语法错误;另一类是运行时错误。
异常类
- Error:系统错误,程序无法处理。
- Exception:程序运行时出现的错误,程序可以处理。
Error和Exception都是Throwable的子类,Throwable、Error、Exception都是存放在java.lang 包中。Error常见的子类:VirtualMachineError、AWTError、IOError。
VirtualMachineError的子类:StackOverflowError、OutOfMemoryError,用来描述内存溢出等系统问题。
Exception常见的子类:IOException和 RuntimeException
IOException存放在java.io包中,RuntimeException存放在java.lang 包中。
IOException常见的子类:FileLockInterruptionException、FileNotFoundException、FilerException,
这些异常通常都是处理通过IO流进行文件传输的时候发生的错误。
RuntimeException 常见的子类:
ArithmeticException://表示数学运算异常。 ClassNotFoundException://表类未定义异常。 llelArgumentException://表示参数格式错误。 ArraylndexOutOfBounds://表示数组下标越界。 NullPointException://表示空指针异常。 NoSuchMethodError://表示方法未定义异常。 NumberFormatException://表示将其他数据类型转为数值类型发生的类型不匹配异常。
throw和throws
throw和 throws是Java在处理异常时使用的两个关键字,都可以用来抛出异常,但是使用的方式和表示的含义完全不同。
Java中抛出异常有3种方式:
- try-catch
- 使用throw是开发者主动抛出异常,即读到 throw代码就一定抛出异常,基本语法: throw newException(),是一种基于代码的逻辑而主动抛出异常的方式
- try-catch和throw都是作用于具体的逻辑代码,throws 是作用于方法的,用来描述方法可能会抛出的异常。
- 如果方法throws的是RuntimeException异常或者其子类,外部调用时可以不处理,JVM会处理
- 如果方法throws的是Exception异常或其子类,外部调用时必须处理,否则报错
多线程是提升程序性能非常重要的一种方式,必须掌握的技术。使用多线程可以让程序充分利用CPU资源。
优点
- 系统资源得到更合理的利用
- 程序设计更加简洁。程序响应更快,运行效率更高。
缺点
-
需要更多的内存空间来支持多线程。
-
多线程并发访问的情况可能会影响数据的准确性。
-
数据被多线程共享,可能会出现死锁的情况。
-
什么是进程︰进程就是计算机正在运行的一个独立的应用程序。
进程是一个动态的概念,当我们启动某个应用的时候,进程就产生了,当我们关闭该应用的时候,进程就结束了,进程的生命周期就是我们在使用该软件的整个过程。
-
什么是线程:
线程是组成进程的基本单位,可以完成特定的功能,一个进程是由一个或多个线程组成的。
应用程序是静态的,进程和线程是动态的,有创建有销毁,存在是暂时的,不是永久的。
-
进程和线程的区别
进程在运行时拥有独立的内存空间,即每个进程所占用的内存空间都是独立的,互不干扰。
线程是共享内存空间的,但是每个线程的执行都是相互独立的,单独的线程是无法执行的,由进程来控制多个线程
多线程
多线程是指在一个进程中,多个线程同时执行,这里说的同时执行并不是真正意义的同时执行。
系统会为每个线程分配CPU资源,在某个具体的时间段内CPU资源会被一个线程占用,在不同的时间段内由不同的线程来占用CPU资源,所以多个线程还是在交替执行,只不过因为CPU运行速度太快,我们感觉是在同时执行。
-
继承Thread类
- 创建自定义类并继承Thread类
- 重写Thread类中的run方法,并编写该线程的业务逻辑代码
- 使用
汪意∶不能通过run万法来调用线程的任务,因为run万法调用相当于晋通对象的执行,并不会去抢占CPU资源。
只有通过start方法才能开启线程,进而去抢占CPU资源,当某个线程抢占到CPU资源后,会自动调用run方法。 -
实现Runnable接口
- 创建自定义类并实现Runnable接口
- 实现run方法,编写给线程的业务逻辑代码
- 使用
线程和任务︰
线程是去抢占CPU资源的,任务是具体执行业务逻辑的,线程内部会包含一个任务,线程启动(start),当抢占到资源之后,任务就开始执行(run)。
两种方式的区别:
- MyTrhead,继承Thread类的方式,直接在类中重写run方法,使用的时候,直接实例化MyThread,start即可,因为Thread 内部存在Runnable。
- MyRunnbale,实现Runnable接口的方法,在实现类中重写run方法,使用的时候,需要先创建 Thread对象,并将MyRunnable注入到Thread中,Thread.start。
实际开发中推荐第二种方式
线程的状态线程共有5种状态,在特定的情况下,线程可以在不同的状态之间切换,5种状态如下所示。
- 创建状态:实例化一个新的线程对象,还未启动。
- 就绪状态︰创建好的线程对象调用start方法完成启动,进入线程池等待抢占CPU资源。
- 运行状态∶线程对象获取了CPU资源,在一定的时间内执行任务。
- 阻塞状态︰正在运行的线程暂停执行任务,释放所占用的CPU资源,并在解除阻塞状态之后也不能直接回到运行状态,而是重新回到就绪状态,等待获取CPU资源。
- 终止状态:线程运行完毕或因为异常导致该线程终止运行。
如何让主线程休眠。直接通过静态方法调用sleep方法
sleep是静态本地方法,可以通过类调用,也可以通过对象调用,方法定义抛出InterruptedException,InterruptedException继承Exception,外部调用时必须手动处理异常。
线程合并合并是指将指定的某个线程加入到当前线程中,合并为一个线程,由两个线程交替执行变成一个线程中的两个自线程顺序执行。
通过调用join方法来实现合并,具体如何合并?
线程甲和线程乙,线程甲执行到某个时间点的时候调用线程乙的join方法,则表示从当前时间点开始CPU资源被线程乙独占,线程甲进入阻塞状态,直到线程乙执行完毕,线程甲进入就绪状态,等待获取CPU资源进入运行状态。
join方法重载,join()表示乙线程执行完毕之后才能执行其他线程,join(long millis)表示乙线程执行millis 毫秒之后,无论是否执行完毕,其他线程都可以和它争夺CPU资源。
线程礼让线程礼让是指在某个特定的时间点,让线程暂停抢占CPU资源的行为,运行状态/就绪状态—》阻塞状态,将CPU资源让给其他线程来使用。
假如线程甲和线程乙在交替执行,某个时间点线程甲做出了礼让,所以在这个时间节点线程乙拥有了CPU资源,执行业务逻辑,但不代表线程甲一直暂停执行。
线程甲只是在特定的时间节点礼让,过了时间节点,线程甲再次进入就绪状态,和线程乙争夺CPU资源。
通过yield方法实现
线程中断有很多种情况会造成线程停止运行:
后[
线程执行完毕自动停止
线程执行过程中遇到错误抛出异常并停止线程执行过程中根据需求手动停止
Java中实现线程中断有如下几个常用方法:
- public void stop()
- public void interrupt()
- public boolean isInterrupted()
stop方法在新版本的JDK已经不推荐使用,重点关注后两个方法。
interrupt 是一个实例方法,当一个线程对象调用该方法时,表示中断当前线程对象。每个线程对象都是通过一个标志位来判断当前是否为中断状态。
isInterrupted就是用来获取当前线程对象的标志位: true表示清除了标志位,当前线程已经中断;false表示没有清除标志位,当前对象没有中断。
当一个线程对象处于不同的状态时,中断机制也是不同的。
创建状态:实例化线程对象,不启动。
NEW表示当前线程对象为创建状态,false表示当前线程并未中断,因为当前线程没有启动,不存在中断,不需要清除标志位。
线程同步Java中允许多线程并行访问,同一时间段内多个线程同时完成各自的操作。多个线程同时操作同一个共享数据时,可能会导致数据不准确的问题。使用线程同步可以解决上述问题。
可以通过synchronized关键字修饰方法实现线程同步,每个Java对象都有一个内置锁,内置锁会保护使用synchronized关键字修饰的方法,要调用该方法就必须先获得锁,否则就处于阻塞状态。
synchronized关键字可以修饰实例方法,也可以修饰静态方法,两者在使用的时候是有区别的。
如何判断线程同步或是不同步?
找到关键点∶锁定的资源在内存中是一份还是多份?一份大家需要排队,线程同步,多份〈一人一份),线程不同步。
无论是锁定方法还是锁定对象,锁定类,只需要分析这个方法、对象、类在内存中有几份即可。
- 对象一般都是多份
- 类一定是一份
- 方法就看是静态方法还是非静态方法,静态方法一定是一份,非静态方法一般是多份
并发、并行
使用并发编程的目的?为了充分利用计算机的资源。提高性能,企业以盈利为目的。
- 并发:多个线程访问同一个共享资源,前提是计算机是单核CPU,多个线程不是同时在访问,而是交替进行,只是因为CPU透行速度太快。看起来是同时在运行。
- 并行:多核CPU,多个线程是真正的同时在运行,各自占用不同的CPU,相互之间没有影响,也不会争夺资源。Java默认线程有两个,main(主线程), GC(垃级回收机制
synchronized 关键字实现线程同步,让在访问同一个资源的多个钱程排队去完成业务,避免出现数据错乱的情况。
锁 死锁 DeadLock前提:一个线程完成业务需要同时访问两个资源。
死锁:多个线程同时在完成业务,出现争抢资源的情况
破解死锁:不要让多线程并发
LockJUC: java.util.concurrent
Lock是一个接口,用来实现线程同步的,功能与synchronized一样。Lock 使用频率最高的实现类是**ReentrantLock** (重入锁),可以重复上锁。
java.util.concurrent
Java并发编程工具包,Java官方提供的一套专门用来处理并发编程的工具集合〈接+类)
并发:单核CPU,多个线程""同时"运行,实际是交替执行,只不过速度太快,看起来是同时执行。 两个厨师一口锅
并行:多核CPU,真正的多个线程同时运行。 两个厨师两口锅
重入锁是JUC使用频率非常高的一个类 ReentrantLock
ReentrantLock 就是对synchronized的升级,目的也是为了实现线程同步。
- ReentrantLock是一个类,synchronized是一个关键字。
- ReentrantLock是JDK实现,synchronized是JVM实现。
- synchronized可以自动释放锁,ReentrantLock 需要手动释放。ReentrantLock 是Lock 接口的实现类。
公平锁和非公平锁的区别
公平锁:线程同步时,多个线程排队,依次执行
非公平锁:线程同步时,可以插队,(默认非公平锁)
生产者和消费者模式
在一个生产环境中,生产者和消费者在同一时间段内共享同一块缓冲区,生产者负责向缓冲区添加数据,消费者负责从缓冲区取出数据。
Java并发编程并发编程可以充分利用计算机的资源,把计算机的性能发挥最大,可以最大节约成本,提高效率
并发编程描述的是一种使系统允许多个任务可以在重叠的时间段内执行的设计结构,不是指多个任务在同一时间段内执行,而是指系统具备处理多个任务在同一时间段内同时执行的能力。
高并发是指我们设计的程序,可以支持海量任务的执行在时间段上重叠的情况。
高并发的标准:
- QPS:每秒响应的HTTP请求数量,QPS不是并发数。
- 吞吐量:单位时间内处理的请求数,由QPS和并发数来决定。
- 平均响应时间:系统对一个请求作出响应的评价时间。
QPS = 并发数/平均响应时间
- 并发用户数:同时承载正常使用系统的用户人数
互联网分布式架构设计,提高系统并发能力的方式
- 垂直扩展
- 水平扩展
提升单机处理能力
- 提升单机的硬件设备,增加CPU核数,升级网卡,硬盘扩容,升级内存。
- 提升单机的架构性能,使用Cache 提高效率,使用异步请求来增加单服务吞吐量,NoSQL提升数据库访问能力。
集群:一个厨师搞不定,多雇几个厨师一起炒菜,多个人干同一件事情。
分布式︰给厨师雇两个助手,一个负责洗菜,一个负责切菜,厨师只负责炒菜,一件事情拆分成多个步骤,由不同的人去完成。
站点层扩展:Nginx反向代理,一个Tomcat跑不动,那就10个Tomcat去跑。
服务层扩展:RPC框架实现远程调用,Spring Boot/Spring Cloud,Dubbo,分布式架构,将业务逻辑拆分到不同的 RPC Client,各自完成对应的业务,如果某项业务并发量很大,增加新的 RPC Client,就能扩展服务层的性能,做到理论上的无限高并发。
数据层扩展︰在数据量很大的情况下,将原来的一台数据库服务器,拆分成多台,以达到扩充系统性能的目的,主从复制,读写分离,分表分库。
Java中实现多线程有几种方式?
- 继承Thread类
- 实现Runnable接口
- 实现Callable 接口
Callable和 Runnable 的区别在于Runnable的run方法没有返回值,callable的call方法有返回值。
Callable –> FutureTask –> Runnable
sleep和wait
- sleep 是让当前线程休眠, wait是让访问当前对象的线程休眠。
- sleep不会释放锁,wait 会释放锁。
synchronized锁定的是什么
- synchronized修饰非静态方法,锁定方法的调用者
- synchronized 修饰静态方法,锁定的是类
- synchronized 静态方法和实例方法同时存在,静态方法锁定的是类,实例方法锁定的是对象
Lock
JUC提供的一种锁机制,功能和synchronized类似,是对synchronized 的升级,它是一个接口
常用的实现类是 ReentrantLock
- synchronized是通过JVM实现锁机制,ReentrantLock是通过JDK实现锁机制。
- synchronized是一个关键字,ReentrantLock是一个类。
重入锁:可以给同一个资源添加多把锁。
synchronized是线程执行完毕之后自动释放锁,ReentrantLock 需要手动解锁。
用synchronized 实现卖票
synchronized和 lock的区别
- synchronized自动上锁。自动释放锁,Lock手动上锁,手动释故锁
- synchronized无法判断是否获取到了锁,Lock 可以判断是否拿到了锁。
- synchronized拿不到锁就会一直等待,Lock不一定会一直等待。
- synchronized是Java关键字,Lock 是接口。
- synchronized 是非公平锁,Lock可以设置是否为公平锁。
公平锁:很公平,排队,当锁没有被占用时,当前线程需要判断队列中是否有其他等待线程。
非公平锁:不公平,插队,当锁没有被占用时,当前线程可以直接占用,而不需要判断当前队列中是否有等待线程。
实际开发中推荐使用Lock的方式 synchronized自动上锁,不专业,Lock手动上锁,专业
ReentrantLock 具备限时性的特点,可以判断某个线程在一定的时间段内能否获取到锁,使用tryLock方法,返回值是boolean 类型,true表示可以获取到锁,false表示无法获取到锁。
生产者消费者模式
必须使用while判断,不能用if,因为if会存在线程虚假唤醒的问题,虚假唤醒就是一些wait方法会在除了notify的其他情况被唤醒,不是真正的唤醒,使用while 完成多重检测,避免这一问题。
使用Lock锁,就不能通过 wait和 notify来暂停线程和唤醒线程,而应该使用Condition的await和 signal来暂停和唤醒线程。
ConcurrentModificationException 并发修改异常
-
Vector
-
Collections.synchronizedList
-
JUC:CopyOnWriteArrayList
CopyonWrite 写时复制,当我们往一个容器添加元素的时候,不是直接给容器添加,而是先将当前容器复制一份,向新的容器中添加数据,添加完成之后,再将原容器的引用指向新的容器。
CountDownLatch:减法计数器
可以用来倒计时,当两个线程同时执行时,如果要确保一个线程优先执行,可以使用计数器,当计数器清零的时候,再让另一个线程执行。
await():计数器停止,唤醒其他线程
new CountDownLatch(100)、coutDown()、await()必须配合起来使用,创建对象的时候财的值是多少,
coutDown()就必须执行多少次,否则计数器是没有清零的,计数器就不会停止,其他线程也无法唤醒,所以必须保证计数器清零,coutDown()的调用次数必须大于构造函数的参数值。
CyclicBarrier:加法计数器
await():在其他线程中试图唤醒计数器线程,当其他线程的执行次数达到计数器的临界值时,则唤醒计数器线程,并且计数器是可以重复使用的,当计数器的线程执行完成一次之后,计数器自动清零,等待下一次执行
Semaphore:计数信号量
实际开发中主要使用它来完成限流操作,限制可以访问某些资源的线程数量。
- 初始化
- 获取许可
- 释放
每个线程在执行的时候,首先获取信号量,只有获取到资源才可以执行,执行完毕后需要释放资源,留给下一个线程
读写锁
接口ReadWriteLock,实现类是ReentrantReadWriteLock,可以多线程同时读,但是同一时间内只能有一个线程进行写入操作。
读写锁也是为了实现线程同步,只不过粒度更细,可以分别给诿和写的操作设置不同的锁。
写入锁也叫独占锁,只能被一个线程占用,读取锁也叫共享锁,多个线程可以同时占用
线程池
预先创建好一定数量的线程对象,存入缓冲池中,需要用的时候直接从缓冲池中取出,用完之后不要销毁,还回到缓冲池中,为了提高资源的利用率。
优势:
- 提高线程的利用率
- 提高响应速度
- 便于统一管理线程对象可以控制最大的并发数
- 线程池初始化的时候创建一定数量的线程对象。
- 如果缓冲池中没有空闲的线程对象,则新来的任务进入等待队列。
- 如果绥冲池中没有空闲的线程对象,等待队列也已经填满,可以申请再创建一定数量的新线程对象,直到到达线程池的最大值,这时候如果还有新的任务进来,只能选择拒绝。
无论R种线程池,都是工具类Executors 封装的,底层代码都一样,都是通过创建ThreadPoolExecutor对象来完成线程池的构建。
- corePoolSize:核心池大小,初始化的线程数量,线程池容量的下限,
- maximumPoolSize:线程池最大线程数,它决定了线程池容量的上跟
corePoolSize就是线程池的大小,maximumPoolSize是一种补救措施,任务量突然增大的时候的一种补救措施。
- keepAliveTime:线程对象的存活时间,在没有任务可执行的情况下,必须是线程池中的数量大于corePoolSize,才会生效,
- TimeUnit:线程对象存活时间单位
- BlockingQueue:等待队列,存储等待执行的任务
- ThrcadFactory:线程工厂,用来创建线程对象
- RejectedExecutioaHandler:拒绝策略
- AbortPolicy:直接抛出异常
- Discardpolicy:放弃任务,不抛出异常
- DiscardOldestPclicy:尝试与等待队列中最前面的任务去争夺,不抛出异常
- CallerRunsPolicy:谁调用谁处理
池化技术池化思想
优势:
- ·提高线程的利用率
- 提高响应透度
- 便于统一管理线程对象
- 可控制最大并发数
线程池的具体设计思想
-
核心池的大小
-
线程池的最大容量
-
等待队列
-
拒绝策略
步骤
- 线程池启动的时候会按照核心池的数来创建初始化的线程对象2个。
- 开始分配任务,如果同时来了多个任务,2个线程对象都被占用了,第3个以及之后的任务进入等待队列,当前有线程完成任务恢复空闲状态的时候,等待队列中的任务获取线程对象。
- 如果等待队列也占满了,又有新的任务进来,需要去协调,让线程池再创建新的线程对象,但是线程池不可能无限去创建线程对象,一定会有一个最大上限,就是线程池的最大容量。
- 如果线程池已经达到了最大上限,并且等待队列也占满了,此时如果有新的任务进来,只能选择拒绝,并且需要根据拒绝策略来选择对应的方案。
线程池3大考点:
//一、Executors工具类的3种实现 Executorservice executorService - Executors. newSingleThreadExecutor( ); Executorservice executorservice = Executors.newFixedThreadPoo1 (5); Executorservice executorService = Executors.newcachedThreadPoo1 ( ) ; //二、7个参数 corePoolSize://核心池的大小 aximumPoclSize://线程池的最大容量 keepaliveTime∶//线程存活时间(在没有任务可执行的情况下〕,必须是线程池中的数量大于corePoolSize,才会生效TincUnit:存活时间单位 BlockingQueue://等待队列,存储等待执行的任务 ThreadFactory://线程工厂,用来创建线程对象 RejcctedExecutionHandler://拒绝策路 //三、4种拒绝策略 1、AbortPolicy:直接抛出异常 2、DiscardPolicy:放弃任务,不抛出异常 3、DiscardoldestPolicy:尝试与等待队列中最前面的任务去争夺,不抛出异常 4、CallerRunsPolicy:谁调用谁处理ForkJoin框架
ForkJoin是JDK 1.7后发布的多线程并发处理框架,功能上和JUC类似,JUC更多时候是使用单个类完成操作,ForkJoin使用多个类同时完成某项工作,处理上比JUC更加丰宫,实际开发中使用的场景并不是很多,互联网公司真正有高并发需求的情况才会使用,面试时候会加分
本质上是对线程池的一种的补充,对线程池功能的一种扩展,基于线程池的,它的核心思想就是将一个大型的任务拆分成很多个小任务,分别执行,最终将小任务的结果进行汇总,生成最终的结果。
本质就是把一个线程的任务拆分成多个小任务,然后由多个线程并发执行,最终将结果进行汇总。
比如AB两个线程同时还执行,A的任务比较多,B的任务相对较少,B先执行完毕,这时候B去帮助A完成任务(将A的一部分任务拿过来替A执行,执行完毕之后再把结果进行汇总,从而提高效率。
工作窃取
ForkJoin框架,核心是两个类
- ForkJoinTask(描述任务)
- ForkJoinPool(线程池)提供多线程并发工作窃取
使用ForkloinTask最重要的就是要搞清楚如何拆分任务,这里用的是递归思想。
需要创建一个ForkJoinTask任务,ForkJoinTask 是一个抽象类,不能直接创建ForkJoinTask 的实例化对象,开发者需要自定义一个类,继承ForkJoinTask的子类RecursiveTask ,Recursive就是递归的意思,该类就提供了实现递归的功能。
Volatile关键字volatile是JVM提供的轻量级同步机制,可见性,主内存对象线程可见。
一个线程执行完任务之后还会把变量存回到主内存中,并且从主内存中读取当前最新的值,如果是一个空的任务,则不会重新读取主内存中的值
线程池workQueue一个阻塞队列,用来存储等待执行的任务,常用的阻塞队列有以下几种:
- ArrayBlockingQueue:基于数组的先进先出队列,创建时必须指定大小。
- LinkedBlockingQueue:基于链表的先进先出队列,创建时可以不指定大小,默认值时lnteger.MAX_VALUE。
- SynchronousQueue:它不会保持提交的任务,而是直接新建一个线程来执行新来的任务。
- PriorityBlockingQueue:具有优先级的阻塞队列。
二叉树遍历,深度优先搜索等。
什么是递归?
常规的定义︰编程语言中,函数func直接或者间接调用函数本身,则该函数称为递归函数。
所有的递归问题都可以用递推公式来表示,所以要用递归解决问题,关键就是先找到递推公式。
递归需要满足3要索:
- 一个父问题可以拆分成若干个子问题,并且若干子问题的结果汇总起来就是父问题的答案。
- 父问题和子问题,解题思路必须完全一致,只是数据规模不同。
- 存在终止条件。
问题在不断拆分的同时,一定要在某个节点终止拆分,得到一个明确的答案。
集合框架为什么要使用集合框架?
- 数组的长度是固定
- 数组无法同时存储多个不同的数据类型
集合简单理解就是一个长度可以改变,可以保持任意数据类型的动态数组。
集合本身是数据结果的基本概念之一,我们这里说的集合是Java语言对这种数据结果的具体实现。
Java中的集合不是由一个类来完成的,而是由一组接口和类构成了一个框架体系。大致可分为3层,最上层是组接口,继而是接口的实现类。
| Collection | 集合框架最基础的接口,最顶层的接口 |
|---|---|
| List | Collection的子接口,存储有序、不唯一〔元素可重复)的对象,最常用的接口。 |
| Set | Collection 的子接口,存储无序、唯一〈元索不可重复)的对象。 |
| Map | 独立于Collection的另外一个接口,最顶层的接口,存储一组键值对象,提供键到值的映射。 |
| lterator | 输出集合元素的接口,一般适用于无序集合,从前往后输出。 |
| Listlterator | lterator子接口,可以双向输出集合中的元素。 |
| Enumeration | 传统的输出接口,已经被lterator取代。 |
| SortedSet | Set的子接口,可以对集合中的元紊进行排序。 |
| SortedMap | Map 的子接口,可以对集合中的元素进行排序。 |
| Queue | 队列接口。 |
| Map.Entry | Map的内部接口,描述Map中存储的一组键值对元素。 |
Collection是集合框架中最基础的父接口,可以存储一组无序、不唯一的对象
public interface Collectionextends Iterable {}
Collection接口可以存储一组无序,不唯一(可重复)的对象,一般不直接使用该接口,也不能被实例化,只是用来提供规范。
Collection是 lterable接口的子接口。
| 方法 | 作用 |
|---|---|
| int size() | 获取集合长度 |
| boolean isEmpty() | 判断集合是否为空 |
| boolean contains(Object o) | 判断集合中是否存在某个对象 |
| lterator iterator() | 实例化lterator接口,遍历集合 |
| ObjecttoArray() | 将集合转换为一个 Object数组 |
| T[]toArray(T[] a) | 将集合转换为一个指定数据类型的数组 |
| boolean add(E e) | 向集合中添加元素 |
| boolean remove(Object o) | 从集合中删除元素 |
| boolean containsAll(Collection c) | 判断集合中是否存在另一个集合的所有元素 |
| boolean addAll(Collection c) | 向集合中添加某个集合的所有元索 |
| boolean removeAllCollection c) | 从集合中删除某个集合的所有元素 |
| void clear() | 清除集合中的所有元素 |
| boolean equals(Collection c) | 判断两个集合是否相等 |
| int hashcode() | 返回集合的哈希值 |
Collection子接口
- List:存放有序、不唯一的元素
- Set:存放无序、唯一的元素
- Queue:队列接口
public interface Listextends Collection {}
- List常用的扩展方法
| 方法 | 作用 |
|---|---|
| T gettint index) | 通过下标返回集合中对应位置的元素 |
| T set(int index,T element) | 在集合中的指定位置存入对象 |
| int indexOf(Object o) | 从前向后查找某个对象在集合中的位置 |
| int lastlndexOf(Object o) | 从后向前查找某个对象在集合中的位置 |
| Listlterator listlterator() | 实例化Listlterator接口,用来遍历List集合 |
| List subList(int fromIndex,int tolndex) | 通过下标截取List集合 |
-
List接口的实现类
-
ArrayList:是开发中使用频率最高的List实现类,实现了长度可变的数组,在内存中分配连续空间,所以读取快,增删慢,非线程安全,效率高
-
Vector:线程安全,效率低,实现线程安全是因为直接通过synchronized修饰方法完成的
-
Stack:Vector的子类,实现了栈的数据结构,(后进先出)
- push:入栈方法
- peek:取出栈顶元素,将栈顶复制一份取出,取完之后栈内的数据不变。
- pop:取出栈顶元素,直接取出栈顶元素,取完之后栈内的数据减一
-
LikedList:实现了先进先出的队列,采用链表的形式存储。
-
ArrayList和 LikedList的区别:
内存中存储的形式不同,ArrayList采用的数组的方式,LinkedList 采用的是链表的形式。
数组在内存中存储空间是连续的,读取块,增删慢
- 因为数组在内存中是连续的,所以取数据可以通过寻址公式很快求出目标元素的内存地址
- 因为内存是连续的,所以新增或者删除元素,必然需要移动数据,而且数组长度越长,需要移动的元素越多,操作就越慢。
链表在内存中存储空间是不连续的,读取慢,增删快。
- 因为链表在内存中是不连续的,没有固定的公式可以使用,要读取只能从第一位开始一直遍历到目标元素,数据规模越大,操作越慢。
- 增删快,因为只需要熏新设置目标元素前后两个节点的后置指针即可,与数据规模无关。
LinkedList 和 Stack都有pop方法,有什么区别和相同点?
pop方法都是取出集合中的第一个元素,但是两者的顺序是相反的,Stack是"后进先出",所以 pop取出的是最后一个元素,LinkedList是“先进先出”,所以pop取出的是第一个元素。
LinkedList 实现了Deque 接口,而Deque 接口是Queue 的子接口,Queue 就是队列,底层实现了队列的数据结构。
实际开发中,不能直接实例化Queue对象。
Queue的实现类是AbstractQueue,它是一个抽象类,不能直接实例化,开发中需要实现它的子类PriorityQueue.
Queue中添加的数据必须是有顺序的,Queue默认给元素进行升序排列,即自然排序。
Set跟List一样,Set是Collection的子接口,Set 集合是以散列的形式存储数据,所以元素是没有顺序的,可以存储一组无序且唯一的数据。
public interface Setextends Collection {}
-
Set接口的实现类
-
HashSet:HashSet是开发中经常使用的一个实现类,存储一组无序且唯一的对象。
- 无序:元素的存储顺序和遍历顺序不一致。
-
LinkedHashSet:LinkedHasSet是Set的另外一个实现类,可以存储一组有序且唯一的元素
- 有序:元素的存储顺序和遍历顺序一致。
-
TreeSet:和 LinkedHashSet一样都是存储一组有序且唯一的数据。
-
LinkedHashSet和 TreeSet的两个有序的区别
- LinkedHashSet的有序是指元素的存储顺序和遍历顺序是—致的。
- TreeSet的有序是指集合内部会自动对所有的元素按照升序进行排列,无论存入的顺序是什么,遍历的时候一定按照升序输出。
equals 和 == 的区别?
所有类中的equals都是继承自Object类,Object类中原生的eqauls方法就是在通过 == 进行判断
public boolean equals (0bject obj) {
return ( this == obj);
}
但是每个类都可以对equals方法进行重写,覆盖掉之前使用 == 进行判断的逻辑,改用新的逻辑进行判断是否相等。
LinkedHashSet 如何判断两个对象是否相等?
首先会判断两个对象的hashCode是否相等
什么是hashCode?
将对象的内部信息(内存地址、属性值等),通过某种特定规则转换成一个散列值,就是该对象的 hashCode
- 两个不同对象的hashCode值可能相等。
- hashcode 不相等的两个对象一定不是同一个对象。
集合在判断两个对象是否相等的时候,会先比较他们的 hashCode,如果 hashCode不相等,则认为不是同一个对象,可以添加。
如果hashCode值相等,还不能认为两个对象是相等的,需要通过equals进行进一步的判断,equals 相等,则两个对象相等,否则两个对象不相等。
==== :判断的是栈内存中的值。==
- 引用类型的数据,栈内存中存储的是地址,所以此时== 判断的是引用地址。
- 引用类型具体的对象(属性)存储在堆中的,再将堆中对象的内存地址赋值给栈中的变量data,data 中存储的就是地址。
- 基本数据类型,栈内存中存储的是具体的数值。
- 基本数据类型不需要用到堆内存,变量在栈中,变量的值直接存储在变量中。|
跟key-value,数据字典,List、Set接口都是Collection的子接口,Map接口是与Collection完全独立的另外一个体系。
- Map常用的方法
| 方法 | 作用 |
|---|---|
| int size() | 获取集合长度 |
| boolean isEmpty() | 判断集合是否为空 |
| boolean containsKey(Object key) | 判断集合中是否存在某个key |
| boolean containsValue(Object value) | 判断集合中是否存在某个value |
| V get(Object key) | 取出集合中key对应的value |
| V put(K key V value) | 向集合中存入一组 key-value的元素 |
| V remove{Object key) | 删除集合中 key对应的value |
| void putAll(Map map) | 向集合中添加另外一个Map |
| void clear() | 清除集合中所有的元泰 |
| Set keySet() | 取出集合中所有的key,返回一个Set |
| Collection values() | 取出集合中所有的value,返回一个Collection |
| Set | 将Map 以 Set的形式输出 |
| int hashCode() | 获取集合的散列值 |
| boolean equals(Object o) | 比较两个集合是否相等 |
- Map接口的实现类
- HashMap:存储一组无序,key不可以重复,value可以重复的元素。非线程安全,效率高
- Hashtable:存储一组无序,key不可以重复,value可以重复的元素。线程安全,效率低,实现线程安全是因为直接通过synchronized修饰方法完成的
- TreeMap:存储一组有序,key不可以重复,value可以重复的元素,可以按照key进行排序。
HashMap和 Hashtable,保存的数据都是无序的,Map的另外一个实现类TreeMap主要功能是按照key对集合中的元素进行排序。
List & Set vS Map
- List & Set & Collection 只能操作单个元素,Map可以操作一对元素,因为Map存储结构是key - value 映射。
- Map 接口定义时使用了泛型,并且定义两个泛型K和V,K表示 key,规定键元素的数据类型,v表示value,规定值元素的数据类型。
Collection接口,List和Set的父接口。
Collections 不是接口,它是一个工具类,专门提供了一些对集合的操作,方便开发者去使用,完成相应的业务功能。
Colletions针对集合的工具类,Collection
Arrays 针对数组的工具类,Array
| 方法 | 作用 |
|---|---|
| public static sort() | 对集合进行排序 |
| public static int binarySearch(List list,Object v) | 查找v在list中的位置,集合必须是升序排列 |
| public static get(List list,int index) | 返回list中index位置的值 |
| public static void reverse(List list) | 对list进行反序输出 |
| public static void swap(List list,int i,int j) | 交换集合中指定位置的两个元素 |
| public static void fill(List list,Object obj) | 将集合中所有元索替换成obj |
| public static Object min(List list) | 返回集合中的最小值 |
| public static Object max(List list) | 返回集合中的最大值 |
| public static boolean replaceAll(List list,Object old,Object new) | 在list集合中用new替换old |
| public static boolean addAll(List list,Object…obj) | 向集合中添加元素 |
可变参数,在调用方法的时候,参数可以是任意个数,但是类型必须匹配。
public static void test ( int... arg)i
Java中默认输出对象的格式:对象所属的全类名(全限定类名)带着包名的类名+@+对象的哈希值
//逐行检测 Java是必须全部编译之后,统一执行,假如有10行Java代码,必须先对这10行代码进行编译,通过之后,再交给JVM执行。 JS逐行执行,执行一行算一行,假如有10行JS代码,一行一行开始执行,执行到第5行报错,那么后续6-10就不再执行,但是已经执行的前5行结果不变。 Java更加严谨,JS更加随意 Java是强语言类型的,JS是弱语言类型泛型
泛型(Generics),是指在类定义时不指定类中信息的具体数据类型,而是暂时用一个标识符来替代,当外部实例化对象的时候再来指定具体的数据类型。
优点:这样做极大地提升程序的灵活性,提升类的扩展性,泛型可以指代类中成员变量的数据类型,方法的返回值类型以及方法的参数类型。
泛型的应用
//自定义类中添加泛型
public class 类名<泛型1,泛型2,泛型3... >{
private 泛型1 属性名;
public 泛型2 方法名(泛型3){
方法体
}
}
泛型通配符
==>==表示可以使用任意的泛型类型对象,这样方法具备通用性了。
多态在泛型中不适用
泛型上限和下限
- 上限:表示实例化时具体的数据类型,可以是上限类型的子类或者是上限类型本身,用extends表示。
//类名 <泛型标识 extends 上限类名>
public static void test (Time extends Number> time ){}
- 下限:表示实例化时具体的数据类型,可以是下限类型的父类或者是下限类型本身,用super表示。
//类名 <泛型标识 super 下限类名>
public static void test2 (Time super String> time ){}
泛型接口
public interface MyInterface{ public T getValue(); }
实现泛型接口有两种方式:
- 实现类在定义时继续使用泛型标识
public class MyInterfaceImplimplements MyInterface { private T obj; public MyInterfaceImpl(T obj) { this.obj = obj; } @Override public T getValue() { return this.obj; } }
- 实现类在定义时直接给出具体的数据类型
package com.southwind.demo; public class MyInterfaceImpl2 implements MyInterfaceJava 实用类 枚举{ private String obj; public MyInterfaceImpl2(String obj) { this.obj = obj; } @Override public String getValue() { return this.obj; } } public class Test { public static void main(String[] args) { MyInterfaceImpl myInterface = new MyInterfaceImpl ("接口"); String val = (String) myInterface.getValue(); MyInterfaceImpl2 myInterface1 = new MyInterfaceImpl2("接口"); val = myInterface1.getValue(); } }
枚举 Enum,是一种有确定值区间的数据类型,本质上就是一个类,具有简洁、安全、方便等特点。
枚举的值被约束到了一个特定的范围内,只能从这个范围以内取值。
为什么要有枚举?
因为在描述某些对象的属性时,该属性的值不能随便定义,必须在某个特定的区间内取值。
出于对数据的安全性考虑,类似这种有特定取值范围的数据我们就可以使用枚举来描述。
枚举指由一组常量组成的类型,指定一个取值区间,我们只能从该区间中取值。
final class Week extends Enum{
public static final Week MONDAY;
public static final Week TUESDAY;
public static final Week WEDNSDAY;
public static final Week THURSDAY;
public static final Week FRIDAY;
public static final Week SATURDAY;
public static final Week SUNDAY;
private static final Week $VALUES[];
static{
MONDAY = new Week("MONDAY",0);
TUESDAY = new Week("TUESDAY",1);
WEDNSDAY = new Week("WEDNSDAY",2);
THURSDAY = new Week("THURSDAY",3);
FRIDAY = new Week("FRIDAY",4);
SATURDAY = new Week("SATURDAY",5);
SUNDAY = new Week("SUNDAY",6);
$VALUES[] = (new Week[]{
MONDAY,TUESDAY,WEDNSDAY,THURSDAY,FRIDAY,SATURDAY,SUNDAY
})
}
public static Week[] values(){
return $VALUES.clone();
}
public static Week valueOf(String s){
return Enum.valueOf(s);
}
private Week(String s,int i){
super(s,i);
}
}
Math
Math 类为开发者提供了一系列的数学方法,同时还提供了两个静态常量 E(自然对数的底数)和 PI(圆周率)。
public class Test {
public static void main(String[] args) {
System.out.println("常量E"+Math.E);
System.out.println("常量PI"+Math.PI);
System.out.println("9的平方根"+Math.sqrt(9));
System.out.println("8的立方根"+Math.cbrt(8));
System.out.println("2的3次方"+Math.pow(2,3));
System.out.println("较大值"+Math.max(6.5,1));
System.out.println("-10.3的绝对值"+Math.abs(-10.3));
System.out.println(Math.ceil(10.000001));
System.out.println(Math.floor(10.999999));
System.out.println((int)(Math.random()*10));
System.out.println(Math.rint(5.4));
}
}
Random
用来产生随机数的类,并且可以任意指定一个区间,在此区间范围内产生一个随机数。
| 方法 | 描述 |
|---|---|
| public Random() | 创建一个无参的随机数构造器,使用系统时间作为默认种子 |
| public Random(long seed) | 使用 long 数据类型的种子创建一个随机数构造器 |
| public boolean nextBoolean() | 返回一个 boolean 类型的随机数 |
| public double nextDouble() | 返回一个 double 类型的随机数,0.0 - 1.0 之间 |
| public float nextFloat() | 返回一个 float 类型的随机数,0.0 - 1.0 之间 |
| public int nextInt() | 返回一个 int 类型的随机数 |
| public int nextInt(n) | 返回一个 int 类型的随机数,0-n之间 |
| public long nextLong | 返回一个 long 类型的随机数,0-1 之间 |
public class Test {
public static void main(String[] args) {
Random random = new Random();
//生成订单编号(时间戳+随机数)
for (int i = 1; i <= 10000; i++) {
//随机生成一个六位数
System.out.println("订单"+i+"的编号是:"+System.currentTimeMillis()+random.nextInt(100000)+100000);
}
}
}
String
Java 通过 String 类来创建和操作字符串数据。
String 实例化
1、直接赋值
String str = "Hello World";
2、通过构造函数创建对象
String str = new String("Hello World");
String 常用方法
| public String() | 创建一个空的字符串对象 |
|---|---|
| public String(String value) | 创建一个值为 value 的字符串对象 |
| public String(char value[]) | 将一个char数组转换为字符串对象 |
| 方法 | 描述 |
| public String(char value[],int offset, int count) | 将一个指定范围的char数组转为字符串对象 |
| public String(byte value[]) | 将一个byte数组转换为字符串对象 |
| public String(byte value[],int offset, int count) | 将一个指定范围的byte数组转为字符串对象 |
| public int length() | 获取字符串的长度 |
| public boolean isEmpty() | 判断字符串是否为空 |
| public char charAt(int index) | 返回指定下标的字符 |
| public byte[] getBytes() | 返回字符串对应的byte数组 |
| public boolean equals(Object anObject) | 判断两个字符串值是否相等 |
| public boolean equalsIgnoreCase(Object anObject) | 判断两个字符串值是否相等(忽略大小写) |
| public int compareTo(String value) | 对字符串进行排序 |
| public int compareToIgnoreCase(String value) | 忽略大小写进行排序 |
| public boolean startsWith(String value) | 判断字符串是否以 value 开头 |
| public boolean endsWith(String value) | 判断字符串是否以 value 结尾 |
| public int hashCode() | 返回字符串的 hash 值 |
| public int indexOf(String str) | 返回 str 在字符串中的下标 |
| public int indexOf(String str,int formIndex) | 从指定位置查找字符串的下标 |
| public String subString(int beginIndex) | 从指定位置开始截取字符串 |
| public String subString(int beginIndex,int endIndex) | 截取指定区间的字符串 |
| public String concat(String str) | 追加字符串 |
| public String replaceAll(String o,String n) | 将字符串中所有的 o 替换成 n |
| public String[] split(String regex) | 用指定的字符串对目标进行分割,返回数组 |
| public String toLowerCase() | 转小写 |
| public String toUpperCase() | 转大写 |
| public char[] toCharArray() | 将字符串转为字符数组 |
null 和空是两个概念。
null 是指对象不存在,引用地址为空。
空是指对象存在,没有内容,长度为零。
StringBuilder StringBufferString 对象一旦创建,值不能修改(原来的值不能修改,一旦修改就是一个新的对象,只要一改动,就会创建一个新的对象)
修改之后会重新开辟内存空间来存储新的对象,会修改 String 的引用。
String 的值为什么不能修改?修改之后会创建一个新的对象?而不是在原有对象的基础上进行修改?
因为 String 底层是用数组来存值的,数组长度一旦创建就不可修改,所以导致上述问题。
StringBuffer 可以解决 String 频繁修改造成的空间资源浪费的问题。
StringBuffer 底层也是使用数组来存值。
- StringBuffer 数组的默认长度为 16,使用无参构造函数来创建对象。
@HotSpotIntrinsicCandidatepublic StringBuffer(){
super(16);
}
- 使用有参构造创建对象,数组长度=值的长度+16。
@HotSpotIntrinsicCandidatepublic StringBuffer(String str){
super(str.length() + 16);
append(str);
}
public class Test {
public static void main(String[] args) {
StringBuffer stringBuffer = new StringBuffer("Hello");
StringBuffer stringBuffer1 = new StringBuffer();
//stringBuffer 底层数组的长度是 21
//stringBuffer1 底层数组的长度是 16
stringBuffer1.append("Hello");
System.out.println(stringBuffer.toString().equals(stringBuffer1.toString()));
System.out.println(stringBuffer.length());
System.out.println(stringBuffer1.length());
}
}
length 方法返回的并不是底层数组的长度,而是它的有效长度(值的长度)。
StringBuffer 一旦创建,默认会有 16 个字节的空间去修改,但是一旦追加的字符串长度超过 16,如何处理?
StringBuffer 不会重新开辟一块新的内存区域,而是在原有的基础上进行扩容,通过调用父类 ensureCapacityInternal() 方法对底层数组进行扩容,保持引用不变。
private void ensureCapacityInternal(int minimumCapacity) {
int oldcapacity = value.length >> coder;
if (minimumCapacity - oldCapacity > 0){
value = Arrays.copyOf(value, newCapacity(minimumCapacity) << coder);
}
StringBuffer 的常用方法,StringBuffer 是线程安全的,但是效率较低,StringBuilder 是线程不安全的,但是效率较高。
| 方法 | 描述 |
|---|---|
| public StringBuffer() | 创建一个空的 StringBuffer对象 |
| public StringBuffer(String str) | 创建一个值为 str 的 StringBuffer 对象 |
| public synchronized int length() | 返回 StringBuffer 的长度 |
| public synchronized char charAt(int index) | 返回指定位置的字符 |
| public synchronized StringBuffer append(String str) | 追加内容 |
| public synchronized StringBuffer delete(int start,int end) | 删除指定区间的值 |
| public synchronized StringBuffer deleteCharAt(int index) | 删除指定位置的字符 |
| public synchronized StringBuffer replace(int start,int end,String str) | 将指定区间的值替换成 str |
| public synchronized String substring(int start) | 截取字符串从指定位置到结尾 |
| public synchronized String substring(int start,int end) | 截取字符串从start开始,到end结束 |
| public synchronized StringBuffer insert(int offset,String str) | 在指定位置插入 str |
| public int indexOf(String str) | 从头开始查找指定字符的位置 |
| public int indexOf(String str,int fromIndex) | 从fromIndex开始查找指定字符的位置 |
| public synchronized StringBuffer reverse() | 进行反转 |
| public synchronized String toString() | 转为 String |
读取数据不需要考虑线程安全问题,因为这种操作不存在安全隐患。
日期类- java.util.Date
Date 对象表示当前的系统时间
- java.util.Calendar
Calendar 用来完成日期数据的逻辑运算
运算思路:(op+com+t)
1、将日期数据传给 Calendar(Calendar 提供了很多静态常量,专门用来记录日期数据)
| 常量 | 描述 |
|---|---|
| public static final int YEAR | 年 |
| public static final int MONTH | 月 |
| public static final int DAY_OF_MONTH | 天,以月为单位 |
| public static final int DAY_OF_YEAR | 天,以年为单位 |
| public static final int HOUR_OF_DAY | 小时 |
| public static final int MINUTE | 分钟 |
| public static final int SECOND | 秒 |
| public static final int MILLSECOND | 毫秒 |
2、调用相关方法进行运算
| 方法 | 描述 |
|---|---|
| public static Calendar getInstance() | 获取Calendar实例化对象 |
| public void set(int field,int value) | 给静态常量赋值 |
| public int get(int field) | 获取静态常量的值 |
| public final Date getTime() | 将Calendar转为Date对象 |
public class Test {
public static void main(String[] args) {
//计算今天所在的周是2020年的第几周
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.YEAR,2020);
//1月为0,4月为3
calendar.set(Calendar.MONTH,3);
calendar.set(Calendar.DAY_OF_MONTH,9);
int week = calendar.get(Calendar.WEEK_OF_YEAR);
System.out.println(week);
//今天之后的63天是几月几号
int days = calendar.get(Calendar.DAY_OF_YEAR);
days += 63;
calendar.set(Calendar.DAY_OF_YEAR,days);
Date date = calendar.getTime();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
System.out.println(simpleDateFormat.format(date));
//今天之前的63天是几月几号
// calendar.set(Calendar.YEAR,2020);
// //1月为0,4月为3
// calendar.set(Calendar.MONTH,3);
// calendar.set(Calendar.DAY_OF_MONTH,9);
calendar.set(Calendar.DAY_OF_YEAR,100);
calendar.set(Calendar.DAY_OF_YEAR,calendar.get(Calendar.DAY_OF_YEAR)-63);
date = calendar.getTime();
System.out.println(simpleDateFormat.format(date));
}
}
文件
File 类
java.io.File,使用该类的构造函数就可以创建文件对象,将硬盘中的一个具体的文件以 Java 对象的形式来表示。
| 方法 | 描述 |
|---|---|
| public File(String pathname) | 根据路径创建对象 |
| public String getName() | 获取文件名 |
| public String getParent() | 获取文件所在的目录 |
| public File getParentFile() | 获取文件所在目录对应的File对象 |
| public String getPath() | 获取文件路径 |
| public boolean exists() | 判断文件是否存在 |
| public boolean isDirectory() | 判断对象是否为目录 |
| public boolean isFile() | 判断对象是否为文件 |
| public long length() | 获取文件的大小 |
| public boolean createNewFile() | 根据当前对象创建新文件 |
| public boolean delete() | 删除对象 |
| public boolean mkdir() | 根据当前对象创建目录 |
| public boolean renameTo(File file) | 为已存在的对象重命名 |
IO
Input 输入流(将外部文件读入到 Java 程序中)
Output 输出流(将 Java 程序中的数据输出到外部)
Java 中的流有很多种不同的分类。
- 按照方向分,输入流和输出流
- 按照单位分,可以分为字节流和字符流(字节流是指每次处理数据以字节为单位,字符流是指每次处理数据以字符为单位)
- 按照功能分,可以分为节点流和处理流。
方法定义时的异常如果直接继承自 Exception,实际调用的时候需要手动处理(捕获异常/丢给虚拟机去处理)
方法定义时的异常如果继承自 RuntimeException,调用的时候不需要处理。
IO流- 按照方向分,可以分为==输入流和输出流==。
- 按照单位分,可以分为==字节流和字符流==。
- 按照功能分,可以分为==节点流和处理流==。
按照方向可以分为输入字节流和输出字节流
//InputStream、OutputStream //1 byte = 8 位二进制数 01010101
InputStream
| 方法 | 描述 |
|---|---|
| int read() | 以字节为单位读取数据 |
| int read(byte b[]) | 将数据存入 byte 类型的数组中,返回数组中有效数据的长度 |
| int read(byte b[],int off,int len) | 将数据存入 byte 数组的指定区间内,返回数组长度 |
| byte[] readAllBytes() | 将所有数据存入 byte 数组并返回 |
| int available() | 返回当前数据流未读取的数据个数 |
| void close() | 关闭数据流 |
OutputStream
| 方法 | 描述 |
|---|---|
| void write(int b) | 以字节为单位输出数据 |
| void write(byte b[]) | 将byte数组中的数据输出 |
| void write(byte b[],int off,int len) | 将byte数组中指定区间的数据输出 |
| void close() | 关闭数据流 |
| void flush() | 将缓冲流中的数据同步到输出流中 |
字节流是单位时间内处理一个字节的数据(输入+输出)
字符流是单位时间内处理一个字符的数据(输入+输出)
字符流:
- 输入字符流 Reader
- 输出字符流 Writer
public avstract class Reader implements Readable,Closeable{}
是一个抽象类
Readable 接口的作用?
可以将数据以字符的形式读入到缓冲区
- 方向:输入+输出
- 单位:字节+字符
- 功能:节点流(字节流) + 处理流(对节点流进行处理,生成其他类型的流)
InputStream(字节输入流) —> Reader(字符输入流)
InputStreamReader 的功能是将字节输入流转换为字符输入流
英文、数字、符号
1 个字节 = 1 个字符
如:a 1 个字符、1 个字节
汉字
1 个字符 = 3 个字节
如:好 1个字符、3 个字节
public class Test {
public static void main(String[] args) throws Exception {
//字符流
Reader reader = new FileReader("/Users/southwind/Desktop/test.txt");
int temp = 0;
System.out.println("*******字符流读取********");
while ((temp = reader.read())!=-1){
System.out.println(temp);
}
reader.close();
//字节流
InputStream inputStream = new FileInputStream("/Users/southwind/Desktop/test.txt");
temp = 0;
System.out.println("*******字节流读取********");
while ((temp = inputStream.read())!=-1){
System.out.println(temp);
}
inputStream.close();
}
}
123456789101112131415161718192021
public class Test2 {
public static void main(String[] args) throws Exception {
Reader reader = new FileReader("/Users/southwind/Desktop/test.txt");
char[] chars = new char[8];
int length = reader.read(chars);
System.out.println("数据流的长度是"+length);
System.out.println("遍历数组");
for (char aChar : chars) {
System.out.println(aChar);
}
}
}
read() 返回的是 int ,直接将字符转成字节(1-1,1-3)
read(char[] chars) 返回的是char数组,直接就返回字符,不会转成字节的。
Writerpublic abstract class Writer implements Appendable,Closeable,Flushable{}
Appendable 接口可以将 char 类型的数据读入到数据缓冲区
OutputStreamWriter 处理流
OutputStreamWriter 的功能是将输出字节流转成输出字符流,与 InputStreamReader 相对应的,将输入字节流转成输入字符流
public class Test {
public static void main(String[] args) throws Exception {
Writer writer = new FileWriter("/Users/southwind/Desktop/copy.txt");
//writer.write("你好");
// char[] chars = {'你','好','世','界'};
// writer.write(chars,2,2);
String str = "Hello World,你好世界";
writer.write(str,10,6);
writer.flush();
writer.close();
}
}
处理流
读文件
public class Test {
public static void main(String[] args) throws Exception {
//基础管道
InputStream inputStream = new FileInputStream("/Users/southwind/Desktop/test.txt");
//处理流
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
char[] chars = new char[1024];
int length = inputStreamReader.read(chars);
inputStreamReader.close();
String result = new String(chars,0,length);
System.out.println(result);
}
}
写文件
public class Test2 {
public static void main(String[] args) throws Exception {
String str = "你好 世界";
OutputStream outputStream = new FileOutputStream("/Users/southwind/Desktop/copy.txt");
OutputStreamWriter writer = new OutputStreamWriter(outputStream);
writer.write(str,2,1);
writer.flush();
writer.close();
}
}
缓冲流
无论是字节流还是字符流,使用的时候都会频繁访问硬盘,对硬盘是一种损伤,同时效率不高,如何解决?
可以使用缓冲流,缓冲流自带缓冲区,可以一次性从硬盘中读取部分数据存入缓冲区,再写入内存,这样就可以有效减少对硬盘的直接访问。
缓冲流属于处理流,如何来区分节点流和处理流?
1、节点流使用的时候可以直接对接到文件对象File
2、处理流使用的时候不可以直接对接到文件对象 File,必须要建立在字节流的基础上才能创建。
缓冲流又可以分为字节缓冲流和字符缓冲流,按照方向再细分,又可以分为字节输入缓冲流和字节输出缓冲流,以及字符输入缓冲流和字符输出缓冲流。
字节输入缓冲流
public class Test {
public static void main(String[] args) throws Exception {
//1、创建节点流
InputStream inputStream = new FileInputStream("/Users/southwind/Desktop/test.txt");
//2、创建缓冲流
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
// int temp = 0;
// while ((temp = bufferedInputStream.read())!=-1){
// System.out.println(temp);
// }
byte[] bytes = new byte[1024];
int length = bufferedInputStream.read(bytes,10,10);
System.out.println(length);
for (byte aByte : bytes) {
System.out.println(aByte);
}
bufferedInputStream.close();
inputStream.close();
}
}
字符输入缓冲流
readLine 方法
public class Test2 {
public static void main(String[] args) throws Exception {
//1、创建字符流(节点流)
Reader reader = new FileReader("/Users/southwind/Desktop/test.txt");
//2、创建缓冲流(处理流)
BufferedReader bufferedReader = new BufferedReader(reader);
String str = null;
int num = 0;
System.out.println("***start***");
while ((str = bufferedReader.readLine())!=null){
System.out.println(str);
num++;
}
System.out.println("***end***,共读取了"+num+"次");
bufferedReader.close();
reader.close();
}
}
字节输出缓冲流
public class Test {
public static void main(String[] args) throws Exception {
OutputStream outputStream = new FileOutputStream("/Users/southwind/Desktop/test2.txt");
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream);
String str = "由于在开发Oak语言时,尚且不存在运行字节码的硬件平台,所以为了在开发时可以对这种语言进行实验研究,他们就在已有的硬件和软件平台基础上,按照自己所指定的规范,用软件建设了一个运行平台,整个系统除了比C++更加简单之外,没有什么大的区别。";
byte[] bytes = str.getBytes();
// for (byte aByte : bytes) {
// bufferedOutputStream.write(aByte);
// }
bufferedOutputStream.write(bytes,9,9);
bufferedOutputStream.flush();
bufferedOutputStream.close();
outputStream.close();
}
}
字符输出缓冲流
public class Test2 {
public static void main(String[] args) throws Exception {
Writer writer = new FileWriter("/Users/southwind/Desktop/test2.txt");
BufferedWriter bufferedWriter = new BufferedWriter(writer);
// String str = "由于在开发语言时尚且不存在运行字节码的硬件平台,所以为了在开发时可以对这种语言进行实验研究,他们就在已有的硬件和软件平台基础上,按照自己所指定的规范,用软件建设了一个运行平台,整个系统除了比C++更加简单之外,没有什么大的区别。";
// bufferedWriter.write(str,5,10);
char[] chars = {'J','a','v','a'};
// bufferedWriter.write(chars,2,1);
bufferedWriter.write(22902);
bufferedWriter.flush();
bufferedWriter.close();
writer.close();
}
}
输入流没有 flush 方法,但不代表它没有缓冲流,输出流是有 flush 方法的,实际开发中在关闭输出缓冲流之前,需要调用 flush 方法。
序列化和反序列化序列化就是将内存中的对象输出到硬盘文件中保存。
反序列化就是相反的操作,从文件中读取数据并还原成内存中的对象。
序列化
1、实体类需要实现序列化接口,Serializable
public class User implements Serializable {
private Integer id;
private String name;
private Integer age;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + ''' +
", age=" + age +
'}';
}
public User(Integer id, String name, Integer age) {
this.id = id;
this.name = name;
this.age = age;
}
}
2、实体类对象进行序列化处理,通过数据流写入到文件中,ObjectOutputStream。
package com.southwind.demo3;
import com.southwind.entity.User;
import java.io.File;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
public class Test {
public static void main(String[] args) throws Exception {
User user = new User(1,"张三",22);
OutputStream outputStream = new FileOutputStream("/Users/southwind/Desktop/obj.txt");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
objectOutputStream.writeObject(user);
objectOutputStream.flush();
objectOutputStream.close();
outputStream.close();
}
}
反序列化
public class Test2 {
public static void main(String[] args) throws Exception {
InputStream inputStream = new FileInputStream("/Users/southwind/Desktop/obj.txt");
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
User user = (User) objectInputStream.readObject();
System.out.println(user);
objectInputStream.close();
inputStream.close();
}
}
IO 流的应用
IO 流就是完成文件传输(上传文件:发朋友圈、换头像,文件下载
- 文本类型的数据(txt、word、Excel、MD)可以使用字符和字节去读取
- 非文本类型的数据(图片、音频、视频)不能用字符去读取,只能用字节去读。
地位:Java 中最核心的模块,Java 之所以称为动态语言的关键,大部分的类库、企业级框架底层都是通过反射来实现的,非常重要。
反射顾名思义就反转执行,生活中的反射就是通过虚像映射到具体的实物,可以获取到实物的某些形态特征。
程序中的反射,通过一个实例化对象映射到类。
一句话理解反射:常规情况下是通过类来创建对象的,反射就是将这一过程进行反转,通过对象来获取类的信息。
通过对象来获取类的信息
类的信息我们也同样使用对象来描述,Class 类专门用来描述其他类的类,每一个 Class 的实例化对象都是对某个类的描述。
Class 是反射的源头
如何来创建 Class 的对象?
1、调用 Class 的静态方法 forName(String name),将目标类的全限定类名(全类名,带着包名的类名)
package com.southwind.demo2;
public class Test {
public static void main(String[] args) throws Exception {
User user = new User();
Class clazz = Class.forName("com.southwind.demo2.User");
System.out.println(clazz.getName());
System.out.println(clazz.getTypeName());
System.out.println(clazz.getSuperclass().getName());
Class[] array = clazz.getInterfaces();
System.out.println("****************");
for (Class aClass : array) {
System.out.println(aClass);
}
}
}
12345678910111213141516
2、通过目标类的 class 创建,Java 中的每一个类都可以调用类.class,class 不是属性也不是方法,叫做“类字面量”,作用是获取内存中目标类型对象的引用(类的结构)。
Class clazz2 = User.class; System.out.println(clazz2.getName()); 12
3、通过目标类的实例化对象获取,getClass()
Class clazz3 = user.getClass(); System.out.println(clazz3.getName()); 12
内部类
- 成员内部类
- 局部内部类
- 匿名内部类
- 静态内部类



