Java 入门基础笔记
标识符
-
标识符必须以 字母 _ $ 开头
-
大小写敏感
-
可包含 字母 _ $
-
不能是 JAVA 的 关键字 int String
-
驼峰规则 javaBasicKonwledge
基本数据类型
数值型 | 字符型 | 布尔型 |
---|---|---|
整数类型(byte short int long) | char 字符型 | true |
浮点型(float double) | String 字符串 是类 不是基本数据类型 | false |
整数类型
1 byte = 8 bit
类型 | 占用存储空间 | 表数范围 |
---|---|---|
byte | 1 byte | -2^7 ~ 2^7 - 1 |
short | 2 byte | -2^15 ~ 2^15 - 1 |
int | 4 byte | -2^31 ~ 2^31 - 1 |
long | 8 byte | -2^63 ~ 2^63 - 1 |
浮点型
类型 | 占用存储空间 | 表数范围 |
---|---|---|
float | 4 byte | -3.403E38 ~ 3.403E38 |
double | 8 byte | -1.798E308 ~ 1.798E308 |
float 单精度类型 位数可精确7位有效数字
double 精度为float的两倍 双精度类型 绝大部分应用程序都采用double类型
浮点型常量默认是double类型 *如:3.14默认就是double类型 3.14f 3.14F就是float类型
有限空间不能存储无限的小数数据 浮点数存在误差 不能精确表示
如需要进行不产生舍入误差的精确数字计算 使用 BigDecimal 类
字符型
类型 | 占用存储空间 | 表数范围 |
---|---|---|
char | 2 byte | Unicode 字符集 |
转义字符
转义符 都是连在一起中间无空格 | 含义 | Unicode值 |
---|---|---|
\b | 退格(backspace) | \u0008 |
\n | 换行 | \u000a |
\r | 回车 | \u000d |
\t | 制表符(tab) | \u0009 |
\ " | 双引号 | \u0022 |
\ ’ | 单引号 | \u0027 |
\ \ | 反斜杠 | \u005c |
- \n newLine 表示新的一行 到下一行
- \r return 回到该行的行首
布尔型
boolean 占用内存空间 1 byte || 4 byte 不可以使用 0 或 !0 的整数 替代 true false 这点与C语言不同
数据类型的自动和强制转换
自动转型
实线 指无数据丢失的自动类型转换 虚线 指可能会有精度损失的自动类型转换
容量小的数据类型可以自动转化为容量大的数据类型
容量大小指表述范围 不是字节大小
整型常量直接赋值给
byte short char
等类型 只要不超表数范围 便可自动转换算术运算符 两个操作数都是整型 有一个是
long
则结果为long
否则为int
即使有byte 结果也是int
有一个操作数是
double
则结果是double
强制转型
public class Test {public static void main(String[] args) {double a = 3.814;int m = (int) a; //小数部分直接去掉 不进行四舍五入操作 故 m = 3System.out.println(m);} }
运行结果
3
Process finished with exit code 0
字符型跟数字之间的转换 根据
ASCII码
转换当将一种类型强制转换为另一种类型 而又超出了目标类型的表数范围 就会被转化成一个完全不同的值
该值无意义 无需纠结
溢出错误: 操作比较大的数 要留意是否溢出
public class Test {public static void main(String[] args) {int salary = 1000000000; //十亿int year = 30;int total = salary * year; System.out.println(total);} }
运行结果
-64771072
Process finished with exit code 0
long total = salary * year
也不可 虽然total
是long
类型 但后面是int
类型 已经发生溢出可以将
salary
改为long
或者long total = 1L * salary * year
将后面转化为long
类型
1L 需写在前面 先让其转化为 long 防止溢出
基本运算符
算术运算符
二元运算符 需要两个操作数才能完成运算 | 一元运算符 只需要一个操作数就能完成运算 |
---|---|
+ - * / %(取余) | ++ – c = a++ 先赋值再加减 ++a 先加减再赋值 |
扩展运算符
运算符 | 用法举例 | 等效的表达式 |
---|---|---|
+= | a += b | a = a + b |
-= | a -=b | a = a ±b |
*= | a *= b | a = a * b |
/= | a /= b | a = a / b |
%= | a %= b | a = a % b |
关系运算符
运算符 | 含义关系运算符的 返回值都为 boolean 类型 |
---|---|
== | 等于 比较性质的等于 返回值为 boolean 类型 |
!= | 不等于 |
> | 大于 |
< | 小于 |
>= | 大于或等于 |
<= | 小于或等于 |
==比较的是两个对象的引用(即内存地址)是否相等
equals ()比较的是两个对象的值(即内存地址里存放的值)是否相等 hashCode 重写例外
逻辑运算符
运算符 | 说明 短路效率更高 一般使用短路 | |
---|---|---|
逻辑与 | & | 两个操作数为 true 结果才是 true 否则是 false |
逻辑或 | | | 两个操作数有一个是 true 结果就是 true |
短路与 | && | 只要有一个为 false 则直接返回 false |
短路或 | || | 只要有一个为 true 则直接返回 true |
逻辑非 | ! | 取反:!false 是 true !true 是 false |
逻辑异或 | ^ | 相同为 false 不同为 true |
位运算符
位运算符 | 说明 以数据的二进制数进行运算 |
---|---|
~ | 取反 |
& | 按位 与 |
| | 按位 或 |
^ | 按位 异或 |
<< | 左移运算符 左移一位相当于乘2 效率相比直接乘二更高 右移运算符同理 |
>> | 右移运算符 右移一位相当于除2 因为计算机的数据都是以二进制存储的 |
- ~10001110 == 01110001
- 1100 & 1011 == 1000
- 逻辑与运算法则 1看作true 0看作false
- 或 与 异或 同理
- 0011 << 1 == 0110 相当于乘2 1<<4 == 16
- ` >> 同理 <<
字符串连接符
“+” 可以连接两个字符(串)
public class Test {public static void main(String[] args) {String a = "Hello";String b = "Java";String c = a + ' ' + b;System.out.println(c);} }
运行结果
Hello Java
Process finished with exit code 0
条件运算符
a = x? y : z
x语句需要 是
布尔表达式
如 a == 13x = true return y a = y 如果x语句为true 则返回 y 语句的值
x = false return z a = z 反之 则返回 z 语句的值
控制语句
选择结构
if else
语法结构
if( 布尔表达式 ) { 代码块1 }
若是true
则执行代码块1
若是false
则不执行
else { 代码块2 }
若是false
则执行代码块2
可以多层嵌套 实现复杂的功能
switch
语法结构
switch( object ){ case 值1 : {代码块1} case 值2 : {代码块2} // object 符合哪一个 就执行哪一个后面的代码块 default: { 代码块0 } //如果不符合值1 值2 则执行代码块0 }
循环结构
break 直接中断循环 从循环出来
continue 中断本次循环 进行下一次循环
while
语法结构
while( 布尔表达式 ){循环体; }
for
语法结构
for( 初始化部分; 条件判断部分; 迭代因子){循环体; }
- 初始化部分设置循环变量的初值
- 条件判断部分为任意的布尔表达式
- 迭代因子控制循环变量的增减
增强for循环
List<String> arr = new ArrayList<String>; ...//对 arr 赋值 for(String str : arr){System.out.println(str); }
嵌套循环
可解决二维数组
List<ArrayList<String>> arrs = new ArrayList<ArrayList<String>>; ...// 对 arrs 赋值 for(ArrayList arr : arrs){ for(String str : arr){System.out.println(str);}}
方法
声明格式
[多个修饰符] 返回值类型 方法名 ( 形式参数列表 ) {
方法内容;
}
修饰符包括:
static public private protected
等等
示例
简单的实现两数相加的方法
public static int addtion (int a,int b){return a + b; }
形式参数列表 返回值类型
输入> 方法名 输出>
方法的重载
overload
指一个类中可以定义多个方法名相同
但参数不同
的方法 调用时会根据不同的参数自动匹配对应的方法
这些方法是完全不同的 仅仅名称相同而已
构成条件
- 不同的含义:形参类型 形参个数 形参顺序不同
- 只有返回值不同不构成方法的重载
int a(String str){} void a(String str)
- 只有形参的名称不同 不构成方法的重载
int a(String str){} int a(String s){}
示例
public class Test{ static int add(int a,int b){ // 实现返回 输入的两个数字的和 的方法return a + b; } static int add(int a,int b,int c){return a + b + c; // 实现返回 输入的三个数字的和 的方法} }
这两个方法名字相同 形参不同 故构成方法的重载 使用时 根据输入参数的个数调动不同的方法
类和对象
类的定义
三个常见成员 | 负责 |
---|---|
属性 field | 静态特征 ( 数据 ) |
方法 method | 动态行为 ( 对数据进行操作 ) |
构造器 constructor | 初始化对象 |
public class Student{//field 属性 静态特征(数据)int id;String name;int score;int age; //method 方法 动态行为(对数据进行操作)public void study(){System.out.println(name + "is studying");} }
属性的初始化
Java
使用默认的值对其初始化 若没有给其赋予对应的值
数据类型 | 默认值 |
---|---|
整型 | 0 |
浮点型 | 0.0 |
字符型 | ‘\u0000’ |
布尔型 | false |
所有引用类型 其他类 自定义的类 如 上面自定义的 Student 类 | null |
构造方法
constructor
用于对象的初始化 通过 new
关键字来调用构造器 是一种特殊的方法
声明格式
[修饰符] 类名 ( 形参列表 ){
语句;
}
- 构造器通过
new
关键字调用- 构造器虽然有返回值 但是不能定义返回值类型 ( 返回值类型肯定是本类 ) 不能在构造器里使用
retuen
返回某个值- 如果我们没有定义构造器 则编译器会自动定义一个无参的构造器( 方法 ) 如果已定义则编译器不会自动添加
- 构造器的方法名必须和类名一致
示例
public class Student{ //Student 类int id;String name;Student(){} //空的构造器Student(int id;String name){ //自己定义的有参的构造器this.id = id;this.name = name;}public static void main(String[] args){Student stu1 = new Student(); //通过空的构造器new一个Student对象Student stu2 = new Student(5,"GuHeng"); //通过自定义的构造器new一个Student对象} }
类与对象
类
class
对象Object
orinstance
( 实例 ) 含义相同类相当于一个模板 对象是实际存在的一个
Object
汽车图纸 与 汽车 的关系
public class Student{ //Student类 模板 一个学生应具有这些 属性 与 方法int id;String name;int score;int age;public void study(){System.out.println(name + "is studying");} } public class void main (String[] args){Student stu01 = new Student(); //new了一个空的Student对象stu01.id = 5;stu01.name = "GuHeng"; //对一个Student对象进行赋值stu01.study();Student stu02 = new Student();stu02.id = 6;stu02.name = "ZhaiNan";stu02.study();Student stu03 = new Student(); }
类的运用
public class Computer { //Computer类String brand; //牌子int price; //价格 }class Student{ //Student类int id; String sName;int age;Computer computer; //学生有一个电脑void study(){System.out.println(sName + "is studying,my computer is " + computer.brand);}public static void main(String[] args) {Computer ROG = new Computer(); //两台电脑ROG.brand = "ROG 7 plus";ROG.price = 15000;Computer TX = new Computer();TX.brand = "TX 3";TX.price = 8500;Student stu01 = new Student(); //两个学生stu01.id = 22;stu01.sName = "GuHeng";stu01.computer = ROG;stu01.age = 18;stu01.study(); //调用此学生的study()方法Student stu02 = new Student();stu02.id = 22;stu02.sName = "ZhaiNan";stu02.computer = TX;stu02.age = 18;stu02.study();} }
内存
内存模型
简化模型
栈
- 栈描述的是方法执行的内存模型 每个方法被调用都会创建一个栈帧 ( 存储局部变量 操作数 方法出口等 )
- 栈的存储特性是
先进后出 后进先出
- 栈是用
自动分配
速度快
栈是一个连续的内存空间
JVM
为每一个线程都创建一个栈 用于存放该线程执行方法的信息 ( 实际参数 局部变量等 )- 栈属于
线程私有
不能实现线程间的和共享
堆
- 堆用于存储创建好的对象和数组 ( 数组也是对象 )
- 堆是一个
不连续
的内存空间分配灵活
速度慢
JVM
只有一个堆
被所有线程共享 ThreadLocal 可以对数据进行隔离
方法区
- 方法区是
JAVA
虚拟机规范 可以有不同的实现- 方法区实际也是堆 只是是用于存储类 常量相关的信息
- 用来存放程序中永远不变或唯一的内容 (类信息 [
Class
对象 反射机制中会重点讲授 ] 静态变量 字符串常量等 )JVM
只有一个方法区 被所有线程共享
模型演示
public class Person { //一个Person类String name; //Person的属性int age;public void show(){System.out.println("姓名:" + name + "\t" + "年龄:" + age);}Person(String name,int age){ //构造方法this.age = age; this.name = name;}Person(){ }public static void main(String[] args) {Person p1 = new Person(); //为p1 初始化 并赋值p1.name = "张三";p1.age = 24;p1.show();Person p2 = new Person("李四",18);p2.show();} }
one
首先将
类
字符串
加载进方法区
two
- 加载
main
方法到栈 为其存储一个栈帧- 运行第一句代码
Person p1 = new Person();
- 在堆里创建一个Person对象 分配一个内存地址 由于没有设置初值 因此其值都自动设置为默认值
- 刚刚的内存地址给到
main
的栈帧的p1
对象
three
- 运行
p1.name = "张三"; p1.age = 24;
- 方法区中的
"张三"
的内存地址给到 0x23 这个对象的name
- 直接给
age
赋值 24
four
- 运行
p1.show();
show()
方法进入栈 随后用完销毁
five
- 运行
Person p2 = new Person("李四",18);
- 在堆中分配一个内存地址 对
name
age
进行赋值- 内存地址再给到
main
中的p2
- 最后在调用 对象的
show()
方法
six
main
方法走完 销毁main
方法的栈帧 和 堆中相关联的所有对象 与 类
this
this
的本质就是 当前对象的地址- 普通方法中
this
总是指向调用该方法的对象 已经new了一个对象 调用该对象的方法- 构造方法中
this
总是指向正要初始化的对象 还未初始化 调用构造方法初始化一个对象时
public class User2 {int id;String name;String pwd;public User2(){}public User2(int id,String name){//当局部变量名与类的属性名相同时 系统优先指向这个局部变量的参数//需要通过 this 调用 类的那个同名属性System.out.println("正在初始化的对象" + this);this.id = id;this.name = name;}public User2(int id,String name,String pwd){//this 也可以调用重载的构造器this(id,name);this.pwd = pwd;}public void login(){System.out.println("Username:" + this.name + "\nPassword:" + pwd);}public static void main(String[] args) {User2 u = new User2(101,"GuHeng");u.login(); //login 内的 this 显然指的是 u 这个对象} }
static
用
static
声明的变量为静态成员变量 也成为类变量从属于类 只有
一份
在类被载入时被显式初始化一般用
类名.类属性/方法
来调用在
static
方法中不可直接访问非static
成员
static
在内存中的 方法区
静态初始化块
- 构造方法用于对象的初始化
- 静态初始化块 用于类的初始化操作
- 可以初始化
static
属性- 在静态初始化块中不能直接访问 非
static
成员- 类被加载时 首先调用 静态初始化块
class TestStaticBlock{int id;static String company = "MiHoYo";static int companyID = 0;static{ //静态初始化块System.out.println(company);companyID = 100010;}static void printCompany(){System.out.println(company + companyID);}public static void main(String[] args){TestStaticBlock.printCompany();} }
运行结果: MiHoYo MiHoYo100010
Java 的类管理机制
package
JDK
中的主要包
Java 中的常用包 | 说明 |
---|---|
java.lang | 包含一些 java 语言的核心类 如 String Math Integer System Thread 提供常用功能 |
java.awt | 包含了构成抽象窗口的工具集 (abstract window toolkits) 多个类 这些类被用来构建和管理应用程序的图形用户界面 |
java.net | 包含执行与网络相关的操作的类 |
java.io | 包含能提供多种输入/输出功能的类 |
java.util | 包含一些使用工具类 如定义系统特性 使用与日期日历相关的函数 |
package biliStudy.ddd //package 位于类的第一个非注释性的语句import java.lang
- 包内共享类
- 要使用其他包内的类 需要导入
import
静态导入
import static
作用是用于 导入指定类的静态属性和静态方法 这样我们就可以直接使用静态属性和和静态方法
面向对象三大特征
继承
instanceof 运算符 override final
- 继承让我们更容易实现类的扩展 这样就实现了代码的重用 不用再重新发明轮子 don’t reinvent wheels
- 从英文字面意思理解
extends
意思是 扩展 子类是父类的扩展
public class inherit {public static void main(String[] args) {Student student = new Student("GuHeng",110,"Java");student.rest(); //Student 调用了父类的方法student.study();System.out.println(student.name);} }class Person { //一个Person类String name;int height;public void rest(){ //public 可以被子类调用System.out.println("Resting");} }class Student extends Person{ //Student类继承Person类 Student便具备了Person的所有属性与方法String major; //Student类自己的属性 方法public void study(){System.out.println("Student is studying");}public Student(String name,int height,String major){this.name = name;this.height = height;this.major = major;} }
Java
中只有单继承 没有C++
那样的多继承Java
中类没有多继承 接口有多继承- 子类继承父类 可以得到父类的全部属性和方法 ( 除了父类的构造方法) 但不见得可以直接访问 父类私有的属性和方法
- 如果定义一个类时 没有调用
extends
则它的父类是java.lang.Object
instanceof
student instanceof Student // student 是 Student 类 则 返回 true 否返回 false
override
子类通过重写父类的方法 可以用自身的行为 替换父类的行为 方法的重写是实现多态的必要条件
须满足的要点
==
方法名 形参列表相同<=
返回值类型和声明异常类型 子类小于等于父类>=
访问权限 子类大于父类
public class TestOverride {public static void main(String[] args) {Horse horse = new Horse();horse.run();horse.stop();Plane plane = new Plane();plane.run();plane.stop();} }class Vehicle { //交通工具类final public void addOil(){} // final 修饰方法 子类无法被重写 可以重载final public void addOil(int a){}public void run(){System.out.println("Run...");}public void stop(){System.out.println("Stop...");} }class Horse extends Vehicle{ //Horse继承Vehicle@Overridepublic void run() {System.out.println("四个蹄子跑...");} }class Plane extends Vehicle{@Overridepublic void run() {System.out.println("Flying");}@Overridepublic void stop() {System.out.println("Stop at airport...");} }
final
- 修饰变量 被他修饰的变量不可改变 一旦赋了初值 就不能被重新赋值
final int MAX_SPEED = 80;
- 修饰方法 该方法不可被子类重写 但是可以被重载
- 修饰类 修饰的类不能被继承 如 Math String等
final class A{}
public class TestFinal {public static void main(String[] args) {final int MAX_SPEED = 120; //常量 只能赋值一次} }
组合
核心“将父类对象作为子类的属性” 然后 “子类通过调用这个属性获得父类的属性和方法”朋友 有事时可以调用朋友的资源
public class TestComposition {public static void main(String[] args) {Stu stu = new Stu("GuHeng",111,"Java");stu.person.rest();System.out.println(stu.major);System.out.println(stu.person.name);} }class Stu {Person person = new Person();String major;public void study(){System.out.println("Stu.study");}public Stu(String name,int height,String major){this.person.name = name; //先调用Person对象 通过这个对象再调用它的属性this.person.height = height;this.major = major;} }
组合和继承
- 组合比较灵活 继承只能有一个父类 但是组合可以有多个属性
- 对于
is-a
关系建议使用继承我是一个学生,是一个人has-a
关系用组合 电脑有一个 CPU 显卡
Object
常用方法
equals
可以自己重写通过比较 类中的某一个属性来判断是否相等hashCode
重写euqals
还需要重写hashCode
改变hashCode值
暂时不用深入理解toString
import java.util.Objects;public class TestObject {public static void main(String[] args) {Employee e1 = new Employee(88,"GuHeng");Employee e2 = new Employee(88,"GuHeng");System.out.println(e1.toString());System.out.println(e1 == e2);System.out.println(e1.equals(e2));} }class Employee extends Object{int id;String name;public Employee(int id,String name){this.id = id;this.name = name;};@Overridepublic String toString() { //重写toString()方法return "EmployeeId: " + id + "\tname: " + name ;}@Overridepublic boolean equals(Object o) { //通过hashCode值来判断是否是同一个对象if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Employee employee = (Employee) o;return id == employee.id;}@Override public int hashCode() { //hashCode 是通过id计算 若id相同便看作为一个对象return Objects.hash(id);} }
super
- “可以看作” 是直接父类对象的引用
- 可以通过
super
来访问父类中被子类覆盖的方法或属性
public class TestSuper {public static void main(String[] args) {Child c = new Child();c.show();} }class Parent{int num = 300;public void show(){System.out.println("Parent.show()");}public Parent(){System.out.println("初始化父类对象...");} }class Child extends Parent{int num = 1000;public Child(){//super(); 不写 Java 也会默认调用System.out.println("初始化子类对象...");}@Overridepublic void show() {System.out.println("Child.show()");super.show();System.out.println("Child.num = " + num);System.out.println("Parent.num = " + super.num);} }
- 在一个类中 若是构造方法的第一行代码没有显示的用
super(...)
或者this(...)
那么Java
默认都会调用super()
含义是调用父类的无参数构造方法 这里的super()
可以省略- 子类创建的整个过程之创建了一个对象
super
的本质就是 当前对象的父类型特征
封装
是对一个类 需要让外部调用者知道的才暴露出来 不需要让外部调用者知道的全部隐藏起来
encapsulation
核心词capsule
胶囊 en 前缀 置于什么之中 tion 后缀 动词名词
优点
- 提高代码安全性
- 提高代码复用性
- 高内聚 封装细节 便于修改内部代码 提高可维护性
- 低耦合 简化外部调用 便于调用者使用 便于扩展和协作
- 高内聚 低耦合 高内聚就是类内部复杂的数据处理尽量自己完成 不让外部知道 低耦合就是尽量方便外部调用者的使用
实现
Java
中是使用 访问控制符 来控制这些细节 通过它我们可以知道哪些属性或方法需要封装 哪些属性和方法需要暴露出去
访问权限修饰符
修饰符 | 同一个类 | 同一个包 | 子类 | 所有类 |
---|---|---|---|---|
private | √ | |||
default | √ | √ | ||
protected | √ | √ | √ | |
public | √ | √ | √ | √ |
private
表示私有 只有自己类能访问default
表示没有修饰符修饰 只有同一个包的类才能访问protected
表示可以被同一个包的类以及其他包中的子类访问public
表示可以被该项目中的所有包中的所有类访问
封装简单规则
- 属性一般用
private
访问权限- 属性私有后 提供相应的
get set
方法来访问相关属性 这些方法通常是public
修饰的 以提供对属性的赋值与读取操作 (booolean
变量的get
方法是is
开头isBoolean()
)- 方法 一些只用于本类的辅助性方法可以用
private
修饰 希望其他类调用的方法用public
修饰
多态
多态指的是同一个方法调用 由于对象不同可能会有不同的行为
- 多态是方法的多态 不是属性的多态 多态与属性无关
- 多态的存在要有 3 个必要条件 继承 方法重写 父类引用指向子类对象
- 父类引用指向子类对象后 用该父类引用调用子类重写的方法 此时多态就出现了
class TestPolymorphism{public static void main(String[] args) {Dog d = new Dog();animalCry(d);animalCry(new Cat());Animal animal = new Dog(); // animal 为 Animal类父类 无法执行子类 Dog类中的方法Dog d2 = (Dog) animal; //可以强制转型 因为他本来就是Dog对象// Cat c2 = (Cat) animal; 编译时不报错 但运行时会有 ClassCastException Dog不能变成Cat}static void animalCry(Animal animal){ //静态代码块 先执行这个块animal.shout();} }public class Animal { //父类public void shout(){System.out.println("Animal shout.");} }class Dog extends Animal{ //子类@Overridepublic void shout() {System.out.println("汪汪汪!");} }class Cat extends Animal{ //子类@Overridepublic void shout() {System.out.println("喵喵喵!");} }
抽象
抽象 “抽出像的部分” 即 “抽出共同特征” abstract
抽象类
- 包含抽象方法的类就是抽象类
- 通过抽象类 我们就可以做到严格限制子类的设计 使子类之间更加通用
public abstract class Animal { //抽象类Animalpublic abstract void shout(); //抽象方法public void sleep(){System.out.println("Test.Animal.sleep");}public static void main(String[] args) {// Test.Animal a = new Test.Animal() 不能这样实例化抽象类 需要用它的实现类Dog d = new Dog();d.shout();} }class Dog extends Animal{ //抽象类的实现类@Overridepublic void shout() { //需要重写所有抽象类中的抽象方法System.out.println("汪汪汪!");}@Overridepublic void sleep() { //也可重写普通啊方法super.sleep(); //缺省实现 便会调用父类的该方法} }class Cat extends Animal{ //抽象类的实现类@Overridepublic void shout() {System.out.println("喵喵喵!");} }
- 有抽象方法的类只能定成抽象类 有抽象方法一定抽象类 抽象类不一定有抽象方法
- 抽象类不能实例化 既不能用
new
来实例化抽象类- 抽象类可以包含 属性 方法 构造方法 但是构造方法不能用来
new
实例 只能用来被子类调用- 抽象类只能用来被继承
- 抽象方法必须被子类实现
- 类比 猫科动物(抽象类) 猫 虎(实现类) 简单的类比
public abstract class DBOperator { //数据库//1.建立连接 2.打开数据库 3.使用数据库 4.关闭连接public abstract void connection();public void open(){System.out.println("打开数据库");}public void use(){System.out.println("使用数据库");}public void close(){System.out.println("关闭数据库");}public void process(){connection();open();use();close();}public static void main(String[] args) {new MySql().process();new OracleOperator().process();} }class MySql extends DBOperator{ //我的数据库@Overridepublic void connection() {System.out.println("建立和 MySql 连接");} }class OracleOperator extends DBOperator{ //Oracle数据库@Overridepublic void connection() {System.out.println("建立和 OracleOperator 连接");} }
抽象方法
- 使用
abstract
修饰的方法 没有方法体 只有声明- 定义的是一种“规范” 就是告诉子类必须要给抽象方法提供具体的实现
接口
接口全面的实现了 规范和具体实现的分离
区别
- 普通类 具体实现
- 抽象类 具体实现 规范( 抽象方法 )
- 接口 规范 简单类比 数据线 type-C lighting USB-A 接口规范
声明格式
[访问修饰符] interface 接口名 [extends 父接口1, 父接口2…] {
常量定义;
方法定义;
}
- 子类通过
implement
来实现接口中的规范- 接口不能创建实例 但是可用于声明引用变量类型 List arr = new ArrayList<>()
- 一个类实现了接口 必须实现接口中所有的方法 并且这些方法只能是
public
的JDK1.8 (不含8)
之前 接口中只能包含静态变量 抽象方法 不能有普通属性 构造方法 普通方法JDK1.8 (含8)
之后 接口中可包含普通的 静态方法 默认方法
public class TestInterface {public static void main(String[] args) {Volant a = new Angel();a.fly();Honest h = new Angel();h.helpOthers();Angel GuHeng = new Angel();GuHeng.fly();GuHeng.helpOthers();new BirdMan().fly();} }interface Volant{int FLY_HEIGHT = 100; // public static finalvoid fly(); //自动加 public abstract }interface Honest{void helpOthers(); }class Angel implements Volant, Honest { //Angel类继承 Volant Honest 两个接口@Overridepublic void fly() {System.out.println("Angel.fly");}@Overridepublic void helpOthers() {System.out.println("Angel.helpOthers");} }class BirdMan implements Volant {@Overridepublic void fly() {System.out.println("BirdMan.fly");} }
接口的多继承
public class TestInterface2 implements C{ //继承C接口 需要实现A B C 三个接口的全部方法@Overridepublic void testA() { System.out.println("A");}@Overridepublic void testB() {System.out.println("B");}@Overridepublic void testC() {System.out.println("C");}public static void main(String[] args) {C c = new TestInterface2();c.testA();c.testB();c.testC();} }interface A{void testA(); }interface B{void testB(); }interface C extends A,B{ //接口C 继承 A B 两个接口void testC(); }
默认方法
- 默认方法用
default
关键字 一个接口中可以有多个默认方法 默认方法也叫扩展方法- 子类实现接口时 可以直接调用接口中的默认方法 也可以重写
静态方法
- 接口中同时还可以定义静态方法 静态方法通过接口名调用
- 如果实现类中定义了相同名字的静态方法 那就是完全不同的方法了 直接从属于实现类 通过类名直接调用
class TestInterface3 {public static void main(String[] args) {TestA testA = new TestA();testA.defaultA();testA.defaultB();A1.static1();} }interface A1{default void defaultA(){System.out.println("A1.defaultA");}default void defaultB(){System.out.println("A1.defaultB");}static void static1(){System.out.println("Hello");} }class TestA implements A1{@Overridepublic void defaultB() {System.out.println("TestA.defaultB");} }
命名冲突问题
如果方法名和形参列表相同 会发生命冲突
- 父类优先 如果父类的方法和接口默认方法名冲突 则父类优先
- 接口冲突 如果多个接口中默认方法有相同改名字 则子类必须 重写这些同名的方法 成为他自己有一个的方法
class TestInterface3 {public static void main(String[] args) {TestA testA = new TestA();testA.defaultA();new TestFather().defaultA(); //父类优先 故执行TestA的方法} }interface A1{default void defaultA(){System.out.println("A1.defaultA");} }interface A2{default void defaultA(){System.out.println("A1.defaultA");} }class TestA implements A1, A2{//因为两个接口有方法重名了 所以必须重写此重名方法@Overridepublic void defaultA() {System.out.println("TestA.defaultA");} }class TestFather extends TestA implements A1{}
内部类
- 内部类是一类特殊的类 指的是定义在一个类内部的类
- 实际开发中 为了方便的使用外部类的相关属性和方法 这时候我们通常会定义一个内部类 内部类可以直接访问外部类的私有属性 内部类被当成其外部类的成员 但外部类不能访问内部类的内部属性
- 内部类提供了更好的封装 只能让外部类直接访问
- 人 和 内部的器官 人可以访问器官但不能访问其内部的细节内部属性 外界也不能直接访问人的内部器官
非静态内部类
public class Outer1 {private int aage = 18;public void show(){System.out.println("Outer.age: " + age);}//内部类 定义在外部类 Outer 内部//非静态内部类 不能有静态的方法 静态的属性 静态的初始化块class Inner1{private int id = 1001;private int age = 20;public void test1(){TestInnerClass1.showw();System.out.println("Inner.test1");System.out.println("Inner.id: " + id);System.out.println("Inner.age: " + this.age);System.out.println("Outer.age: " + Outer1.this.aage); Outer1.this.show(); //可以直接调用 也可以通过 Outer1.this 调用System.out.println(aage); show();TestInnerClass1.showw();}} }class TestInnerClass1{static void showw(){System.out.println("------------------------------------");}public static void main(String[] args) {//非静态内部类的对象必须寄存在一个外部类对象里//先创建外部类的对象 然后使用外部类对象创建内部类对象Outer1.Inner1 inner1 = new Outer1().new Inner1();inner1.test1();} }
静态内部类
定义方式
static class ClassName{
//类体
}
- 静态内部类可以访问外部类的静态成员 不能访问外部类的普通成员
- 静态内部类看作外部类的一个静态成员
public class Outer2 {private int a = 10;private static int b = 20;//静态内部类static class Inner2{public void test(){//System.out.println(a); //静态内部类不能访问外部类的 普通属性/方法System.out.println(b); //静态内部类可以访问外部类的 静态属性/方法}} }class TestStaticInnerClass{public static void main(String[] args) {//通过 new 外部类名.内部类名() 类创建静态内部类Outer2.Inner2 inner2 = new Outer2.Inner2();} }
匿名内部类
适合那种只需要使用一次的类 如 键盘监听操作等等 在 Android开发 awt swing GUI 里开发中常见 (GUI 用户界面编程)
//匿名内部类 public class TestAnonymousInnerClass {public void test(A3 a3){ //参数 创建一个接口的示例对象System.out.println("TestAnonymousInnerClass.test");a3.run();}public static void main(String[] args) {TestAnonymousInnerClass tc = new TestAnonymousInnerClass();//只使用一次tc.test(new A3() { //实现类 没有名字 只在这里用一次@Overridepublic void run() {System.out.println("AnonymousClass.run");}});tc.test(new A3() {@Overridepublic void run() {System.out.println("Second anonymous class.");}});} }interface A3{ //一个接口void run(); }
局部内部类*
定义在方法内部的 作用域只限于本方法 成为局部内部类
局部内部类在实际开发中应用很少 作为基本知识点了解即可
//局部内部类 public class TestLocalInnerClass {public static void main(String[] args) {new TestLocalInnerClass().show();}public void show(){System.out.println("Step1");//局部内部类 作用域仅限于此 show() 方法class Inner3{public void run(){System.out.println("Inner3.run");}}new Inner3().run();} }
数组
数组的定义和本质
int[] a = new int[5]; Car[] cars = new Cars[5]; //数组是相同类型数据的有序集合
- 长度是确定的 数组一旦被创建 它的大小就是不可改变的
- 其元素的类型必须是相同类型 不允许出现混合类型
- 数组类型可以是任何数据类型 包括基本类和引用类型
- 数组也是对象 数组中的元素相当于该对象的成员变量
public class Test01 {public static void main(String[] args) {//数组的定义int[] a = new int[5];int b [] = new int[6];//静态初始化int[] c = {5,8,4,1,0,6};//默认初始化System.out.println(a[0]);//数组的初始化a[0] = 10;a[1] = 20;a[2] = 30;a[3] = 40;a[4] = 50;for (int i = 0;i < b.length;i++){b[i] = i * 10;System.out.println(b[i]);}//通过 增强 for循环 遍历数组for (int temp : c){System.out.println(temp);}} }class Test02{public static void main(String[] args) {//不同方式的初始化User[] users = new User[3];User[] users1 = {new User(511,"WuYaoyao"),new User(111,"BaiXiaohua")};users[0] = new User(10,"GuHeng");users[1] = new User(11,"XiaoYu");users[2] = new User(12,"Wuyaoyao");//增强for循环 for (User user : users){System.out.println(user.toString());}} }class User{ //User类 引用类型private int id;private String name;public User(int id, String name) {this.id = id;this.name = name;}@Overridepublic String toString() {return "User{" +"id=" + id +", name='" + name + '\'' +'}';} }class Test03{public static void main(String[] args) {String[] a = {"GuHeng","JiangXiaoyu","WuYaoyao","BaiJi","TangXin"};String[] b = new String[6];System.arraycopy(a,1,b,0,2);for (String temp : b){System.out.println(temp);}} }
Arrays 工具类
class Test04{public static void main(String[] args) {int[] c = {54,57,95,6,44};//遍历打出来 [54,57,95,6,44]System.out.println(c);System.out.println(Arrays.toString(c));//从小到大排序Arrays.sort(c);System.out.println(Arrays.toString(c));//二分查找法 需要先从小到大排好序System.out.println(Arrays.binarySearch(c,100));//填充数组int[] d = new int[10];Arrays.fill(d,100);System.out.println(Arrays.toString(d));} }
多维数组
多维数组可以看成以数组为元素的数组
int[][] a = {{1,2,3,4},{1,2,3},{1,2} } //所有元素数组 的 length 相等每个元素初始为默认值
//二维数组 public class Test05 {public static void main(String[] args) {int [][] a = new int[3][];a[0] = new int[2];a[1] = new int[4];a[2] = new int[3];a[0][0] = 10;a[0][1] = 20;a[1] = new int[]{10,20,30,40};a[2] = new int[]{10,20,30};int[][] b = {{10,20,30},{10,20},{10,20,30,50},};//嵌套循环for (int i=0;i<b.length;i++){for (int j=0;j<b[i].length;j++){System.out.print(b[i][j] + " ");}System.out.println();}for (int i=0;i<b.length;i++){System.out.println(Arrays.toString(b[i]));}} }
ID | 姓名 | 年龄 | 职能 | 入职日期 |
---|---|---|---|---|
1001 | 谷恒条野 | 18 | 打游戏 | 2019-2-30 |
1002 | 江小鱼 | 18 | 病娇 | 2014 |
1003 | 巫瑶瑶 | 18 | 粉毛狐狸 | 2002 |
//利用 Object 数组存储表格信息 public class Test06 {public static void main(String[] args) {Object[] a1 = {1001,"谷恒条野",18,"打游戏","2019-2-30"}; //包装类Object[] a2 = {"江小鱼"};Object[] a3 = {"巫瑶瑶"};System.out.println(Arrays.toString(a1));Object[][] emps = new Object[3][];emps[0] = a1;emps[1] = a2;emps[2] = a3;for (Object[] emp : emps){System.out.println(Arrays.toString(emp));}Emp e1 = new Emp(1001,"谷恒条野",18,"打游戏","2019-2-30");Emp e2 = new Emp(1002,"江小鱼",18,"病娇","2019-2-30");Emp e3 = new Emp(1003,"巫瑶瑶",18,"粉毛狐狸","2019-2-30"); Emp[] emps1 = {e1,e2,e3};for (Emp emp : emps1){System.out.println(emp.toString());}} }class Emp{private int id;private String name;private int age;private String job;private String hireDate;@Overridepublic String toString() {return "Emp{" +"id=" + id +", name='" + name + '\'' +", age=" + age +", job='" + job + '\'' +", hireDate='" + hireDate + '\'' +'}';}public void setId(int id) {this.id = id;}public void setName(String name) {this.name = name;}public void setAge(int age) {this.age = age;}public void setJob(String job) {this.job = job;}public void setHireDate(String hireDate) {this.hireDate = hireDate;}public int getId() {return id;}public String getName() {return name;}public int getAge() {return age;}public String getJob() {return job;}public String getHireDate() {return hireDate;}public Emp(int id, String name, int age, String job, String hireDate) {this.id = id;this.name = name;this.age = age;this.job = job;this.hireDate = hireDate;} }
排序算法
//冒泡排序 增加功能 可以提前判断数组是否达到有序状态 提前结束排序 public class Test07 {public static void main(String[] args) {int[] values = {2,5,7,4,6,9,1,3,8,0};System.out.println("排序前: " + Arrays.toString(values));//标记数组是否达到有序状态boolean flag = true;for (int i=0;i<values.length;i++){System.out.println(i);for (int j=i+1;j<values.length;j++){if (values[i] > values[j]){int temp = values[j];values[j] = values[i];values[i] = temp;System.out.println(j + Arrays.toString(values));//发生了交换 表明数组仍处于无序状态 还需要继续排序flag = false;}}if (flag){break; //已经有序 则直接中断循环}else {flag = true; //重置}}System.out.println("排序后: " + Arrays.toString(values));} }
//冒泡排序 简化 class Sort {public static void main(String[] args) {int[] values = {2,5,7,4,6,9,1,3,8,0};boolean flag = true;for (int i=0;i<values.length;i++){for (int j=i+1;j<values.length;j++){if (values[i] > values[j]){int temp = values[i];values[i] = values[j]; values[j] = temp;flag = false;}}if (flag) break;else flag = true;}System.out.println(Arrays.toString(values));} }
二分查找法
类似于 高中 二分法求根
public class Test08 {public static void main(String[] args) {int[] arr = {10,20,30,40,50,60,70,80,90,};int searchInt = 20;//二分查找的数组 必须先排序Arrays.sort(arr); //从小到大排序System.out.println(Arrays.toString(arr));System.out.println(binarySearch(arr,20));}public static int binarySearch(int[] array,int value){int low = 0; //左边界int high = array.length-1; //右边界while(low <= high){int middle = (low + high) / 2; //中间位if (value == array[middle]) return middle; //相等则已找到目标值 返回索引即可//目标值较小 说明目标值在middle左边if (value <= array[middle]) return high = middle - 1; //目标值较大 说明目标值在middle右边 if (value >= array[middle]) return low = middle + 1;}return -1; //表示没有目标值 则返回一个负数}}
常用类
String
String
类又可称为不可变字符序列String
位于java.lang
包中Java
程序默认导入java.lang
包下的所有类Java
字符串就是Unicode
字符序列 例如字符串 “Java” 就是4个Unicode
字符 “J” “a” “v” “a” 组成的Java
没有内置的字符串类型 而是在标准Java
类库中提供了一个预定的类String
每个双引号括起来的字符串都是String
类的一个实例
public class TestString {public static void main(String[] args) {test1();}public static void test1(){//String类的定义String s1 = "abc"; //凡是字符串常量 都会放到字符串的常量池里String s2 = new String("abc");String s3 = "abc";String s4 = "aBC";System.out.println(s1 == s2); // F 内存地址不同 s2是 new 了一个新的对象System.out.println(s1 == s3); // T 内存地址相同System.out.println(s1.equals(s2)); //字符串的值是否相等TSystem.out.println(s1.equalsIgnoreCase(s4)); //忽略大小写 T} }
==比较的是两个对象的引用(即内存地址)是否相等
equals ()比较的是两个对象的值(即内存地址里存放的值)是否相等 hashCode 重写例外
方法 返回值 + 函数名 + (参数) | 解释说明 |
---|---|
char charAt (int index) | 返回字符串中第 index 个字符 |
boolean equals (String other) | 如果字符串与 other 相等 返回 true 否则返回 false |
boolean equalsIgnoreCase (String other) | 忽略大小写 如果字符串与 other 相等 返回 true 否则返回 false |
int indexOf (String str) | 返回从头开始查找第一个子字符串 str 在字符串中的索引位置 如果未找到子字符串 str 则返回 -1 |
int lastIndexof () | 返回从末尾开始查找第一个子字符串 str 在字符串中的索引位置 如果未找到子字符串 str 则返回 -1 |
int length() | 返回字符串的长度 |
String replace (char oldChar, char newChar) | 返回一个新的字符串 它是通过 newChar 替换此字符串中出现的所有 oldChar 而生成的 |
boolean startsWith (Sting prefix) | 如果字符串以 prefix 开始 则返回 true |
boolean endsWith (String prefix) | 如果字符串以 prefix 结束 则返回 true |
String substring (int beginIndex) | 返回一个新字符串 该字符串饱饭从原始字符串 beginIndex 到串尾 |
String substring (int begingIndex, int endIndex) | 返回一个新字符串 该字符串饱饭从原始字符串 beginIndex 到串尾 或 endIndex-1 的所有字符 |
String toLowerCase() | 返回一个新字符串 该串将原始字符串中的所有大写字母改成小写字母 |
String toUpperCase() | 返回一个新字符串 该串将原始字符串中的所有小写字母改为大写字母 |
String trim() | 返回一个新字符串 该串删除了原始字符穿头部和尾部的空格 |
public static void test2(){String s1 = "0123456789,How are you,I am fine,Thank you.";System.out.println(s1.charAt(5));System.out.println(s1.length());//准话为数组char[] chars = s1.toCharArray();System.out.println(Arrays.toString(chars));String[] strings = s1.split(","); //可以传入正则表达式System.out.println(Arrays.toString(strings));//是否包含某字符串System.out.println(s1.indexOf("are"));System.out.println(s1.lastIndexOf("are"));System.out.println(s1.indexOf("o"));System.out.println(s1.lastIndexOf("o"));System.out.println(s1.contains("you"));System.out.println(s1.startsWith("0123"));System.out.println(s1.endsWith("you"));}public static void test3(){String s1 = "0123456789,How are you,I am fine,Thank you.";String s2 = s1.replace(" ","[]");System.out.println(s1);System.out.println(s2);s2 = s1.substring(4);System.out.println(s2);System.out.println(s1.substring(4,10)); // [4,10) 包括4不包括10System.out.println(s1.toUpperCase());System.out.println(s1.toLowerCase());//去首尾空格String s3 = " How are you? ";System.out.println(s3);System.out.println(s3.trim());}
StringBuilder StringBuffer
- String 不可变字符序列
- StringBuffer StringBuilder 可变字符序列 方法一模一样
- StringBuffer 线程安全 做线程同步检查 效率较低
- StringBuffer 线程不安全 不做线程同步检查 因此效率较高 建议采用该类
public class TestStringBuilder {public static void main(String[] args) {test2();}public static void test1(){String s = "";StringBuilder sb = new StringBuilder();StringBuffer sb2 = new StringBuffer();sb.append("a");sb.append(" hello");sb.append(" c").append("dd").append("ee"); //向sb后面直接添加对应字符串System.out.println(sb);sb2.append("GuHeng");sb2.insert(0,"0").insert(0,1); //向指定索引插入指定字符串System.out.println(sb2);sb2.delete(0,2); // [0,2) //删除指定索引范围的字符System.out.println(sb2);sb2.deleteCharAt(1); //删除指定索引的字符System.out.println(sb2);System.out.println(sb2.reverse()); //字符串逆序}public static void test2(){//使用 String 进行字符串的拼接 //工作不能用String str = "";long num1 = Runtime.getRuntime().freeMemory(); //获取JVM剩余的内存空间 Bytelong time1 = System.currentTimeMillis(); //获取当前时间 mmfor (int i=0;i<=5000;i++){str += i; //相当于产生了 5000 个 String 对象}long num2 = Runtime.getRuntime().freeMemory(); //获取JVM剩余的内存空间 Bytelong time2 = System.currentTimeMillis(); //获取当前时间System.out.println("Use Time: " + (time2 - time1));System.out.println("Use Memery: " + (num1 - num2));System.out.println("StringBuilder");StringBuilder sb = new StringBuilder();for (int i=0;i<=500000;i++){sb.append(i); //只有一个 sb 对象}long num3 = Runtime.getRuntime().freeMemory(); //获取JVM剩余的内存空间 Bytelong time3 = System.currentTimeMillis(); //获取当钱时间System.out.println("Use Time: " + (time3 - time2));System.out.println("Use Memery: " + (num2 - num3));} }
运行后易看出StringBuilder使用的内存更少 消耗时间也更短
包装类
前面讲的八种基本数据类型 对应八种包装类
基本数据类型 | 包装类 |
---|---|
byte | Byte |
boolean | Boolean |
short | Short |
char | Character |
int | Integer |
long | Long |
float | Float |
double | Double |
JAVA
是面向对象的语言 但并不是 “纯面向对象” 不像Python
一切都是对象java
中 基本数据类型不是对象 这时候 我们可以用包装类把基本数据类型变成对象
public class TestInteger {public static void main(String[] args) {testCache();}public static void testInteger(){//基本数据类型 转化成 Integer 对象Integer int1 = Integer.valueOf(100);//包装类对象 转换成 基本数据类型int int2 = int1.intValue();long long1 = int1.longValue();//字符串转成 Integer 对象Integer int3 = Integer.parseInt("324"); //全数字可以转System.out.println(int3);System.out.println(int3.toString()); //包装类对象转为字符串System.out.println("int.MAX: " + Integer.MAX_VALUE);}public static void testAutoBox(){Integer a = 100; //自动装箱 编译器添加:Integer a = Integer.value.valueOf(100);int b = a; //自动拆箱 编译器添加:int b = a.intValue();//空指针异常Integer c = null;int d = c; //自动拆箱 int d = c.intValue();}public static void testCache(){//整形 char 类型 所对应的包装类 在自动装箱时,对于 -128 ~ 127 之间的值会进行缓存的处理 为了提高效率Integer a = Integer.valueOf(100);Integer b = 100;System.out.println(a == b); //是同一个对象Integer c = 300;Integer d = 300;System.out.println(c == d); //不是同一个对象int e = 500;int f = 500;System.out.println(e == f);}}//此包装类仅限于学习娱乐 class MyInteger{private int value; //值private static MyInteger[] cache = new MyInteger[256]; //提前缓存一些数字 提高效率public static final int LOW = -128; //最小值public static final int HIGH = 127; //最大值//静态初始化块 是在类被加载的时候 初始化类的静态属性static{for (int i=LOW;i<=HIGH;i++){//[0] -128 [256] 127cache[i - LOW] = new MyInteger(i);}}public static MyInteger valueOf(int i){//如果在 [-128,127] 则返回数组中缓存的对象 否额创建新的对象 // if (i>=-128 && i<=127) return cache[i-LOW]; // else return new MyInteger(i);return i>=-128 && i<=127 ? cache[i-LOW] : new MyInteger(i);}public int intValue(){return value; //返回int的值}@Overridepublic String toString() {return value + "";}private MyInteger(int i){ //构造器this.value = i;}public static void main(String[] args) {MyInteger a = MyInteger.valueOf(100); MyInteger b = MyInteger.valueOf(100);System.out.println(a == b); //引用的都是同一个缓存对象 所以是 trueMyInteger c = MyInteger.valueOf(400);MyInteger d = MyInteger.valueOf(400);System.out.println(c == d); //都是new的对象 所以是 false} }
时间转化字符串
1970.1.1 00:00:00
是时间零点 单位 mm
long
字母了解 | 日期或时间元素 | 示例 |
---|---|---|
G | Era 标志符 | AD/BC |
y | 年 | 1996 , 97 |
M | 年中的月份 | July , Jul , 07 |
w | 年中的周数 | 27 |
W | 月份中的周数 | 2 |
D | 年中的天数 | 189 |
d | 月份中的天数 | 10 |
F | 月份中的星期 | 2 |
E | 星期中的天数 | Tuesday , Tue |
a | AM/PM 标记 | PM |
H | 一天中的小时数(0-23) | 0 |
k | 一天中的小时数(1-24) | 24 |
K | am/pm 中的小时数(0-11) | 0 |
h | am/pm 中的小时数(1-12) | 12 |
m | 小时中的分钟数 | 30 |
s | 分钟中的秒数 | 55 |
S | 毫秒数 | 999 |
z | 时区 | Pacific Standard Time , PST , GMT-08:00 |
Z | 时区 | 0800 |
import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar;public class TestDate { public static void main(String[] args) throws ParseException {test3(); }//Date 类 public static void test1(){long nowNum = System.currentTimeMillis(); //返回当前的时刻的毫秒数 前后两亿年System.out.println(nowNum);Date d1 = new Date(1000);System.out.println(d1);System.out.println(new Date());Date d2 = new Date(1000L * 3600 * 24 * 365 * 150); //距离1970年150年后忽略日月System.out.println(d2);Date d3 = new Date();d3.getDate(); //方法被废弃 可以用 但不建议 }//DateFormat 类 public static void test2() throws ParseException {DateFormat df = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");Date d2 = new Date(1000L * 3600 * 24 * 365 * 150);String str2 = df.format(d2);System.out.println(str2);String str3 = "2005-8-17 12:45:11";Date d3 = df.parse(str3);System.out.println(d3);System.out.println(d3.getTime());DateFormat df2 = new SimpleDateFormat("yyyy年MM月dd日");//利用格式化字符可以方便的做一些其他的事 获取当前时间是今年底几天DateFormat df3 = new SimpleDateFormat("D");Date date3 = new Date();System.out.println(df3.format(date3)); }public static void test3(){//月份 : 0-11 0:1月 11:12月//星期 : 1-7 1:周日 7:周六//年 月 日 时 分 秒Calendar calendar = new GregorianCalendar(2999,6,7,9,0,25);int year = calendar.get(Calendar.YEAR); //取年份int month = calendar.get(Calendar.MONTH); //取月份System.out.println(year);System.out.println(month);calendar.set(Calendar.YEAR,2042); //设置年份为 2042System.out.println(calendar.get(Calendar.YEAR));System.out.println(calendar.getTime()); //返回对应的Date对象System.out.println(calendar.getTimeInMillis()); //返回对应的毫秒数//日期的计算calendar.add(Calendar.DATE,1000); //天数System.out.println(calendar.getTime());calendar.add(Calendar.YEAR,-45); //往前减45年System.out.println(calendar.getTime()); } }
异常机制
public class Test {public static void main(String[] args) {//将 D:\\a.txt 复制到 E:\\a.txt//仅仅演示 不具实际意义if ("D:\\a.txt"这个文件存在){if (E盘空间大于a.txt文件长度){if (文件复制到一般IO流断掉) 停止copy 输出:IO流出现问题else copyFile("D\\a.txt","E:\\a.txt");}else System.out.println("E盘空间不足!");}System.out.println("a.txt不存在!");//使用异常处理try{copyFile(new File("D:\\a.txt"),new File("E:\\a.txt"));} catch (IOException e) {e.printStackTrace();}} }
异常 exception
所谓异常处理 就是指程序在出现问题时依然可以正确的执行完
Java
面向对象处理异常过程
- 抛出异常 在执行一个方法时 如果发生异常 则把这个方法生成代表该异常的一个对象 停止当前执行路径 并把异常对象交给
JRE
- 捕获异常
JRE
得到该异常后 寻找下相应的代码来处理该异常JRE
在方法的调用栈中查找 从生成异常的方法开始回溯 直到找到相应的异常处理代码为止
public class Test01 {public static void main(String[] args) {test3();}public static void test1(){System.out.println("111");int i = 1 / 0; System.out.println("222");}public static void test2(){System.out.println("1");try{int i = 1 / 0; //ctrl+alt+t 快捷键增加 try catch}catch (Exception e){e.printStackTrace();}System.out.println("2");}// RunTimeException 需要程序员做逻辑处理public static void test3(){int a = 1;int b = 0;//ArithmeticException//System.out.println(a/b);if (b != 0){System.out.println(a / b);}//空指针异常 NullPointerExceptionString string = null;if (string != null) {System.out.println(string.charAt(2));}//数字格式化异常 NumberFormatExceptionString string1 = "23aaa";//int c = Integer.parseInt(string1);//System.out.println(c);Pattern pattern = Pattern.compile("\\d + $");Matcher matcher = pattern.matcher(string1);if (matcher.matches()){System.out.println(Integer.parseInt(string1));}else {System.out.println("数字格式不符");}//数组下标越界异常 ArrayIndexOutOfBoundsExceptionint[] ints = new int[5];int f = 5;if (f >= 0 && f < ints.length) {System.out.println(ints[f]);}//类型转化异常 ClassCastExceptionAnimal animal = new Dog();//Cat cat = (Cat) animal;if (animal instanceof Cat){Cat cat = (Cat) animal;}} }class Animal{}class Dog extends Animal{}class Cat extends Animal{}
public class Test02 {public static void main(String[] args) throws ParseException {System.out.println(test4());;}// try-catch-finally 无论如何都要在最后 执行finally中的语句public static void test1(){FileReader reader = null;try {reader = new FileReader("E:/TestFile/Test.txt"); //这里需要输入自己的文件路径char c1 = (char) reader.read();char c2 = (char) reader.read();System.out.println("File context: " + c1 + c2);} catch (IOException e) {e.printStackTrace();} finally {if (reader != null){try {reader.close();} catch (IOException e) {e.printStackTrace();}}}}//测试 throws 声明式抛出异常public static void test2() throws ParseException {DateFormat df = new SimpleDateFormat("yyyy-MM-dd");String string = "2042-5-4";Date d = df.parse(string);System.out.println(d);}// try-with-resource 可以自动关闭实现了 AutoClosable// try-catch-finally -> try-catch//一种语法糖 编程器帮我们做了处理 转化成了 try-catch-finallypublic static void test3(){try(FileReader reader = new FileReader("E:/TestFile/Test.txt")){char c1 = (char) reader.read();char c2 = (char) reader.read();System.out.println("File context: " + c1 + c2);}catch(Exception e){e.printStackTrace();}}/*** 任何执行 try 中的 return 语句之前都会先执行 finally 语句 (如果 finally 语句存在)* 如果 finally 中也有 return 则直接 return* @return*/public static int test4(){int a = 10;System.out.println("--Step1--");try{System.out.println("--Step2--");a = 20;//执行这句话之前 会先执行 finally 中的语句 finally 也有 return 就执行 finally 中的 returnreturn a;} catch (Exception e){e.printStackTrace();} finally{System.out.println("--Step3--");a = 40;return a;}} }
自定义异常
- 自定义异常类只需从
Exception
类或者它的子类派生一个子类即可- 自定义异常类如果继承
Exception
类 则为受检查异常 必须对其进行处理 如果不想处理 可以让自定义异常类继承运行时异常RuntimeException
类- 习惯上 自定义异常类应该包含 2 歌构造器 一个是默认的构造器 另一个是带有详细信息的构造器
//自定义异常 public class Test03 {public static void main(String[] args) throws IllegalAgeException {Person person = new Person();person.setAge(-888);}}//不合法年龄异常 //为测试而测试 class IllegalAgeException extends Exception{ //extends RuntimeException 只在运行时出现错误报错//默认无参构造器public IllegalAgeException(){}public IllegalAgeException(String message){super(message); //调用父类的方法}}class Person{private String name;private int age;public void setAge(int age) throws IllegalAgeException{if (age < 0){throw new IllegalAgeException("A person's age can't be under 0");}else {this.age = age;}}public int getAge(){return age;} }
Bing/Google 搜索用好的关键: 关键词的确认 正确的提问
ChatGPT Bing 解答
- 寻找问题本身的关键词 n.
- 寻找问题上下文的关键词 n.
- 尽量细致的描述问题 开始搜索
- 如果没找到 慢慢减少关键词 扩大搜索范围
调试
进行调试的核心是设置断点 程序执行到断点时 暂时挂起 停止执行 就像看视频按下停止一样
我们可以详细的观看停止出的每一个细节
中文名称 | 英文名称 | 图标 | 说明 |
---|---|---|---|
单步调试:遇到方法跳过 | step over | 若当前执行的是一个方法 则会把这个方法当作整体一步执行完 不会进入这个方法内部 | |
单步调试:遇到函数进入 | step into | 若当前执行的是一个自定义方法 泽湖进入方法内部 JDK 内置方法不会进去 | |
强制进入 | force step into | 可以跳进任何方法 包含JDK 内置方法 | |
跳出函数 | step out | 当单步执行到子方法内时 用step out 就可以执行完子方法余下部分 并返回到上一层方法 | |
删除栈帧 | drop frame | 删除当前栈帧 跳回到上一个栈桢 | |
执行的光标处 | run to cursor | 一直执行 到光标处停止 用在循环内部时 点击1次就执行一个循环 | |
重新执行程序 | return | 重新执行所有程序 | |
继续执行 | resume | 继续执行到下一个断点 或者 程序结束 | |
停止程序 | stop | ||
查看所有断电信息 | view breakpoints |
容器
Collection
数组 int[] 的使用很不灵活 容量是固定的
List
有序 可重复
ArrayLIst LinkedList Vector
ArrayList
底层是用数组实现的存储 查询效率高 增删效率低 线程不安全LinkedList
底层用双向链表实现的存储 查询效率低 增删效率高 线程不安全Vector
底层使用数组实现的List
相关的方法都加了同步检查 因此 线程安全 效率低- 一般情况都用
ArrayList
使用建议
- 需要线程安全时 用
Vector
- 不存在线程安全问题时 并且查找较多用
ArrayList
一般使用它- 不存在线程安全问题时 增加或删除元素较多用
LinkedList
public class ListTest {public static void main(String[] args) {test03();}//测试 isEmpty add remove size contains toArray 等常见方法//这些方法多位于 Collection 接口中public static void test01(){//有序 可重复List list = new ArrayList();System.out.println(list.isEmpty()); //判断是否为空list.add("GuHeng");System.out.println(list);System.out.println(list.isEmpty());list.add("511");list.add("XiaoYu");System.out.println(list);System.out.println("List size: " + list.size()); //容器的元素个数 大小System.out.println("Contains GuHeng?: " + list.contains("GuHeng")); //是否包含某个元素//仅仅时将 "XiaoYu" 从容器中移除 并没有将其删除 还存在 但不在数组中list.remove("XiaoYu"); System.out.println(list);Object[] objects = list.toArray();System.out.println("Convert to Object Array: " + Arrays.toString(objects));list.clear(); //清空容器System.out.println(list);}//测试和索引操作相关的方法public static void test02() {//List 存储的是 有序可重复List list = new ArrayList();list.add("A");list.add("B");list.add("C");list.add("D");System.out.println(list);list.add(2,"W");System.out.println(list);list.remove(2); //移除索引位置的元素System.out.println(list);list.set(2,"W"); //设置索引位置i的元素为 "w"System.out.println(list);System.out.println(list.get(1));list.add("B"); //在容器后面添加元素System.out.println(list);System.out.println(list.indexOf("B")); //从左到右第一个System.out.println(list.lastIndexOf("B")); //从右到左第一个}//测试两个容器之间的元素处理public static void test03(){List LinkedList = new LinkedList();List list = new ArrayList();list.add("GuHeng");list.add("511");list.add("XiaoYu");List list1 = new ArrayList();list1.add("GuHeng");list1.add("Genius");list1.add("StarRail");System.out.println(list);System.out.println(list1);System.out.println(list.containsAll(list1));list.addAll(list1); //将数组的元素全部添加进去System.out.println(list);// list.removeAll(list1); //删除掉 list 中所有两数组相同的元素 // System.out.println(list);System.out.println("list: " + list);System.out.println("list1: " + list1);list.retainAll(list1); //取出与list1相等的元素System.out.println(list);} }
Set
无序 不可重复
Set List 接口都继承于 Collection 接口 因此以上 List 可用的方法 Set 也可用
//Set 无序 不可重复 public class SetTest {public static void main(String[] args) {test02();}public static void test01(){Set set = new HashSet();set.add("hello");set.add("world"); //顺序无意义set.add("hello"); //相等的元素不会再被加入System.out.println(set);//其他方法和 List 一致 因为 Set List 都是 Collection 的接口的子接口System.out.println(set.size());System.out.println(set.isEmpty());}//Set 中不可重复的核心 equals() 方法public static void test02(){Emp emp0 = new Emp(1001,"GuHeng");Emp emp1 = new Emp(1002,"Xiaoyu");Emp emp2 = new Emp(1001,"511");Set set = new HashSet();set.add(emp0);set.add(emp1);set.add(emp2);//相等 (equals() 返回true) 的元素不会再被加入System.out.println(set);} }class Emp {private int id;private String name;@Overridepublic String toString() {return "Emp{" +"id=" + id +", name='" + name + '\'' +'}';}//若没有重写 equals 则id相同不判定为一个对象@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Emp emo = (Emp) o;return id == emo.id;}@Overridepublic int hashCode() {return Objects.hash(id);}public Emp(int id, String name) {this.id = id;this.name = name;}public void setId(int id) {this.id = id;}public void setName(String name) {this.name = name;}public int getId() {return id;}public String getName() {return name;} }
Map
Map 和 List Set 完全不同 Map 是成对存储 按照 键值对 的形式存储
Map
是用来存储 “键(key) - 值(value)对”的Map
类中存储的“键值对” 通过键来标识 所以 “键对象”不能重复Map
接口的实现类有HashMap TreeMap HashTable Properties
等
HashMap HashTable
- HashMap 线程不安全 效率高 允许 key value 为 null
- HashTable 线程安全 效率低 不允许 key value 为 null
//Map public class MapTest {public static void main(String[] args) {test3();}//Map 常见用法public static void test1(){Map map = new HashMap(); //new HashTable();map.put(1,"One");map.put(2,"Two"); //装对象 自动装箱 Integer.valueOf(2)map.put(3,"Three");map.put(2,"二"); //如果键重复(equals() 为核心进行判断) 则替换掉旧的键值对Object object = map.get(2);System.out.println(object);System.out.println(map);System.out.println(map.size());System.out.println(map.isEmpty());System.out.println(map.containsKey(4));System.out.println(map.containsValue("Three"));map.remove(3); //从 Map 容器中移除这个键值对System.out.println(map);Map map1 = new HashMap();map1.put("One","1111");map1.put("Two","1111");map1.put(3,"1111");System.out.println(map1);map.putAll(map1);System.out.println(map);}//泛型 简单用法public static void test2(){List<String> list = new ArrayList<>();list.add("GuHeng");String string = list.get(0); //返回 StringSystem.out.println(string);Set<Integer> set = new HashSet<>();//set.add("ADADAD");set.add(555);Map<String,String> map = new HashMap<>();map.put("One","1");map.put("Two","2");}//遍历 List Setpublic static void iterateListSet(){//List<String> list = new ArrayList<>();Set <String> list = new HashSet<>();list.add("aa");list.add("bb");list.add("cc");System.out.println("-------");//通过索引遍历 List 只适用于 Listfor (int i=0;i<list.size();i++){//System.out.println(list.get(i));}System.out.println("-------");//增强 for 循环 (forEach) List Setfor (String string : list){System.out.println(string);}System.out.println("-------");//使用 Iterator 对象 List Setfor (Iterator<String> iterator = list.iterator();iterator.hasNext();){String string = iterator.next();System.out.println(string);}}//遍历 Map (遍历Key Value)public static void test3(){Map<String,String> map = new HashMap<>();map.put("One","1111");map.put("Two","2222");map.put("Three","3333");//遍历键Set<String> keySet = map.keySet(); //获得所有键for (String string : keySet){ // System.out.println(string);System.out.println(map.get(string)); //根据键获得对应的值System.out.println(string + "---" + map.get(string));}//遍历值Collection<String> values = map.values(); //获得所有的值for (String string : values){System.out.println(string);}//使用 EntrySet 遍历 key value 了解Set<Map.Entry<String,String>> entrySet = map.entrySet();for (Map.Entry e : entrySet){System.out.println(e.getKey() + "===" + e.getValue());}} }
存取二维表格信息
视频信息
id | title | creatDate | length |
---|---|---|---|
1001 | 我爱Java | 2021-8-10 | 300 |
1002 | 我爱百战 | 2021-9-10 | 400 |
1003 | 我来编程 | 2021-9-12 | 500 |
//使用容器的不同方式 存储表格信息 public class StoreTable {public static void main(String[] args) {test02();}//Map 对应一行数据public static void test01(){Map<String,Object> map = new HashMap<>();map.put("Id",1001);map.put("Title","我爱JAVA");map.put("CreatTime","2021-8-10");map.put("Length",300);Map<String,Object> map1 = new HashMap<>();map1.put("Id",1002);map1.put("Title","我爱钱");map1.put("CreatTime","2021-9-10");map1.put("Length",400);Map<String,Object> map2 = new HashMap<>();map2.put("Id",1003);map2.put("Title","我来编程");map2.put("CreatTime","2021-9-12");map2.put("Length",500);List<Map<String,Object>> list = new ArrayList();list.add(map);list.add(map1);list.add(map2);for (Map temp : list){System.out.println(temp);}}//List + Javabean 的方式 //Javabean 就是把多个属性封装到一个类中public static void test02(){VideoInfo video1 = new VideoInfo(1001,"我爱JAVA","2021-8-10",300);VideoInfo video2 = new VideoInfo(1002,"我爱钱","2021-9-10",400);VideoInfo video3 = new VideoInfo(1003,"我爱编程","2021-10-10",3800);List<VideoInfo> list = new ArrayList<>();list.add(video1);list.add(video2);list.add(video3);for (VideoInfo videoInfo : list){System.out.println(videoInfo);}} }class VideoInfo {private int id;private String title;private String createTime;private int length; //秒为单位@Overridepublic String toString() {return "VideoInfo{" +"id=" + getId() +", title='" + getTitle() + '\'' +", createTime='" + getCreateTime() + '\'' +", length=" + getLength() +'}';}public VideoInfo(){}public VideoInfo(int id, String title, String createTime, int length) {this.id = id;this.title = title;this.createTime = createTime;this.length = length;}public int getId() {return id;}public String getTitle() {return title;}public String getCreateTime() {return createTime;}public String getLength() {//>60s 显示几分几秒 >3600s 显示 几小时几分几秒if (this.length < 60) return this.length + "seconds";if (this.length < 3600){int minutes = length / 60;int seconds = length % 60;return minutes + "minutes" + seconds + "seconds";}int hours = length / 3600;int minutes = (length % 3600 )/ 60;int seconds = (length % 3600) % 60;return hours + "hours" + minutes + "minutes" + seconds + "seconds";}public void setId(int id) {this.id = id;}public void setTitle(String title) {this.title = title;}public void setCreateTime(String createTime) {this.createTime = createTime;}public void setLength(int length) {this.length = length;} }
泛型
泛型的本质就是 “数据类型的参数化” 处理的数据类型不是固定的 而是可以作为参数传入
- 把类型当作是参数一样传递
- <数据类型> 只能是引用类型 基本数据类型不可用
使用泛型的好处
- 代码可读性更好 【不用强制转换】
- 程序更加安全 【只要编译时期没有警告 运行时期就不会出现
ClassCastException
异常】类型擦除
- 泛型主要用于编译阶段 编译后生成的字节码
class
手写容器
ArrayList
package Container;import java.util.ArrayList; import java.util.List;/*** 自定义List接口 >> List 接口* @param <E>*/ public interface MyList<E> { // Element 元素List list = new ArrayList();int size(); //获取包含的元素个数boolean isEmpty(); //是否为空void add(E obj); //增加 obj 对象public void add(int index, E obj); //在指定索引处 增加 obj对象public E set(int index, E obj); //将索引位置的元素更改为 obj 再返回原来的元素public E get(int index); //获取指定索引处的对象public boolean contains(E obj); //是否包含 obj 对象Object[] toArray(); //转为 Object[] 数组boolean remove(E obj); //移除 obj 若包含他再返回true反之返回falsevoid remove(int index); //移除指定索引处的对象void clear(); //清空数组 }
//自定义 MyArrayList 实现类 public class MyArrayList<E> implements MyList<E>{private int size;private Object[] elementData;public int size(){ return size;}public boolean isEmpty(){return size == 0;}public void add(E obj){ensureCapacity();elementData[size] = obj;size++;}public void add(int index, E obj){//在index位置处插入一个元素 其余元素后移//索引是否合法rangeCheck(index);ensureCapacity();//0,1,2,3,4 -> 0,1,x,2,3,4System.arraycopy(elementData,index,elementData,index+1,size-index-1);elementData[index] = obj;size++;}public E set(int index, E obj){//将索引位置的元素更改为 obj 再返回原来的元素rangeCheck(index);Object oldValue = elementData[index];elementData[index] = obj; //置换元素return (E)oldValue;}public E get(int index){ //获取指定索引处的元素rangeCheck(index);return (E)elementData[index];}public boolean contains(E obj){ //是否包含某元素for (int i=0;i<size;i++) if(elementData[i].equals(obj)) return true;return false;}public MaArrayList(){this(10);}public MyArrayList(int initialCapacity){if(initialCapacity < 0){ //初始容量不能为负try{throw new Exception("Your MyArrayList initialCapacity can't below 0 ");} catch(Exception e){e.printStackTrace();}}elementData = new Object[initialCapacity];}private void rangeCheck(int index){ //检查索引是否超出数组的范围if(index < 0 || index >= size){try{throw new Exception("Index must be between 0 and size-1");} catch(Exception e){e.printStackTrace();}}}private void ensureCapacity(){ //确保数组容量足够放下元素if(size == elementData.length){ //数组扩容Object[] newArray = new Object[elementData.length >> 1 ];System.arraycopy(elementData,0,newArray,0,elementData.length);elementData = newArray;}}public Object[] toArray(){ //转为 Object[] 返回return elementData;}public boolean remove(E obj){ //移除某个元素for(int i=0;i<size;i++){if(elementData[i].equals(obj)){remove(i);return true;}}return false;}public void remove(int index){rangeCheck(index);//删除指定位置的元素 a b c d e f g >> a b c e f gint moveNUm = size-1-index;if(moveNum > 0){System.arraycopy(elementData,index+1;elementData,index,moveNum);}size--;elementData[size] = null;}public void clear(){ //清楚数组中所有元素for(int i=0;i<size;i++){elementData[i] = null;}size = 0;}//重写toString方法public String toString(){StringBuilder sb = new StringBuilder(); //利用StringBuildersb.append("[");for(int i=0;i<size;i++){sb.append(elementData[i]);if(i != size -1) sb.append(",")}sb.append("]");return sb.toString();} }class test{public static void main(String[] args) {MyList<String> myList = new MyArrayList(10);System.out.println(myList);myList.add("1");myList.add("2");myList.add("3");myList.add("4");myList.add("5");myList.add("6");myList.add(2,"dss5");String s = myList.get(2);System.out.println(s);System.out.println(myList.contains("aa"));myList.remove(2);myList.remove("dss5");System.out.println(myList);myList.clear();} }
LinkedList
双向链表
查找效率低 增删效率高 只需将两个元素的 pervioius next 更改即可
public class MyLinkedList<E> implements MyList<E> { //与ArrayList一样都是接入的List接口private int size;private Node first; //第一个节点private Node last; //最后一个节点public int size(){return size;}public boolean isEmpty(){return size==0;}public void add(E obj){//向容器中增加一个obj 对应实际上需要增加一个节点 (使用这个节点存储obj)Node n;if(first == null){//增加第一个节点n = new Node(null,null,obj);first = n;}else{n = new Node(last,null,obj);last.next = n;}last = n;size++;}private Node node(int index){if(index >= size || index < 0){try {throw new Exception("Your index is illegal");} catch (Exception e) {e.printStackTrace();}}Node temp = null;if(first != null){if(index < (size >> 2)){temp = first;for(int i=0;i<index;i++) temp = temp.next;}else{temp = last;for(int i=size-1;i>index;i--) temp = temp.previoue;}}return temp;}public void add(int index, E obj){Node node = Node(index);Node addNode = new Node(node.previous,node,obj);if(index == 0) first = addNode;else node.previous.next = addNode;node.previous = addNode;size++;}public E set(int index, E obj){Node node = node(index);E oldValue = (E)node.obj;node.bj = obj;return oldValue;}public E get(int index){return (E) node(index).obj;}public boolean contains(E obj){for(int i=0;i<size;i++) if(node(i).obj.equals(obj)) return true;return false;}public Object[] toAttay(){Object[] objects = new Object[size];for(int i=0;i<size;i++) objects[i] = node(i).obj;return objects;}public boolean remove(int index){Node node = node(i);if(index == 0){first = node.next;first.previous = null;}if(index = size-1){last = node;last.next = null;}else{node.previous.next = node.next;node.next.previous = node.previous;}size--;}public void clear(){first = null; last = null;size = 0;}public String toString() {StringBuilder sb = new StringBuilder();sb.append("[");for (int i=0;i<size;i++){sb.append(node(i).obj);if (i != size-1) sb.append(",");}sb.append("]");return sb.toString();} }class Node{ //LikedList 元素Node previous; //上一个节点Node next; //下一个节点Object obj; //当前节点的信息public Node(Node previous, Node next, Object obj){this.previous = previous;this.next = next;this.obj = obj;}public Node(){}public Node getPrevious() {return previous;}public Node getNext() {return next;}public Object getObj() {return obj;}public void setPrevious(Node previous) {this.previous = previous;}public void setNext(Node next) {this.next = next;}public void setObj(Object obj) {this.obj = obj;} }class test1{public static void main(String[] args) {MyList<String> list = new MyLinkedList<>();list.add("1");list.add("2");list.add("3");list.add("4");list.add("5");list.add("6");list.add(0, "0");list.add(6, "x");list.remove("x");System.out.println(list.toString());} }
HashMap
HashMap底层实现详解
HashMap
底层实现采用了哈希表 这是一种非常重要的数据结构 队医以后理解很多技术都有帮助
Redis
数据库的核心技术和HashMap
一样 因此有必要了解 数据结构中由数组和链表来实现对数据的存储 它们各有特点
- 数组 占用空间连续 寻址容易 查询速度快 但是 增加和删除效率非常低
- 链表 占用空间不连续 寻址困难 但是 增加和删除效率非常高
那么 能否结合数组和链表的有点呢? 答案就是
哈希表
哈希表的本质就是 数组+(单向)链表一个
Entry
对象存储了:
- key : 键对象 value : 值对象
- next : 下一个节点
- hash : 键对象的hash值
显然每一个
Entry
对象就是一个单向链表结构 我们使用图形表示一个Entry
对象的典型示意
/*** 自定义Map接口* @param <K>* @param <V>*/ public interface MyMap<K,V> { public void put(K key, V value); //放键值对public V get(K key); //获取键对象 所对应的值public boolean containsKey(K key); //是否包含 key 对象public boolean containValue(V value); //是否包含 value 这个值public void remove(K key); //移除该键对象 和 他对应的值public int size();public boolean isEmpty(); }
/*** 手工实现HashMap* 仅用于学习*/ public class MyHashMap<K,V> implements MyMap<K,V> {private static final int INITIAL_CAPACITY = 1 << 4; //初始容量 位运算符 效率高private int size; //记录元素个数private Entry[] table; //Entry数组 存储数据public MyHashMap(){ //空参构造器 new一个默认大小的Entry数组table = new Entry[INITIAL_CAPACITY];}public void put(K key,V value){int index = hash(key);Entry entry = new Entry(key,value,index,null);//若首位为空 则直接对首位赋值if(table[index] == null) table[index] = Entry;else{Entry e = table[index];Entry last = e;while(e != null){if(e.key == key){e.value = value; //如果key相等 则直接覆盖valuereturn;}last = e; //保存最后一个e = e.next; //若等于null 则表明已经循环完毕 跳出循环 e为最后一个Entry}//整个循环结束 没有return 则表明整个单向链表中没有Entry的key与提供的key相等//则添加到链表的最后last.next = entry;}size++;}private int hash(K key){int hashcode = key.hashcode(); //返回int 有可能是负数hashcode = hashcode < 0 ? -hashcode : hashcode; //确保是正数//int index = hashcode/hashcode //最差的算法 hashcode 退化为链表//int index = hashcode % table.lentgth //早期jdk算法 效率不高return hashcode & (table.length-1); //位运算 效率高 产生[0,length-1] 的数字}public V get(K key){ //通过key返回对应的value 无key则返回nullint index = hash();if(table[index] != null){Entry e = table[index];while(e != null){if(e.key.equals(key)) return (V) e.value; //如果key相等 则返回对应的valuee = e.next;}}return null;}@Overridepublic boolean containsKey(K key) {int index = hash(key);if (table[index] != null){Entry e = table[index];while(e != null){if (e.key.equals(key)) return true; //key相等说明有这个key返回truee = e.next;}}return false;}@Overridepublic boolean containValue(V value) {for (int i=0;i<table.length;i++){Entry e = table[i];if (e != null){ while(e != null) { if (e.value.equals(value)) return true;e = e.next; }}}return false;}public void remove(K key){ int index = hash(key);Entry e = table[index];if(e != null){Entry last = null;while(e.key != null){if(e.key == key){if(last == null) table[index] = e.next;else last.next = e.next;size--;return;} last = e;e = e.next;}}}public int size() {return size;}@Overridepublic boolean isEmpty() {return size == 0;}public String toString() {StringBuilder sb = new StringBuilder();//[{a:123,b:456},{a:123,b:456},{}]sb.append("[");for (Entry e : table){while(e != null){sb.append("{" + e.key + ":" + e.value + "},");e = e.next;}}sb.append("\b]");return sb.toString();} }class Entry<K,V>{K key; //盒子的号码V value; //盒子里物品(Object)int hash; //门牌号Entry next; //下一个盒子public Entry(K key, V value, int hash, Entry next){this.key = key;this.value = value;this.hash = hash;this.next = next;}public Entry(){}; }class test2{public static void main(String[] args) {MyMap<String,String> myMap = new MyHashMap<>();myMap.put("1","AAA");myMap.put("2","BBB");myMap.put("3","CCC");myMap.put("4","DDD");myMap.put("5","EEE");myMap.put("1","EEE");System.out.println(myMap.get("2"));System.out.println(myMap.get("28"));System.out.println(myMap);myMap.remove("2");System.out.println(myMap);} }
HashSet
/*** 自定义实现的 HashSet*/ public class MyHashSet {MyHashMap map;private static final Object PRESENT = new Object();public MyHashSet(){map = new MyHashMap();}public int size(){return map.size();}public void add(Object o){map.put(o,PRESENT); //Set 的不可重复就是利用了map里面键对象不可重复的特点} }class test3{public static void main(String[] args) {MyHashSet set = new MyHashSet();set.add("1");set.add("2");set.add("3");set.add("1");} }
IO流
基础概念
数据源
数据源
data resource
提供数据的原始媒介 常见数据源::数据库 文件 其他程序 内存 网络连接 IO设备
- 数据源
- 源设备:为程序提供数据 一般对应输入流
- 目标设备:程序数据的目的地 一般对应输出流
流的概念
流是一个抽象 动态的概念 是一连串连续动态的数据集合
对于输入流而言 数据源就像水箱 流
stream
就像水管中流动着的水流 程序就是我们最终的云南过户 我们通过流A Stream
将数据源Source
中的数据information
输送到程序Program
中对于输出流而言 目标数据源就是目的地
destination
我们通过流A Stream
将程序Program
中的数据information
输送到目的数据源destination
中
输入/输出流的划分是相对程序而言的 并不是相对数据源
IO流体系
文件字节流
/*** 仅用于测试!代码不规范!下一节再将规范写法*/ public class Test01 {public static void main(String[] args) {writeFile();}public static void readFile(){try {FileInputStream fis = new FileInputStream("D:\\test\\test01.txt");//test01.txt 存放了 "Hello"//文件字节流 一个一个数读取int s1 = fis.read();int s2 = fis.read();int s3 = fis.read();int s4 = fis.read();int s5 = fis.read();int s6 = fis.read();System.out.print((char) s1);System.out.print((char) s2);System.out.print((char) s3);System.out.print((char) s4);System.out.println((char) s5);System.out.println(s6); //由于文件内容已经读取完毕 返回-1//流对象用完后必须关闭 不然总占用系统资源 最终会造成系统崩溃!//jvm 通过 OS 打开硬盘的文件资源 若不关闭 则OS没收到关闭命令 文件会一直打开着//需要 .close() 关闭文件资源 调用到了底层操作系统资源fis.close();} catch (IOException e) {e.printStackTrace();}}public static void writeFile(){//只测试英文 不测试中文try {FileOutputStream fos = new FileOutputStream("D:\\test\\test02.txt");fos.write('Y');fos.write('o');fos.write('u');fos.close();} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}} }
//规范写法 可能会在中间报错断掉 无法执行.close() 故在finally中执行close() //finally中的语句一定会执行 /*** 使用 try-catch-finally 经典结构*/ public class Test02 {public static void main(String[] args) {}public static void readFile(){FileInputStream fis = null;try {fis = new FileInputStream("D:\\test\\test01.txt");int s1 = fis.read();int s2 = fis.read();int s3 = fis.read();int s4 = fis.read();int s5 = fis.read();int s6 = fis.read();System.out.print((char) s1);System.out.print((char) s2);System.out.print((char) s3);System.out.print((char) s4);System.out.println((char) s5);System.out.println(s6);} catch (IOException e) {e.printStackTrace();}finally{try {if (fis != null) fis.close(); //如果为null说明没有打开文件 所以不需要.close()} catch (IOException e) {e.printStackTrace();}}}public static void writeFile(){FileOutputStream fos = null;try {fos = new FileOutputStream("D:\\test\\test02.txt");fos.write('Y');fos.write('o');fos.write('u');} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}finally {try {if (fos != null) fos.close();} catch (IOException e) {e.printStackTrace();}}} }
/*** try-with-resource 语法结构 使用流对象*文件字节流 以字节为单位进行操作*/ public class Test03 {public static void main(String[] args) {}public static void readFile(){try (FileInputStream fis = new FileInputStream("D:\\test\\test01.txt")){int s1 = fis.read();int s2 = fis.read();int s3 = fis.read();int s4 = fis.read();int s5 = fis.read();int s6 = fis.read();System.out.print((char) s1);System.out.print((char) s2);System.out.print((char) s3);System.out.print((char) s4);System.out.println((char) s5);System.out.println(s6);} catch (IOException e) {e.printStackTrace();}}public static void writeFile(){try (FileOutputStream fos = new FileOutputStream("D:\\test\\test02.txt");){fos.write('Y');fos.write('o');fos.write('u');} catch (IOException e) {e.printStackTrace();}} }
文件字符流
前面的文件字节流可以处理所有的文件 但是字节流不能很好的处理
Unicode
字符 经常会出现乱码 所以处理文本文件 一般可以使用文件字符流 他以字符为单位进行操作public class TestFileReaderWriter {public static void main(String[] args) {try(FileReader fr = new FileReader("D:\\Test\\Test03.txt");FileWriter fw = new FileWriter("D:\\Test\\Test033.txt")){int temp = 0;while((temp = fr.read()) != -1){ //没有数据则会返回-1 有数据则读取fw.write(temp); //将数据一个一个写进去}} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}} }
缓冲字节流
节点流:可以从或向一个特定的地方(节点)读写数据。如FileReader
处理流:是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写。
如BufferedReader 处理流的构造方法总是要带一个其他的流对象做参数。一个流对象经过
其他流的多次包装,称为流的链接。
Java
缓冲流本身并不具有IO
流的读取与写入功能 只是在别的流(节点流或其他处理流)上加上缓冲功能提高效率 就像是把别的流包装起来一样 因此缓冲流是一种处理流(包装流)当对文件或者其他数据源进行频繁的读写操作时 效率比较低 这是如果使用缓冲流就能高效的读写信息 因为缓冲流是先将数据缓存起来 然后当缓存区存满后或者手动刷新时再一次性的读取到程序中或写入目的地
因此 缓冲流是很重要的 我们在IO操作时记得加上缓冲流来提升性能
BufferedInputStream BufferedOutputStream
这两个流是缓冲字节流 通过内部缓存数组来提高操作流的效率下面我们可以通过两种方式(普通文件字节流与缓冲文件字节流)实现对一个视频文件的复制 来体会一下缓冲流的好处
/*** 测试缓冲字节流*/ public class TestBufferedInputOutputStream {public static void main(String[] args) {try(FileInputStream fis = new FileInputStream("D:\\Test\\TestVideo.mp4");FileOutputStream fos = new FileOutputStream("D:\\Test\\TestVideoCopy.mp4");BufferedInputStream bis = new BufferedInputStream(fis);BufferedOutputStream bos = new BufferedOutputStream(fos);){//进行复制int temp = 0; while((temp = bis.read()) != -1){ bos.write(temp);}} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}} }
缓冲字符流
BufferedReader BufferedWriter
增加了缓存机制 大大提高了读写文本文件的效率 同时 提供了更方便的按行读取的方法readLine()
处理文本时 我们一般可以使用缓冲字符流
/*** 使用缓冲字符流*/ public class TestBufferedReaderWriter {public static void main(String[] args) {try (BufferedReader br = new BufferedReader(new FileReader("D:\\Test\\Test04.txt"));BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\Test\\Test044.txt"));){String tempString = ""; //按行读取 若为空表示文件以读取完毕while((tempString = br.readLine()) != null){bw.write(tempString);bw.newLine(); //writer 换到新的一行继续写}} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}} }
字节数组流
ByteArrayInputStream ByteArrayOutputStream
经常用在需要流和数组之间转化的情况 说白了FileInputStream
是把文件爱你当作数据源ByteArrayInputStram
则是把内存中的“某个字节数组对象”当作数据源public class TestByteArrayInputOutputStream {public static void main(String[] args) {test01("Hello".getBytes());}public static void test01(byte[] bytes){int temp = 0;int num = 0; //用于表示读取的字节数try(ByteArrayInputStream bis = new ByteArrayInputStream(bytes)){while((temp = bis.read()) != -1){System.out.print((char) temp);num++;}} catch (IOException e) {e.printStackTrace();}System.out.println();System.out.println("读取的字节数: "+ num);} }
数据流
数据流将“基本数据类型与字符串类型”作为数据源 从而允许程序以与机器无关的方式从底层输入输出流中操作Java基本数据类型与字符串类型
DataInputStream DataOutputStream
提供了可以存取与机器无关的所有Java基础类型数据 如 int double String的方法
DataInputStream DataOutputStream
是处理流 可以对其他节点流或处理流进行包装 增加一些更灵活 高效的功能public class TestDataInputOutputStream {public static void main(String[] args) { // writeData();readData();}public static void writeData(){try(DataOutputStream dos = new DataOutputStream(new FileOutputStream("D:\\Test\\Test01.txt"))) {dos.writeChar('d');dos.writeInt(5);dos.writeDouble(Math.random());dos.writeBoolean(true);dos.writeUTF("你好");//手动刷新缓冲区 将流中的数据写入文件dos.flush();} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}public static void readData(){try(DataInputStream dis = new DataInputStream(new FileInputStream("D:\\Test\\Test01.txt"))) {//读取数据时 需要和写入时的顺序保持一致 否则 无法正确读出数据System.out.println(dis.readChar());System.out.println(dis.readInt());System.out.println(dis.readDouble());System.out.println(dis.readBoolean());System.out.println(dis.readUTF());} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}} }
对象流
ObjectInputStream
ObjectOutputStream
是以 对象 为数据源 但是必须将传输的对象进行序列化与反序i恶化操作需要序列化的对象 需要实现接口
java.io.Serializable
- 持久化 把对象的字节序列永久的保存到硬盘上 通常存放在一个文件中 比如 休眠的实现 以后服务器session管理 hibernate将对象持久化实现
- 网络通信 在网络上传送对象的字节序列 比如 服务器之间的数据通信 对象传递
java.io.Serializable
空接口 标记作用static
属性不参与序列化- 对象中的某些属性如果不想被徐序列化 不能使用
static
而是使用transient
修饰public class User implements java.io.Serializable{private int id;private String uName;transient private String pwd;public User(int id, String uName, String pwd) {this.id = id;this.uName = uName;this.pwd = pwd;}public int getId() {return id;}public void setId(int id) {this.id = id;}public String getuName() {return uName;}public void setuName(String uName) {this.uName = uName;}public String getPwd() {return pwd;}public void setPwd(String pwd) {this.pwd = pwd;} } class TestObjectInputOutputStream{public static void main(String[] args) { // writeObject();readObject();}public static void writeObject(){try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\Test\\Test02.txt"))){ArrayList<User> list = new ArrayList<>(); //也需要实现 Serializable 接口 全部都需要实现此接口用到的list.add(new User(1001,"GuHeng","269260"));list.add(new User(1002,"511","511115"));list.add(new User(1003,"XiaoYu","147523"));oos.writeObject(list);oos.flush();} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}public static void readObject(){try(ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\Test\\Test02.txt"))){ArrayList<User> list = (ArrayList) ois.readObject();for (User u : list){System.out.println(u.getId() + "," + u.getuName() + "," + u.getPwd()); //因为 pwd 未参与序列化 故读出为null}} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} catch (ClassNotFoundException e) {e.printStackTrace();}} }
转换流
InputStreamReader
OutputStreamWriter
用来实现将字节流转化成字符流/*** 测试转换流*/ public class TestInputStreamReader {public static void main(String[] args) {try(BufferedReader br = new BufferedReader(new InputStreamReader(System.in));BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out))) {String string = br.readLine();while(!"exit".equals(string)){bw.write("Keyboard input: " + string);bw.newLine();bw.flush();string = br.readLine();}} catch (IOException e) {e.printStackTrace();}} }
随意访问文件流
RandomAccessFile
可以实现两个作用
- 实现对一个文件做读和写的操作
- 可以访问文件的任意位置 不像其他流只能按照先后顺序读取
/*** 随意访问文件 //任意访问文件*/ public class TestRandomAccessFile {public static void main(String[] args) {try(RandomAccessFile raf = new RandomAccessFile("D:\\Test\\Test03.txt","rw")) {int[] data = {10,20,30,40,50,60,70,80,90,100};for (int datum : data) raf.writeInt(datum);//直接从 Test03.txt 中读取数据 位置为第4字节开始raf.seek(4);System.out.println(raf.readInt());//在第八个字节处插入一个新数据 35 替换之前的数据raf.seek(8);raf.writeInt(35);for (int i=0;i<data.length;i++){raf.seek(i*4);System.out.print(raf.readInt() + "\t");}} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} ;} }
Apache IO 包
装饰器模式构建IO流体系
装饰器模式是
GOF
23种设计模式中较传统的一种模式 它可以实现对原有类的包装和修饰 使新的类具有更强的功能
BufferedReader br = new BufferedReader(new FileReader("D:\\d.txt"));
- BufferedReader 通过对FileReader包装 使其功能更强大
public class TestDecoration {public static void main(String[] args) {Iphone iphone15ProMax = new Iphone("Iphone15 ProMax");iphone15ProMax.show();System.out.println("After decorating-");TouyingIphone tyPhone = new TouyingIphone(iphone15ProMax);tyPhone.show();} }class Iphone{private String name;public Iphone(String name) {this.name = name;}public void show(){System.out.println("I am " + name + ",I can show image on my screen.");} }class TouyingIphone{ //投影装置 对手机进行包装使其具有投影的功能Iphone iphone;public TouyingIphone(Iphone iphone) { //构造器需要传入一个手机对其进行包装this.iphone = iphone;}public void show(){System.out.println("Open touying function...");iphone.show();System.out.println("I can show image on wall.");System.out.println("Touying over...");} }
/*** 测试Apache common 组件下的io工具库* 同时 熟悉一下如何让加载外部的jar包*/ public class TestApacheFileUtils {public static void main(String[] args) {directoryCopy();}public static void writeFile(){StringBuilder sb = new StringBuilder();for (int i=0;i<10000;i++){sb.append(Math.random()+"\n");}try {FileUtils.write(new File("D:\\Test\\apache-test.txt"),sb,"GBK");//可以直接将一串StringBuilder写到文件中 “GBK”是编码格式//过程简化很多} catch (IOException e) {e.printStackTrace();}}public static void readFile(){try {List<String> list = FileUtils.readLines(new File("D:\\Test\\apache-test.txt"),"GBK");//按行读取 直至全部读取完毕 list : ArrayList<String>for (String s : list){System.out.println(s);}} catch (IOException e) {e.printStackTrace();}}public static void readURL(){try {URL url = new URL("http://www.itbaizhan.com");InputStream is = url.openStream();String content = IOUtils.toString(is,"UTF-8");System.out.println(content);} catch (MalformedURLException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}public static void fileCopy(){File scrFile = new File("D:\\Test\\test01.txt");File desFile = new File("D:\\Test\\test011.txt");try {FileUtils.copyFile(scrFile,desFile); //将文件复制到目标文件FileUtils.copyFileToDirectory(scrFile,new File("D:\\"));//将文件复制到目标目录} catch (IOException e) {e.printStackTrace();}}public static void directoryCopy(){File dir = new File("D:\\ChatGPT");File dir1 = new File("D:\\ChatGPT\\copy");//目录的复制try {FileUtils.copyDirectory(dir,dir1,new FileFilter() {//筛选特定文件@Overridepublic boolean accept(File pathname) {if (pathname.isDirectory() || pathname.getName().endsWith("png")) return true;else return false;}});} catch (IOException e) {e.printStackTrace();}}}
线程
概念
多线程概念
多线程是
java
语言的重要特性 大量应用于网络编程 服务器端程序的开发 最常见的UI
界面底层原理 操作系统底层原理都大量使用了多线程程序
Program
程序是一个静态的概念 一般对应于操作系统中的一个可执行文件 如“我们要启动Steam 则对应Steam的可执行程序 当我们双击Steam 则加载程序到内存中 开始执行该程序 于是产生了进程
进程
Process
执行中的程序叫做进程 是一个动态概念 现代的操作系统都可以同时启动多个进程 如:我们在玩星穹铁道 同时也可以打开某音乐听音乐 也可以同时打开录屏软件录屏 进程具有以下特点
- 进程是计算机中的程序关于某数据集合上的一次运行活动 是系统进行资源分配和调度的基本单位 是结构的基础
cpu data code
三部分组成 每个进程都是独立的 保有自己的cpu时间 代码和数据- 进程的查看:
- Windows:
Ctrl + Shift + Esc
启动任务管理器- Linux:
ps
/top
线程
一个进程可以产生多个线程 同一进程的多个线程也可以共享此进程的某些资源 代码 数据 所以县城又被称为轻量级进程
lightweight process
- 是能够进行运算的最小单位 他被包含在进程之中 是进程中的实际运作单位
- 一个进程可拥有多个线程
- 一个进程中的多个线程共享相同的内存单元/内存地址空间 可以访问相同的变量和对象 而他们从同一堆中分配对象并进行通信 数据交换和同步操作
- 线程的启动 中断 消亡 消耗的资源非常少
- 每个
java
程序都有一个主线程main thread
对应main方法启动JAVA中线程和操作系统线程的关系
green thread
是一种由运行环境或虚拟机(VM)调度 而不是由本地底层操作系统调度的线程 绿色线程并不依赖底层的系统功能 模拟实现了多线程的运行 这种线程的管理调配发生在用户空间而不是内核空间 所以他们可以在没有原生线程支持的环境中工作在
Java1.1
中 绿色线程是JVM
中使用的唯一一种线程模型
Java1.2
之后 Linux中的JVM是基于pthread实现的 即现在的Java中现成的本质 其实就是操作系统中的线程我们分析Thread类的start() 就能看出最终调用的是native方法start0() 也即是调用了操作系统底层的方法
通过
Thread
类实现多线程步骤
- 在
Java
中负责实现线程功能的类是java.lang.Thread
类- 可以通过创建
Thread
的示例来创建新的线程- 每个线程都是通过某个特定的
Thread
对象所对应的方法run()
来完成其操作的 方法run()
称为线程体- 调用
Thread
类的start()
方法来启动一个线程
public class Test01 {public static void main(String[] args) {new Thread().start();} }
线程实现方式
- 通过继承
Thread
类实现多线程
- 在
Java
中负责实现线程功能的类是java.lang.Thread
类- 可以通过创建
Thread
的实例来创建新的线程- 每个县城都是通过
run()
来完成其操作的 方法run()
称为线程体- 调用
Thread
类的start()
方法来启动一个线程- 通过
Runnable
接口实现多线程
- 在开发中 我们应用更多的是
Runnable
接口实现多线程 这种方式克服了继承Thread
类的去缺点 即在实现Runnable
接口的同时还可以继承某个类- 使用
lambda
创建 JDK8新增
/*** 创建自己的第一个线程类*/ public class TestThread extends Thread {//继承Thread类//重写run()方法 run()是线程体@Overridepublic void run() {for (int i=0;i<100;i++){System.out.println(this.getName() + ":" +i); //getName(返回的是线程的名称}}public static void main(String[] args) {TestThread testThread = new TestThread(); //创建线程对象testThread.start(); //启动线程 通过start()调用run()方法TestThread testThread1 = new TestThread();testThread1.start();} }
public class TestThread2 implements Runnable{@Overridepublic void run() {for (int i=0;i<100;i++){System.out.println(Thread.currentThread().getName() + ":" +i); //getName(0返回的是线程的名称}}public static void main(String[] args) {TestThread2 t = new TestThread2();//把实现了Runnable接口的对象作为参数传入Thread thread = new Thread(t);Thread thread1 = new Thread(t);thread.start();thread1.start();} }
/*** 使用lambda表达式创建线程 //匿名内部类*/ public class TestThread3 {public static void main(String[] args) {new Thread(new Runnable() {@Overridepublic void run() {for (int i=0;i<100;i++){System.out.println(Thread.currentThread().getName() + ":" +i); //getName(0返回的是线程的名称}}}).start();//简化 new Runnable() -> () -> 适用于类中只有一个方法 Runnable接口中只有run()方法new Thread(() ->{for (int i=0;i<100;i++){System.out.println(Thread.currentThread().getName() + ":" +i); //getName(0返回的是线程的名称}}).start();} }
生命周期
/*** 测试终止线程的典型方法*/ public class TestThreadCycle_Terminal implements Runnable{boolean live = true; //标记变量 表示线程是否可以终止@Overridepublic void run() {int i = 0;while(live){System.out.println(Thread.currentThread().getName() + (i++));}}public void terminal(){live = false; //典型结束线程方式}public static void main(String[] args) {TestThreadCycle_Terminal tt = new TestThreadCycle_Terminal();Thread t1 = new Thread(tt); //新生状态t1.start(); //就绪状态for (int i = 0;i<100;i++){System.out.println("Main Thread:" + i);}tt.terminal();System.out.println("t1.stop!");} }
public class TestThreadState_join {public static void main(String[] args) {Thread f = new Thread(new FatherThread());f.start();}}class FatherThread implements Runnable{@Overridepublic void run() {System.out.println("爸爸想抽烟,发现烟抽完了。");System.out.println("爸爸让儿子去买包红塔山。");Thread son = new Thread(new SonThread());son.start();System.out.println("爸爸等儿子买眼回来。");try {son.join();//让son()线程加入进来 让father()阻塞} catch (InterruptedException e) {e.printStackTrace();System.out.println("爸爸出门去找儿子跑哪去了。");//结束JVM 如果是0则表示正常结束 如果非0则表示正常结束System.exit(1);}System.out.println("爸爸高兴的接过烟开始抽,并把零钱给了儿子。");} }class SonThread implements Runnable{@Overridepublic void run() {System.out.println("儿子出门去买烟。");System.out.println("儿子买眼需要十分钟。");try {for (int i=1;i<=10;i++){System.out.println("第" + i + "分钟");Thread.sleep(1000);}} catch (InterruptedException e) {e.printStackTrace();}System.out.println("儿子买烟回来了。");} }
获取线程基本信息的方法
方法 | 功能 |
---|---|
isAlive() | 判断线程是否还"活着" 即线程是否还未终止 |
getPriority() | 获得线程的优先级数值 |
setPriority() | 设置线程的优先级数值 |
setName() | 给线程一个名字 |
getName() | 得到线程的名字 |
currentThread() | 得到当前正在运行的线程对象 也就是取得自己本身 |
优先级
- 处于就绪状态的线程 会进入"就绪队列" 等待
JVM
来挑选- 线程的优先级用数字表示 范围
[1,10]
一个线程的缺省优先级是5- **注意 ** 优先级低只是意味着获得调度的概率低 并不是绝对先调用优先级高的线程后调用优先级低的线程
/*** 测试线程的基本信息 优先级*/ public class TestThreadInfo {public static void main(String[] args) {MyThread myThread = new MyThread();Thread thread = new Thread(myThread,"GuHeng");thread.setPriority(1);thread.start();System.out.println("Name is " + thread.getName());System.out.println(thread.isAlive());System.out.println("Main thread.over");Thread thread1 = new Thread(myThread);thread1.setName("511");thread1.setPriority(10);thread1.start();} }class MyThread implements Runnable{@Overridepublic void run() {for (int i=0;i<10000;i++){System.out.println(Thread.currentThread().getName() + ":" + i); // try { // Thread.sleep(1000); // } catch (InterruptedException e) { // e.printStackTrace(); // }Thread.yield();}} }
线程同步 synchronize
- 同步问题的提出
- 现实生活中 我们会遇到“同一个资源 多个人都想使用”的问题 如 教室里 只有一台电脑 多个人都想使用 天然的解决办法是 在电脑旁边 大家排队 前一人使用完后 后一个人在使用
- 线程同步的概念
- 多个线程访问同一个对象 并且某些线程还想修改这个对象 这是 我们就需要用到“线程同步” 线程同步其实就是一种等待机制 多个需要同时访问此对象的线程进入这个对象的等待池形成队列 等待前面的线程使用完毕之后 下一个线程再使用
/*** 测试同步 没有加线程同步 两人一起取钱 会有错误产生*/ public class TestSynchronized {public static void main(String[] args) {Account a1 = new Account(500,"GuHeng");Drawing d1 = new Drawing(280,a1); //模拟取钱的线程Drawing d2 = new Drawing(300,a1); //模拟取钱的线程d1.start(); //我取钱d2.start(); //女票取钱} }class Account{int money;String name;public Account(int money, String name) {this.money = money;this.name = name;} }//模拟取款操作 class Drawing extends Thread{int drawingNum; //取款金额Account account; //取款账户int expenseTotal; //取款总金额public Drawing(int drawingNum,Account account){this.account = account;this.drawingNum = drawingNum;}@Overridepublic void run() {if (account.money - drawingNum < 0){System.out.println("Your account money is not enough!");return;}try{Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}account.money -= drawingNum;expenseTotal += drawingNum;System.out.println(this.getName() + "Account money:" + account.money);System.out.println(this.getName() + "Drawing total money:" + expenseTotal);} }
实现线程同步
由于同一进程的多个线程共享同一块存储空间 在带来方便的同时 也带来了访问冲突的问题
Java
语言提供了专门机制以解决这种冲突 有效避免了同一个数据对象被多个线程同时访问时出现错误这个机制就是
synchronized
关键字 它包括两种用法synchronized方法
和synchronized块
- synchronized 方法
- 通过在方法声明中加入 synchronized 关键字来声明 语法如下
- public synchronized void accessVal ( int newVal );
- synchronized 方法控制对“对成员的成员变量”的访问 每个对象对应一把锁 每个synchronized反复都必须获得调用该方法的对象的锁方能执行 否则所属线程阻塞 方法一旦执行 就独占该锁 直到从该方法返回时 才将锁释放 此后被阻塞的线程方能获得该锁 重新进入可执行状态
- synchronized 块
- synchronized 方法的缺陷 若将啊ing一个大的方法声明为 synchronized 将会大大影响效率
- Java 为我们提供了更好的解决办法 那就是synchronized块 块可以让我们精确地控制到具体的“成员变量” 缩小同步的范围 提高效率
- synchronized 块:通过synchronized关键字来声明synchronized块 语法如下
- synchronized (syncObjct) { // 允许访问控制的代码 }
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cpoiFgYn-1688472414968)(E:\programmeDemo\Markdown\CSDN\Snipaste_2023-07-04_14-17-51.png)]/*** 测试同步 增加了线程同步 更加安全*/ public class TestSynchronized {public static void main(String[] args) {Account a1 = new Account(500,"GuHeng");Drawing d1 = new Drawing(280,a1); //模拟取钱的线程Drawing d2 = new Drawing(300,a1); //模拟取钱的线程d1.start(); //我取钱d2.start(); //女票取钱} }class Account{int money;String name;public Account(int money, String name) {this.money = money;this.name = name;} }//模拟取款操作 class Drawing extends Thread{int drawingNum; //取款金额Account account; //取款账户int expenseTotal; //取款总金额public Drawing(int drawingNum,Account account){this.account = account;this.drawingNum = drawingNum;}@Overridepublic void run() {synchronized (account){//需要对其内容修改 则添加到代码块里面if (account.money - drawingNum < 0){System.out.println("Your account money is not enough!");return;}try{Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}account.money -= drawingNum;expenseTotal += drawingNum;}System.out.println(this.getName() + "Account money:" + account.money);System.out.println(this.getName() + "Drawing total money:" + expenseTotal);} }
生产者消费者
在实际开发中 尤其“结构设计”中 会大量使用这个模式 对于初学者了解即可 如果晋升到中高级开发人员 这就是必须要掌握的内容
多线程环境下 我们经常需要多个线程的并发和协作 这个时候 就需要了解一个重要的多线程并发协作模型“生产者/消费者模式”
- 生产者 负责生产数据的模块 这里模块可能是 方法 对象 线程 进程
- 消费者 负责处理数据的模块 这里模块可能是 方法 对象 线程 进程
- 缓冲区 消费者不能直接使用生产者的数据 它们之间有个"缓冲区" 生产者将生产好的数据放入"缓冲区" 消费者从"缓冲区"拿要处理的数据
线程并发协作 也叫线程通信 通常用于生产者/消费者模式 需要使用 wait()/notify()/notifyAll() 方法
方法名 | 作用 | 其他说明 |
---|---|---|
final void wait() | 表示线程一直等待 | 线程执行wait()方法时候 会释放 |
void wait(long timeout) | 线程等待指定毫秒参数的时间 | 当前的锁 然后让出CPU 进入等待状态 |
final void notify() | 唤醒一个处于等待状态的线程 | |
final void notifyAll() | 唤醒一个对象上所有调用wait()方法的线程 |
以上方法均是java.lang.Object
类的方法 都只能在同步方法或者同步代码块中使用 否则会抛出异常
synchronized (object n) 同步代码块如下
/*** 手写生产者消费者模式*/ public class TestProduceConsumer {public static void main(String[] args) throws InterruptedException {SyncStack ss = new SyncStack(); //定义缓冲区对象Produce p = new Produce(ss); //定义生产者线程Consumer c = new Consumer(ss); //定义消费者线程p.start();Thread.currentThread().sleep(2000);c.start();} }class Mantou{int id;Mantou(int id){this.id = id;} }class SyncStack{ //缓冲区 (馒头筐) producer push 压栈-> pop出栈 -> consumerint index = 0;Mantou[] ms = new Mantou[10];public synchronized void push(Mantou mantou){while (index == ms.length){ //馒头筐满了try {//调用 wait() 线程会将持有的对象锁释放 进入等待池(等待池)//这样 锁池中的线程就可以京城获得对象锁this.wait();} catch (InterruptedException e) {e.printStackTrace();}}//唤醒在当前对象等待池中的一个线程 进入当前对象的锁池this.notify();ms[index] = mantou;index++;}public synchronized Mantou pop(){while(index == 0){ //馒头筐空了try {//馒头筐空了 消费线程进入等待this.wait();} catch (InterruptedException e) {e.printStackTrace();}}this.notify();return ms[--index];} }class Produce extends Thread{SyncStack ss = null;public Produce(SyncStack ss){this.ss = ss;}public void run(){for (int i=0;i<10;i++){System.out.println("Produce mantou:" + i);Mantou m = new Mantou(i);ss.push(m);}} }class Consumer extends Thread{SyncStack ss = null;public Consumer(SyncStack ss){this.ss = ss;}@Overridepublic void run() {for (int i=0;i<10;i++){Mantou m = ss.pop();System.out.println("Consume mantou:" + m.id);}} }
线程池
池化技术
池化技术指的是提前准备一些资源 在需要时可以重复使用这些预先准备的资源 他有两个优点
- 提前创建
- 重复利用
- 常见的池化技术的使用有 线程池 数据库连接池 HttpClient连接池
什么是线程池
通俗点讲 当有工作来 就会向线程池拿一个线程 当工作完成后 并不是直接关闭线程 而是将这个线程归还给线程池供其他任务使用
这样就避免了频繁地创建线程 销毁线程 极大的提高了相应速度 假如创建线程时间为t1 执行任务时间为t2 销毁线程时间为t3 那么使用线程池就免去了t1+t3的时间
- 比如 有一个省级银行的数据网络中心 高峰期每秒的客户端请求并发数超过200 如果为每个客户端请求创建一个新的现成的话 那耗费的CPU时间和内存都是十分惊人的 如果采用一个拥有200个线程的线程池 那将会节约大量的系统资源 使得更多的CPU时间和内存来处理实际的商业应用 而不是频繁的线程创建和销毁
线程池优势
- 解耦作用 建成的创建与执行完全分开 方便维护
- 重复利用 降低系统资源消耗 通过重用已存在的线程 降低线程创建和销毁造成的消耗
- 统一线程管理 控制线程并发数量 降低服务器压力 统一管理所有线程
- 提升系统响应速度 使用线程池以后 工作效率通常能提高10倍以上
阿里巴巴《Java开发手册》中强制规定【线程资源必须通过线程池提供 不允许在应用中自行显式创建线程】
线程池工作原理
//通过如下构造器创建线程池对象 public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);//corePoolSize 线程池中的核心线程数量 这几个核心线程 即使在没有用的时候 也不会被回收//maximuxPoolSize 线程池中可以容纳的最大线的数量(核心线程数+非核心线程数)//keepAliveTime和TimeUnit:Milliseconds 非核心线程可以保留的最长的空闲时间(TimeUnit就是计算这个好似简单一个单位 前面单位就是毫秒 ms)//workQueue 任务队列 任务可以储存在任务队列中等待被执行 执行的是FIFO原则(先进先出)//threadFactory 创建线程对象的工厂//handler 线程池已满 拒绝执行的策略ThreadPoolExecutor executor = new ThreadPoolExecutor(5,10,200,TImeUnit.MILLISECONDS,new ArrayBlockingQueue<RUnnable>(5));
四种内置线程池
- CachedThreadPool 全是临时工干活
美团
- 该线程中没有核心线程 非核心线程的数量为 Integer.max_value 就是无限大 当有需要时创建线程来执行任务 没有需要时回收线程 适用于耗时少 任务量大的情况
- ScheduledThreadPool 有正式工 也有临时工
- 周期型执行任务的线程池 按照某种特定的计划执行线程中的任务 有核心线程 但也有非核心线程 非核心线程的大小也为无限大 也可以执行周期性的任务
- SingleThreadPool 只有一个正式工
- 只有一条线程来执行任务 他只会用唯一的工作线程来执行任务 保证所有任务按照指定顺序(FIFO 先进先出 LIFO 后进先出 优先级) 执行适用于有顺序的任务的应用场景
- FixedThreadPool 全是正式工
京东
- 定长的线程池 只有核心线程 核心线程的数量即为最大的线程数量 没有非核心线程
/*** 测试线程池的使用*/ public class TestThreadPool {public static void main(String[] args) {//核心线程池大小: 5 线程池中的核心线程数量 这几个核心线程 即使在没有用的时候 也不会被回收// maximumPoolSize:10线程池中可以容纳的最大线程的数量(核心线程数+非核心线程数)// keepA/iveTime.200 和 TimeUnit.Milliseconds非核心线程可以保留的最长的空闲时间是 200 毫秒 (TimeUnit 就是计算这个时间的一个单位)//workQueue:任务队列任务可以储存在任务队列中等待被执行,执行的是 FIFIO 原则先进先出)//threadFactory: 创建线程对象的工厂//handler: 线程池已满,拒绝执行策略// ThreadPoolExecutor executor = new ThreadPoolExecutor( // 5,10,200, TimeUnit.MILLISECONDS,new ArrayBlockingQueue<Runnable>(5));// ExecutorService executor = Executors.newCachedThreadPool(); // ExecutorService executor = Executors.newFixedThreadPool(20); // ExecutorService executor = Executors.newSingleThreadExecutor();ScheduledExecutorService executor = Executors.newScheduledThreadPool(20);//ScheduleThreadPool 里面的方法 30s 后执行executor.schedule(()->{System.out.println("30s后,延时执行!");},30,TimeUnit.MILLISECONDS);for (int i=0;i<40;i++){MyTask myTask = new MyTask(i);executor.execute(myTask);// System.out.println("线程池中的线程数目:" + executor.getPoolSize() + ",队列中等待的任务数" + // executor.getQueue().size() + "已执行完的任务数:" + executor.getCompletedTaskCount());}executor.shutdown();} }class MyTask implements Runnable{int taskNum;public MyTask (int taskNum){this.taskNum = taskNum;}@Overridepublic void run() {System.out.println("Doing task:" + taskNum);try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Task:" + taskNum + ",accomplished!");} }
ThreadLocal
常见误解
ThreadLocal
很容易让人望文生义 想当然地认为是一个“本地线程”- 很多人会说
ThreadLocal
是为了避免线程同步问题 个人认为不太恰当正确说法
ThreadLocal
为每个线程提供独立的变量副本 所以每一个线程都可以独立地改变自己的副本 而不会影响其他线程所对应的副本
ThreadLocal
更多的是为了实现数据隔离 避免多次传参问题 围绕这点 有很多场景:
数据隔离
- 进行事务操作 用于存储线程事务信息(Spring实现事务隔离级别的源码)
- 数据库连接管理
- Cookie Session 的管理
- Android开发中 Lopper类
避免多次传参
- 在进行对象属性跨层传递的时候 使用
ThreadLocal
可以避免多次传参 打破层次间的约束混个眼熟
四个方法
- void set(Object value) 设置当前线程局部变量的值
- public Object get() 该方法返回当前线程对应的线程局部变量
- public void remove() 使用完 ThreadLocal 变量时 要将当前线程局部变量的值删除 避免因为县城周期长 而造成的内存泄露
- protected Object initialValue() 在线程第1次调用get() 或 set(Object) 方法时才执行 并且仅执行一次 ThreadLocal 中缺省实现直接返回一个 null
/*** 测试ThreadLocal*/ public class TestThreadLocal extends Thread{public static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>(){@Overrideprotected Integer initialValue() {System.out.println("初始化,第一次被调用!");return new Integer(100); //100时初始值 每格线程第一次调用set/get方法 都会调用initialValue()}};public static int getNextNum(){seqNum.set(seqNum.get() + 1);return seqNum.get();}public static ThreadLocal<Integer> seqNum1 = new ThreadLocal<Integer>(){@Overrideprotected Integer initialValue() {return new Integer(10); //初始值10}};public static int getAdd10Num1(){seqNum1.set(seqNum1.get() + 10);return seqNum1.get();}public void run(){for (int i=0;i<3;i++){System.out.println(Thread.currentThread().getName() + ",num:" + getNextNum() +",num1:" + getAdd10Num1());}//用完 就remove() 避免内存泄漏seqNum.remove();seqNum1.remove();}public static void main(String[] args) {//每个线程都有一个独立的值Thread t0 = new TestThreadLocal();Thread t1 = new TestThreadLocal();t0.start();t1.start();} }
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8c7urEfh-1688472414969)(E:\programmeDemo\Markdown\CSDN\Snipaste_2023-07-04_19-57-24.png)]
内存泄漏 不再会被使用的对象或者变量占用的内存不能被回收 就是内存泄露
产生原因 长生命周期对象持有段生命周期的对象导致短生命周期的对象无法被释放
- 强引用
- 最普遍的应用 一个对象具有强引用 不会被垃圾回收器回收 当内存空间不足
Java
虚拟机宁愿抛出OutOfMemoryError
错误 使程序异常终止 也不回收这种对象- 如果想取消强引用和某个对象之间的关联 可以显式地将引用赋值为
null
这样可以使JVM
在合适的时间回收该对象 a = null- 弱引用
- 进行垃圾回收时 无论内存是否充足 会回收只被弱引用关联的对象
java
中 用java.lang.ref.WeakReference
类来表示弱引用可能会造成内存泄漏 如果
ThreadLocal
只被弱引用难怪会被GC
回收 回收后 没有再调用get()/set()/remove()
方法 从而造成value
对象无法回收但
ThreadLocal
内存泄漏的真正根源是:由于ThreadLocalMap
的生命周期跟Thread
一样长 如果没有手动删除对应的key
和value
就会导致内存泄漏 而不一定是弱引用每次使用完
ThreadLocal
变量 都调用他的 remove() 方法清除数据
某种特定的计划执行线程中的任务 有核心线程 但也有非核心线程 非核心线程的大小也为无限大 也可以执行周期性的任务
- SingleThreadPool 只有一个正式工
- 只有一条线程来执行任务 他只会用唯一的工作线程来执行任务 保证所有任务按照指定顺序(FIFO 先进先出 LIFO 后进先出 优先级) 执行适用于有顺序的任务的应用场景
- FixedThreadPool 全是正式工
京东
- 定长的线程池 只有核心线程 核心线程的数量即为最大的线程数量 没有非核心线程
/*** 测试线程池的使用*/ public class TestThreadPool {public static void main(String[] args) {//核心线程池大小: 5 线程池中的核心线程数量 这几个核心线程 即使在没有用的时候 也不会被回收// maximumPoolSize:10线程池中可以容纳的最大线程的数量(核心线程数+非核心线程数)// keepA/iveTime.200 和 TimeUnit.Milliseconds非核心线程可以保留的最长的空闲时间是 200 毫秒 (TimeUnit 就是计算这个时间的一个单位)//workQueue:任务队列任务可以储存在任务队列中等待被执行,执行的是 FIFIO 原则先进先出)//threadFactory: 创建线程对象的工厂//handler: 线程池已满,拒绝执行策略// ThreadPoolExecutor executor = new ThreadPoolExecutor( // 5,10,200, TimeUnit.MILLISECONDS,new ArrayBlockingQueue<Runnable>(5));// ExecutorService executor = Executors.newCachedThreadPool(); // ExecutorService executor = Executors.newFixedThreadPool(20); // ExecutorService executor = Executors.newSingleThreadExecutor();ScheduledExecutorService executor = Executors.newScheduledThreadPool(20);//ScheduleThreadPool 里面的方法 30s 后执行executor.schedule(()->{System.out.println("30s后,延时执行!");},30,TimeUnit.MILLISECONDS);for (int i=0;i<40;i++){MyTask myTask = new MyTask(i);executor.execute(myTask);// System.out.println("线程池中的线程数目:" + executor.getPoolSize() + ",队列中等待的任务数" + // executor.getQueue().size() + "已执行完的任务数:" + executor.getCompletedTaskCount());}executor.shutdown();} }class MyTask implements Runnable{int taskNum;public MyTask (int taskNum){this.taskNum = taskNum;}@Overridepublic void run() {System.out.println("Doing task:" + taskNum);try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Task:" + taskNum + ",accomplished!");} }
ThreadLocal
常见误解
ThreadLocal
很容易让人望文生义 想当然地认为是一个“本地线程”- 很多人会说
ThreadLocal
是为了避免线程同步问题 个人认为不太恰当正确说法
ThreadLocal
为每个线程提供独立的变量副本 所以每一个线程都可以独立地改变自己的副本 而不会影响其他线程所对应的副本
ThreadLocal
更多的是为了实现数据隔离 避免多次传参问题 围绕这点 有很多场景:
数据隔离
- 进行事务操作 用于存储线程事务信息(Spring实现事务隔离级别的源码)
- 数据库连接管理
- Cookie Session 的管理
- Android开发中 Lopper类
避免多次传参
- 在进行对象属性跨层传递的时候 使用
ThreadLocal
可以避免多次传参 打破层次间的约束混个眼熟
四个方法
- void set(Object value) 设置当前线程局部变量的值
- public Object get() 该方法返回当前线程对应的线程局部变量
- public void remove() 使用完 ThreadLocal 变量时 要将当前线程局部变量的值删除 避免因为县城周期长 而造成的内存泄露
- protected Object initialValue() 在线程第1次调用get() 或 set(Object) 方法时才执行 并且仅执行一次 ThreadLocal 中缺省实现直接返回一个 null
/*** 测试ThreadLocal*/ public class TestThreadLocal extends Thread{public static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>(){@Overrideprotected Integer initialValue() {System.out.println("初始化,第一次被调用!");return new Integer(100); //100时初始值 每格线程第一次调用set/get方法 都会调用initialValue()}};public static int getNextNum(){seqNum.set(seqNum.get() + 1);return seqNum.get();}public static ThreadLocal<Integer> seqNum1 = new ThreadLocal<Integer>(){@Overrideprotected Integer initialValue() {return new Integer(10); //初始值10}};public static int getAdd10Num1(){seqNum1.set(seqNum1.get() + 10);return seqNum1.get();}public void run(){for (int i=0;i<3;i++){System.out.println(Thread.currentThread().getName() + ",num:" + getNextNum() +",num1:" + getAdd10Num1());}//用完 就remove() 避免内存泄漏seqNum.remove();seqNum1.remove();}public static void main(String[] args) {//每个线程都有一个独立的值Thread t0 = new TestThreadLocal();Thread t1 = new TestThreadLocal();t0.start();t1.start();} }
内存泄漏 不再会被使用的对象或者变量占用的内存不能被回收 就是内存泄露
产生原因 长生命周期对象持有段生命周期的对象导致短生命周期的对象无法被释放
- 强引用
- 最普遍的应用 一个对象具有强引用 不会被垃圾回收器回收 当内存空间不足
Java
虚拟机宁愿抛出OutOfMemoryError
错误 使程序异常终止 也不回收这种对象- 如果想取消强引用和某个对象之间的关联 可以显式地将引用赋值为
null
这样可以使JVM
在合适的时间回收该对象 a = null- 弱引用
- 进行垃圾回收时 无论内存是否充足 会回收只被弱引用关联的对象
java
中 用java.lang.ref.WeakReference
类来表示弱引用可能会造成内存泄漏 如果
ThreadLocal
只被弱引用难怪会被GC
回收 回收后 没有再调用get()/set()/remove()
方法 从而造成value
对象无法回收但
ThreadLocal
内存泄漏的真正根源是:由于ThreadLocalMap
的生命周期跟Thread
一样长 如果没有手动删除对应的key
和value
就会导致内存泄漏 而不一定是弱引用每次使用完
ThreadLocal
变量 都调用他的 remove() 方法清除数据