一、File类
这个知识点很重要,在实际开发中肯会用到。比如文件下载、 文件上传、头像上传 、图片上传等都是需要使用到File类。File类是sun公司给java程序提供的操作文件和文件夹的类。
1.1相对路径和绝对路径
相对路径:
./当前工作目录
../相对于本级目录的上一级目录
../../相对于本级目录的上两级目录
绝对路径:
从磁盘的根目录 一级一级的找。
路径斜线问题:
1./ 在window和Linux系统都是可以使用的;
2.\ 在windows系统可以 ,其他系统不支持。
在开发中一般使用/
1.2File类的构造方法
File(File parent, String child) 从父抽象路径名和子路径名字符串创建新的 File 实例。 |
File(String pathname) 根据文件夹或者文件路径,抽象成为文件夹或或者文件对象 |
File(String parent, String child) 从父路径名字符串和子路径名字符串创建新的 File 实例。 |
案例:
package com.abc.c_file;
import java.io.File;
public class Demo1 {
public static void main(String[] args) {
//将磁盘c下面文件夹下面的1.txt文件抽象成为一个对象
//构造方法作用传一个路径字符串,然后java可以把它变成一个对象,然后这个对象下面有一个方法,比如删除文件方法,复制文件方法等可以用来处理c:/aaa/1.txt
//万事万物皆对象,你磁盘下面的一个文件也是当成java代码中的对象的。可以磁盘下面的文件进行操作
//开发中用第一种,根据文件夹或者文件路径,抽象成为文件夹或或者文件对象
File file = new File("c:/aaa/1.txt");
System.out.println(file);
//"c:/aaa"父路径名字符串,"1.txt"子路径名字符串
File file1 = new File("c:/aaa", "1.txt");
System.out.println(file1);
//new File("c:/aaa")是父路径抽象名,"1.txt"是子路径名字符串
File file2 = new File(new File("c:/aaa"), "1.txt");
System.out.println(file2);
//windows系统下面打印的是 \ linux系统下面打印的/
System.out.println(File.separator);
File file3 = new File("c:" + File.separator + "aaa"+ File.separator + "1.txt");
System.out.println(file3);
}
}
1.3File类的常用方法
创建文件或文件夹的方法
package com.abc.file;
import java.io.File;
import java.io.IOException;
public class Demo {
public static void main(String[] args) throws IOException {
File file = new File("c:/aaa/1.txt");
System.out.println(file.createNewFile());
//创建一个文件夹,有的话就不会再创建了,返回false
File file2 = new File("c:/aaa/ddd");
System.out.println(file2.mkdir());
//能不能创建多层级的文件夹 能,使用mkdirs()
File file3 = new File("c:/aaa/eee/fff/ggg");
System.out.println(file3.mkdirs());
File file4 = new File("c:/aaa/bbb/1.txt");
File file5 = new File("c:/aaa/100.txt");
//重命名方法,移动完以后,原始文件不存在了
System.out.println(file4.renameTo(file5));
}
}
删除文件或文件夹的方法
void | deleteOnExit() 请求在虚拟机终止时删除由此抽象路径名表示的文件或目录 |
package com.abc.file;
import java.io.File;
import java.util.Scanner;
public class Demo {
public static void main(String[] args) {
File file = new File("c:/aaa/100.txt");
//删除文件,从磁盘上面删除,不会放到回收站
System.out.println(file.delete());
//程序退了才删除
File file1 = new File("c:/aaa/99.wmv");
file1.deleteOnExit();
new Scanner(System.in).nextInt();//这条语句是用来控制程序的退出,也可以使用其他的方法来控制
//删除文件夹, 只能删除空的文件夹,如果文件夹中有东西删除不了
File file2 = new File("c:/aaa/ccc");
System.out.println(file2.delete());
}
}
File类的几个判断方法,返回值类型是boolean
boolean | isDirectory() 判断是否目录(文件夹) |
boolean | isFile() 判断是否是文件 |
boolean | isHidden() 判断是否是隐藏文件 |
package com.abc.c_file;
import java.io.File;
public class Demo4 {
public static void main(String[] args) {
//是文件
System.out.println(new File("c:/aaa/1.txt").isFile());
System.out.println(new File("c:/aaa").isFile());//false
System.out.println(new File("c:/aaa").isDirectory());//true
System.out.println(new File("c:/aaa/1.txt").isDirectory());//false
System.out.println(new File("c:/aaa/1.txt").isHidden());//false
System.out.println(new File("c:/aaa/1.txt").isAbsolute());//true
System.out.println(new File("c:/aaa/8888.txt").exists());//false
}
}
返回值是String类型的方法
package com.abc.c_file;
import java.io.File;
public class Demo {
public static void main(String[] args) {
File file = new File("c:/aaa/1.txt");
System.out.println(file.getName());
System.out.println(file.getParent());//c:/aaa
//c:\aaa\1.txt
System.out.println(file.getPath());//完整的文件路径
File file2 = new File("./");
//通过相对路径获取对应的绝对路径
System.out.println(file2.getAbsolutePath());
}
}
返回值类型是long的方法
package com.abc.c_file;
import java.io.File;
public class Demo {
public static void main(String[] args) {
//获取文件的字节数
File file = new File("c:/aaa/1.txt");
System.out.println(file.length());
//1641802255357
//从1970年的1月1日的0时0分0秒到咱们这个文件修改时间
//2022年1.10 16:10
//之间 一个毫秒数
System.out.println(file.lastModified());
}
}
文件列表方法
package com.abc.c_file;
import java.io.File;
public class Demo {
public static void main(String[] args) {
File file = new File("C:\\Program Files\\Java\\jdk1.8.0_241");
//获取当前文件夹对象下面的所有的文件和文件夹的名字
String[] listStrings = file.list();
for (String string : listStrings) {
System.out.println(string);
}
System.out.println("=========");
//打印的可用的磁盘的根目录对象
File[] files = File.listRoots();
for (File file2 : files) {
System.out.println(file2);
}
}
}
总结:
二、I/O流
计算机的cpu通过内存从磁盘上面读取数据,是一个一个自己读取的,然后先存到缓冲内存里面。大概存4kb左右。再进行整次的读取。
为什么要读取4kb做缓冲区:
1.提高读取和存储的效率
2.保护磁盘
电脑上面操作文件的时候:读取和写入数据
1.读取数据:按照CPU通过内存访问磁盘的习惯,读取4kb到缓冲区,再从内存中取出来。
磁盘(电脑里面的各种盘符)----》内存 -----》通过java代码显示数据
2.写入数据:先把数据写到内存中,然后再从内存写入到磁盘里面。
java代码的数据 -----》内存中 -----》磁盘上面(电脑里面 的各种盘符)
2.1字节输入流
package com.abc.IObytestream;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
/**
* 字节输入流
* 将磁盘下面的1.txt文本通过内存读取到java代码里面显示
* @author 紫色的贝壳
*
*/
public class Demo1 {
public static void main(String[] args) throws IOException {
//1.找到对应磁盘上面要读取的文件,变成(抽象化成)一个的对象File(万物皆对象)
File file = new File("e:\\Users\\gxt\\Desktop\\day21_gxt\\A\\1.txt");
//2.创建字节输入流对象FileInputStream,他没有缓冲的功能
FileInputStream fis = new FileInputStream(file);
/*
* BufferedInputStream具有缓冲输入的功能,当创建BufferedInputStream时,将创建一个内部缓冲数组,
*/
//3. 对FileInputStream字节输入流对象加缓冲流
BufferedInputStream bis = new BufferedInputStream(fis);//目前bis里面有1.txt的数据了
//4.将内存读取到数组中,这个数组就是缓冲区
byte[] buf = new byte[4*1024];//每次缓冲4096个字节,缓冲区的大小
//读取数据的缓冲区,读入缓冲区的总字节数,如果没有更多的数据,因为文件的结尾已经到达,-1
int readData = -1;//每次读取的字节个数
//使用循环进行读取数据bis.read(buf),当读取到最后一个的时候,没有更多的数据,(返回-1,表示数据已经读取完毕,后面没有数据了 )
while((readData = bis.read(buf)) != -1) {
System.out.println(readData);
//new String(buf,0,readCount)第1个参数:解析对象byte数组,第2个参数:从数组中的哪个下标开始解析(偏移量),第三个参数:表示解析多长
System.out.println(new String(buf,0,readData));//从磁盘上的1.txt中通过内存读取到byte数组缓冲区中,使用代码显示出来
}
//使用过后要关闭流,关闭流的原则,先用后关
bis.close();
fis.close();
}
}
字节输入流总结:
2.2字节输出流
package com.abc.IObytestream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* 字节输出流
* 将一个字符串写到从磁盘中
* @author 紫色的贝壳
*
*/
public class Demo2 {
public static void main(String[] args) throws IOException {
//实例化File文件对象被写入的文件
File file = new File("e:\\Users\\gxt\\Desktop\\day21_gxt\\A\\2.txt");
//2.使用FileOutputStream流,创建字节输出流对象,没有缓冲效果
FileOutputStream fos = new FileOutputStream(file);
//3.使用BufferedOutputStream流进行缓冲效果
BufferedOutputStream bos = new BufferedOutputStream(fos);
String string ="helloWorld";
/*
* 将字符写入到磁盘中,参数是一个byte类型的数组
* string.getBytes()是将一个字符串类型的数据变成一个字节数组
* bos在没有调用write方法的时候,文件里面的内容时空的
* 转变为字节数组的原因:字节输入在进行写的时候的最小单位是字节
*
*/
try {
//string.getBytes()把string字符串helloWorld转变成字节类型的数组
bos.write(string.getBytes(),0,6);//表示从下标0开始,写入的长度为5
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//关闭流,先用后关
bos.close();
fos.close();
}
}
字节输出流总结:
2.3复制视频的案例
代码中有2个方法,分别是带缓冲区和不带缓冲区的方法,从而可以看出缓冲区的优点:
package com.qfedu.IObytestream;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class Demo3 {
public static void main(String[] args) throws IOException {
//copyVideo();//复制视频的时间为478
copyVideo2();//复制同样的一个视频,不带缓冲区的复制时间为831
//文件越大,带有缓冲区的方法,效果会越明显
}
/*
* 复制视频的思路:
* 磁盘上面的某一个文件夹中要有视频---》写到内存中---》再写入到磁盘某个文件夹的下面
* 先使用字节输入流写到内存中,再使用字节输出流写入磁盘
*/
public static void copyVideo() throws IOException {
long start = System.currentTimeMillis();//代码走到这里会有一个时间标记
//1.创建对应的 缓冲字节输入流读到内存 和 缓冲字节输出流写到磁盘
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(new File("e:\\Users\\gxt\\Desktop\\day21_gxt\\video\\2字节输入流的入门案例.mp4")));
//从内存中写入到一个文件夹中
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(new File("e:\\Users\\gxt\\Desktop\\day21_gxt\\B\\测试复制视频.mp4")));
//2.准备一个字节数组缓冲区
byte[] buf = new byte[4*1024];//缓冲区的大小为4*1024=4k
//3.读取数据
int readData = -1;//变量
while((readData = bis.read(buf)) != -1) {//BufferedInputStream把数据读入到缓冲区数组中,
bos.write(buf,0,readData);
}
//4.关闭流
bos.close();
bis.close();
long end = System.currentTimeMillis();//又有一个时间标记
//这两个时间标记的作用:是为了计算出这两个标记之间的代码的运行时间
System.out.println(end - start);
}
//以下是不带缓冲流的写法
public static void copyVideo2() throws IOException {
long start = System.currentTimeMillis();
FileInputStream fis = new FileInputStream(new File("e:\\Users\\gxt\\Desktop\\day21_gxt\\video\\2字节输入流的入门案例.mp4"));
FileOutputStream fos = new FileOutputStream(new File("e:\\Users\\gxt\\Desktop\\day21_gxt\\B\\测试复制视频.mp4"));
//声明一个byte类型的数组,用来存放读入的数据(注意:这个数组不是缓冲区数组)
byte[] arr = new byte[4 * 1024];
int readData = -1;
while((readData = fis.read(arr)) != -1) {
fos.write(arr,0,readData);
}
fos.close();
fis.close();
long end =System.currentTimeMillis();
System.out.println(end - start);
}
}
2.4字符输入流
Reader有一个孙子类叫FileReader,专门处理文本类型的,一个非常方便的类,但是他处理音频视频图片是不行的,有可能出错。字符输入流的底层依旧是使用字节进行读取的,但是中间会进行解码操作变成字符,但是一旦解码失败。读取数据将会失败。
FileReader是不具备缓冲的效果的,BufferedReader 让其具有缓冲效果。
字符输入流案例:
package com.abc.zifustream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
/**
* 字符输入流,从磁盘读取数据到内存
* @author 紫色的贝壳
*
*/
public class CharInputStream {
public static void main(String[] args) throws IOException {
//创建File对象,也就是要读入的文件(万物皆对象)
File file = new File("e:\\Users\\gxt\\Desktop\\day21_gxt\\A\\1.txt");
//2.创建字符输入流对象
FileReader fr = new FileReader(file);
//3.对字符输入流加缓冲效果
BufferedReader br = new BufferedReader(fr);
//4.读取内容到内存中
/*
* int read(char[] cbuf,int off, int len)将字符读入数组一部分
* String readLine()读取一行文字
*/
char[] cbuf = new char[1024];//读取数据到字符数组里面(内存),底层使用的还是字节,是通过解码转为字符,读取到的字符数是一个int类型的数据,将数据读到字符数组中,返回的是字符的个数
int readCount = br.read(cbuf,0,5);
System.out.println(readCount);
System.out.println(new String(cbuf,0,readCount));//与System.out.println(cbuf);打印效果一样
}
}
read()方法的案例:
package com.abc.zifustream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
/**
* 使用read(),字符输入流从磁盘读取数据到内存
* @author 紫色的贝壳
*
*/
public class Test1 {
public static void main(String[] args) throws IOException {
File file = new File("e:\\Users\\gxt\\Desktop\\day21_gxt\\A\\1.txt");
//2.创建字符输入流对象
FileReader fr = new FileReader(file);
//3.对字符输入流加缓冲效果
BufferedReader br = new BufferedReader(fr);
//4.读取数据,read()方法返回的是accic值
int readData = -1;
while((readData = br.read()) != -1) {
System.out.println((char)readData);//强转为char
System.out.println(readData);//不强转的话,打印出的是ASSIC的十进制值,中文打印的是Unicode的十进制值
}
br.close();
fr.close();
}
}
readLine()方法的案例 :
package com.abc.zifustream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
/**
* 字符输入流从磁盘读取数据到内存
* readLine()方法,一次读取一行
* @author 紫色的贝壳
*
*/
public class Test2 {
public static void main(String[] args) throws IOException {
//1.创建File对象
File file = new File("e:\\Users\\gxt\\Desktop\\day21_gxt\\A\\1.txt");
//2.创建字符输入流对象
FileReader fr = new FileReader(file);
//3.对字符输入流加缓冲效果
BufferedReader br = new BufferedReader(fr);
//4.读取数据
String string = null;//初始化一个变量为空,用来判断读取的数据是否是最后一行
while((string = br.readLine()) != null) {
System.out.println(string);
}
//5.关闭流
br.close();
fr.close();
}
}
2.5字符输出流
package com.abc.zifustream;
/**
* 字符输出流FileWriter
* writer()
*/
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
public class Test3 {
public static void main(String[] args) throws IOException {
//1.创建File对象
File file = new File("e:\\Users\\gxt\\Desktop\\day21_gxt\\A\\2.txt");
/**
* 注意:如果要写入的文件已存在,那么此文件中原来的内容会被覆盖掉
*/
//2.创建字符写入流对象
FileWriter fw = new FileWriter(file);
//3.创建字符写入缓冲流
BufferedWriter bw = new BufferedWriter(fw);
//4.写入数据
bw.write("坚持住呀,兄弟,坚持就是胜利,不抛弃,不放弃");
//换行写
bw.newLine();
bw.write(new char[] {'1','2','3','4','5'});//写出一个字符数组
bw.write(new char[] {'a','b','c'},0,2);//对这个char数组操作,从下标为0的数据开始,写入的数据长度是2,所以写入的数据为ab
bw.write("呵呵哒嘻嘻哒",0,3);//对这个字符串进行操作,从下标为0 的字符开始,写入数据的长度是3,所以写入的数据为呵呵哒
//关闭流
bw.close();
fw.close();
}
}
三、常用类
3.1StringBuffer类
线程安全,可变的字符序列。 字符串缓冲区就像一个String,但可以修改。 在任何时间点,它包含一些特定的字符序列,但可以通过某些方法调用来更改序列的长度和内容。
String类拼接是开辟另外一个内存 ,StringBuffer直接在缓冲区域的后面直接添加。
StringBuilder(线程不安全的) 比StringBuffer(线程安全的)快 比String 快。
package com.abc.a_stringbuffer;
public class Demo1 {
public static void main(String[] args) {
StringBuffer sb = new StringBuffer("start");//默认缓冲区的容量为16
System.out.println(sb);//字符串的缓冲区在堆区
sb.append("top");//在后面追加
System.out.println(sb);//是一个区域,随时可以向里面添加
//start 字符串和 starttop这个字符字符串在同一个内存地址里面
//如果是string 拼接字符串的话,他们不再同一个内存地址里面
sb.insert(3, "大");//在某一个位置插入一个数据
System.out.println(sb);//sta大rttop
sb.delete(2, 4);// a大删除了 包含2 不包含4 要头不要尾
System.out.println(sb);
//咱们这个里面也牵涉到扩容
//确保最小的容量。这个默认的是16
//据意味着你要在内存开辟空间是耗内存的
sb.ensureCapacity(4567);//自定义确保最低的容量为4567
System.out.println(sb.length());//返回字符串的长度
}
}
3.2枚举类
Java中枚举类是一个特殊的类,一般是表示一组常量,一年的4个季节。一年12个月份。一个星期有7天。方向东西南北等。可以使用枚举代替常量。
public static final int a = 20;java 声明常量的方式。
package com.abc.a_stringbuffer;
//枚举类的语法格式 enum代替class
//枚举类只能写常量 常量大写
enum Color {
RED,GREEN, BLUE
}
public class Demo2 {
public static void main(String[] args) {
//使用枚举下面的常量
//枚举类.常量 获取当前的常量
Color c1 = Color.RED;
System.out.println(c1);
}
}
枚举的应用:
package com.abc.a_stringbuffer;
enum Week {
MON,TUE, WEN,THUR,FRI,SAT,SUN
}
public class Demo3 {
public static void main(String[] args) {
//把它想象成静态的final的类下面的属性 类名.属性
printWeek(Week.FRI);
}
public static void printWeek(Week week) {
switch (week) {
case MON:
System.out.println("星期一");
break;
case TUE:
System.out.println("星期二");
break;
case WEN:
System.out.println("星期三");
break;
case THUR:
System.out.println("星期四");
break;
case FRI:
System.out.println("星期五");
break;
case SAT:
System.out.println("星期六");
break;
case SUN:
System.out.println("星期日");
break;
default:
break;
}
}
}
3.3包装类
每个基本数据类型在java中都有与之对应的包装类。包装类下面提供了很多的方法,让咱们可以对基本数据类型进行增删改查的操作上。
int===>Integer
byte===>Byte
short====>Short
long====>Long
float====>Float
double ===>Double
char===>Character
boolean===>Boolean
除了int 和char 其他的包装了都是首字母大写。
【重点】:
1.JDK1.5之后有装箱和拆箱之说
装箱:将基本数据类型转为包装类对象
拆箱:将包装类对象转为基本数据类型
2.***Value();将包装类对象转为基本数据类型的
3.parseXxx();将字符串转为基本数据类型
4.valueOf();将基本数据类型转为包装类
package com.abc.a_stringbuffer;
public class Demo {
public static void main(String[] args) {
byte a = (byte)20;//20 int 类型的数据->byte 大转小,但是javajdk1.5之后自动转了
Byte a1 = 20; //进行自动装箱的过程 将基本数据类型转为包装类对象
Byte byte1 = new Byte((byte)20);//将基本数据类型转为包装类
System.out.println(a1);
System.out.println(byte1);
Integer integer = new Integer(250);
int int1 = integer;//自动拆箱 将包装类对象转为基本数据类型
System.out.println(int1);
Character c1Character = 'A';//装箱
Boolean boolean1= true;
//将包装类对象转为基本数据类型的方法 ***Value()
Integer integer2 = new Integer(34);
int int2 = integer2.intValue();//返回值是int 类型的数据
System.out.println(int2);
Short short1 = new Short((short)45);
short short2 = short1.shortValue();
System.out.println(short2);
int v2 = Integer.parseInt("89");//开发要用 数字字符串
System.out.println(v2);//int类型的89
//一般如果怕精度丢失的的话,先用字符串来表示,然后再转为double
double d2 = Double.parseDouble("89.99999");
System.out.println(d2);
byte byte4 = 45;
Byte b4 = Byte.valueOf(byte4);//将 基本数据类型转为包装类
System.out.println(b4);
}
}
3.4Math类
package com.abc.d_math;
import java.util.Arrays;
public class Demo {
public static void main(String[] args) {
System.out.println(Math.abs(-9.8));//绝对值
System.out.println(Math.max(1, 2));//最大值
System.out.println(Math.min(3, 5));//最小值
//求三个及三个以上数的最大值
System.out.println(Math.max(Math.max(3, 4), 5));
//要百十个的话比较大小的话,就循环吧
System.out.println(Math.ceil(3.4));//向上取整 4.0
System.out.println(Math.floor(5.6));//向下取整 5.0
System.out.println(Math.round(4.5));//四舍五入 5
System.out.println(Math.random());//[0, 1) 包含0 不包含1的随机数
//random() 返回值是double类型的数据
//随机出来0~100直接的整数
System.out.println((int)(Math.random() * 100));
}
}
3.5Random类
这个类是专门处理随机数的,提供了大量的随机数的方法。
package com.abc.e_random;
import java.util.Random;
public class Demo {
public static void main(String[] args) {
Random random = new Random();
System.out.println(random);
System.out.println(random.nextBoolean());//随机生成一个布尔类型的数据
System.out.println(random.nextInt());//随机生成一个整型的数据
System.out.println(random.nextFloat());//
System.out.println(random.nextDouble());
//100种子
Random random1 = new Random(100);
System.out.println(random1);
System.out.println(random1.nextInt());
}
}
3.6System类
System类是系统类,提供了一静态的方法和静态的属性。
package com.qfedu.f_system;
import java.util.Properties;
import java.util.Scanner;
public class Demo {
public static void main(String[] args) {
System.out.println("hello world");//正常颜色的字体
Scanner scanner = new Scanner(System.in);
System.err.println("heheda");//红色的字体
//获取当前时间 1641968933855 1970 1.1 0时0分-刚才代码执行时间之间的毫秒数
System.out.println(System.currentTimeMillis());
//获取电脑系统一些信息
Properties properties = System.getProperties();
System.out.println(properties.get("os.name"));
System.out.println(properties.get("os.version"));
System.out.println(properties.get("user.name"));
System.out.println(properties.get("user.dir"));
System.out.println(properties.get("java.version"));
}
}
3.7Runtime类
这个类提供了咱们java在运行时候的信息。
package com.abc.g_runtime;
import java.io.IOException;
public class Demo {
public static void main(String[] args) throws IOException {
Runtime runtime = Runtime.getRuntime();
System.out.println(runtime.maxMemory()/1024/1024);// 1027866624 单位是字节Java虚拟机将尝试使用的最大内存量。
System.out.println(runtime.freeMemory()/1024/1024);//空闲内存
//在咱们电脑中打开一个软件,双击这个软件,还可以通过java代码来软件
runtime.exec("C:\\Program Files (x86)\\Notepad++\\notepad++.exe");
}
}
3.8Date类
java.util.Date专门处理电脑日期的一个类。
package com.qfedu.changyonglei;
import java.util.Date;
/**
* Date类适用于专门操作电脑日期的类
* util包
* @author 紫色的贝壳
*
*/
public class DataClass {
public static void main(String[] args) {
Date date = new Date();
System.out.println(date);//Wed Jan 12 15:32:31 CST 2022
System.out.println(date.getYear()+1900);//此方法已被弃用
System.out.println(date.getMonth()+1);//获取月份,月份的范围(0~11),0表示一月
System.out.println(date.getDay());//获取星期几
System.out.println(date.getHours());//获取小时
}
}
3.9Calendar类
package com.abc.changyonglei;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
/**
* Calender是一个抽象类,很多东西是静态的
* @author 紫色的贝壳
*
*/
public class CalenderClass {
public static void main(String[] args) {
// TODO Auto-generated method stub
Calendar calendar = Calendar.getInstance();
//通过get()方法获取系统的时间常量 YEAR
System.out.println(calendar.get(Calendar.YEAR));
System.out.println(calendar.get(Calendar.MONTH));//0表示1月
System.out.println(calendar.get(Calendar.DAY_OF_MONTH));//获取本月的第几天
System.out.println(calendar.get(Calendar.DAY_OF_WEEK));//获取本周的第几天,周日是第一天,周2返回3
System.out.println(calendar.get(Calendar.DAY_OF_YEAR));//获取本年的第几天
//获取当前时间
Date time = calendar.getTime();
System.out.println(time);//Wed Jan 12 15:53:24 CST 2022
//格式化时间,把上面的时间格式变成我们都能看懂的时间
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//这个类是专门用来格式化时间的
/**
* M表示 月份
* m表示 分钟
* y表示 年份
* d表示 当月的第几天(月)
* D表示 当年的第几天(年)
* s表示 秒
* S表示 毫秒
*/
SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒");
System.out.println(sdf.format(time));//2022-01-12 03:57:39
System.out.println(sdf2.format(time));
}
}
四、线程
4.1线程的相关概念
进程:应用程序的执行实例,有独立的内存空间和系统资源。每个进程至少有一个线程。
线程:CPU调度和分派的基本单位,应用程序运算的最小单位。
进程与线程的区别:
- 进程是一个完整的程序,线程是进程里面的执行的一个功能;
- 一个应用程序(进程)是由多个线程组成的;
- 进程申请是系统的资源,线程申请是进程的资源;
- 多个线程在执行的时候,cpu会根据每个线程分配的时间片来随机抢占执行,每个线程最多占用的时间片大概是20ms,过了时间片,就切换到其他线程了。
与线程相关的几个概念:
线程特性:
1.抢占式运行
CPU在执行的时候,是按照时间片进行执行的。单位的时间片是相互抢占的 。比如qq下面有两个线程, 一个线程抢到资源 40ms(时间片)以后这两个线程再次抢占资源,谁抢的算谁的。可以一直执行下去,咱们有的电脑卡的原因,就是因为你这个进程下面线程没有抢过别的软件的线程,所以你这个应用程序的线程在等待,显示会卡顿。
2.资源共享性
一个应用程序中的线程可以共享当前的资源。比如CPU,内存,网络等等。
Java程序:
一个java程序就是一个进程 Demo的main主函数 属一个应用程序。
一个java应用程序至少有两个线程:
1.main主函数线程
2.jvm 垃圾回收
线程并行和并发:
并发Concurrent
单核cpu下,线程实际还是 串行执行的,操作系统中有一个组件叫任务调度器,将cup的时间片(windows下的时间片最短约15ms) 分给不同的线程使用,只是由于cpu(时间片很短) 在线程间的切换很快,给人一种错觉是同时运行(人一般的感知是100ms),总结一句话就是 ,微观上是串行,宏观上是并行。
并行 Parallel
多核cpu下,每个core 核都可以调度运行线程,这时候线程可以是并行的(但一般实际情况下 线程比较多,多核也是并发与并行同时存在);引用 Rob Pike(golong语言创始人,该语言并发做的很好)的一段描述:
并发(Concurrent):是同一时间应对多件事件的能力;
并行(Parallel):是同一时间 动手做多件事件的能力;
4.2创建线程的两种方式
第一种创建方式
Thread类:
1.声明一个Thread的子类,重写run方法
2.调用start开启线程
package com.abc.a_thread;
class MyThread1 extends Thread {
@Override
public void run() {
//for循环
for (int i = 0; i < 100; i++) {
System.out.println("我是Mythread1线程里面的代码");
}
}
}
class MyThread2 extends Thread {
@Override
public void run() {
//for循环
for (int i = 0; i < 100; i++) {
System.out.println("我是Mythread2线程里面的代码");
}
}
}
public class Demo1 {
public static void main(String[] args) {
//main一个线程
//实例化出来一个线程对象
MyThread1 myThread1 = new MyThread1();
//通过调用start方法开启线程一定是start方法
myThread1.start();
MyThread2 myThread2 = new MyThread2();
myThread2.start();
//4个线程 2个myThread 1main 1jvm 垃圾回收器
for (int i = 0; i < 100; i++) {
System.out.println("我是main主函数线程");
}
}
}
第二种创建方式【开发中常用的】
1.写一个类去实现Runnable接口,实现run方法
2.实例化Thread类,然后构造器的参数是一个实现Runnable接口的对象
package com.abc.a_thread;
class MyThread4 implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 100; i++) {
System.out.println("MyThread" + i);
}
}
}
public class Demo2 {
public static void main(String[] args) {
//第二种写法
Thread thread = new Thread(new MyThread4());
thread.start();//开启线程
for (int i = 0; i < 100; i++) {
System.out.println("main:" + i);
}
}
}
面试题:
4.3线程的构造方法和常用方法
Thread() 分配一个新的 Thread 对象。 |
---|
Thread(Runnable target) 分配一个新的 Thread 对象。 |
Thread(Runnable target, String name) 分配一个新的 Thread 对象, 并对当前线程起个名字 |
成员方法:
- static Thread currentThread();获取当前线程对象
- String getName();获取当前线程名字的
- void setName(String name);设置当前线程的名字
- void setPriority(int newPriority);设置当前线程的优先级。这个优先级只会增加线程执行的概率(但是真正的会不会优先是不一定的,int类型的参数,所有线程默认的优先级是5, 取值范围值 1~10 ,1的优先级是最低的,10优先级是最高的)
- int getPriority();获取当前线程优先级的
- static void sleep();让线程休眠多少毫秒(有一个问题,线程不可控,乱抢。怎么解决这样的问题呢?现在的解决方案让其睡会,就不抢占了)可以让线程睡一会儿来控制线程的执行的先后顺序
注意:在run方法中调用Thread.sleep();只能try-catch不能抛
4.4锁synchronized
synchronized 是 Java 的关键字 , 同步锁。
线程是不安全的,随机抢占的。为了让其线程有规律的执行,可以加锁。一个线程抢到资源以后,加上锁,这个线程先执行结束,会自动的释放锁,然后其他线程会进行抢占。
用法:
1.修饰一个代码块
2.修饰一个方法
3.修饰一个静态方法
4.修饰一个类
synchronized修饰代码块
一个线程访问一个对象中synchronised(this)同步代码块的时候,其他试图访问该对象的线程就会被阻塞。
语法格式:
synchronised (this) {
代码块
}
package com.abc.suo;
class SynThread1 implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub
//开始锁for循环 加上锁
/**
* 在同一时刻 线程会进到run方法中,发现有一个锁
* 假如SYN抢到了这个锁 , syn 线程在锁的外面等待着
* SYN抢到了,就要把for循环代码执行完,锁会自动释放
* syn 才抢到,加锁了,也要把下面for循环执行完
*/
synchronized (Object.class) {
//加锁的这一部分,只会让一个线程先执行完。释放锁另外一个线程再进来
//this 不是线程1 也不是线程2
System.out.println(this.toString());
for (int i = 0; i < 5; i++) {
System.out.println("线程的名字:" +Thread.currentThread().getName() );
}
}
}
}
public class Demo2 {
public static void main(String[] args) {
//开启的时候使用的是两个不同的线程类对象
Thread thread = new Thread(new SynThread1(), "SYN");
thread.start();
Thread thread1 = new Thread(new SynThread1(), "syn");
thread1.start();
//以上写法依旧是线程不安全的 乱的,为啥呢?因为new了2个对象,锁的不是同一个对象,这样对线程加锁没有作用
}
}
卖票案例:
package com.abc.c_suo;
/*
* 三个平台:淘票票 美团 猫眼 三个线程
* 三个平台需要将某一个场次 100张卖完。100张票就是共享资源
*/
class SaleTicket implements Runnable {
private static int ticket = 100;
@Override
public void run() {
// TODO Auto-generated method stub
while (true) {
//三个线程都在这个地方等着
//淘票票抢到锁了 执行if语句,其他两个线程等待 卖出第100张
//票以后 ticket--以后才会释放锁 99
//三个线程又抢 又是淘票票抢到了 打印淘票票卖出了第99张
//tiket-- 以后才会释放锁 98
//美团抢到这个锁 打印美团卖出去第98张
//tiket-- 以后才会释放这个锁 97
//三个线程还会接着再抢
synchronized (this) {
if (ticket > 0) {
//三个线程同时进来了
//打印 淘票票买了100张
//打印 美团买了100张
//打印 猫眼买了100张
//不是符合线程安全的
System.out.println(Thread.currentThread().getName()+ "售出了第" + ticket + "张");
//其他线程已经减过了 这个时候另外一个线程进来的时候票已经被减过一次
ticket--;
}else {
//tiket =0 三个线程不让卖了
System.out.println( "卖完了");
break;//循环终止
}
}
}
}
}
public class Demo3 {
public static void main(String[] args) {
SaleTicket saleTicket = new SaleTicket();
Thread thread1 = new Thread(saleTicket, "淘票票");
thread1.start();
Thread thread2 = new Thread(saleTicket, "美团");
thread2.start();
Thread thread3 = new Thread(saleTicket, "猫眼");
thread3.start();
}
}
4.5守护线程
默认情况下,java的进程需要等待所有的线程结束后 才能结束;但是有一种特殊的线程叫做 守护线程,只要其他的非守护线程运行结束了,即此时守护线程的代码没有执行完,也会强制结束;
setDaemon(true);设置线程为守护线程;默认为false;
qq 、内网通 这些聊天工具是叫非守护线程。打开QQ的聊天窗口(守护线程)。主程序退了。聊天窗口也会没了。qq是主线程 ,聊天窗口线程叫守护线程。主线程关闭,守护线程随即也会关闭。守护线程要依附于主线程执行。
package com.abc.c_suo;
class MyThread6 implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("软件更新中..........");
for (int i = 1; i < 101; i++) {
System.out.println("downloding:" + i + "%");
}
}
}
public class Demo4 {
public static void main(String[] args) {
Thread thread = new Thread(new MyThread6());
thread.setDaemon(true);//将线程设置为守护线程
thread.start();
//当主线程执行结束以后,守护线程即使没有运执行完也要关闭
//更新软件 ,软件运行。退出软件,更新就失败了
for (int i = 1; i < 31; i++) {
System.out.println("主程序在运行");
}
}
}
4.6线程的生命周期
线程创建好以后,开启线程start 方法,可运行状态,运行状态,阻塞状态,消亡
创建开启线程: start()
可运行状态:线程在抢占、在等待,如果抢到的话就接着往下执行,如果没有抢到就等待;
运行状态:真正的执行线程下面run方法;
阻塞状态:锁,sleep, wait
消亡状态:线程执行结束
4.7死锁
应用场景:并发的时候,多线程的,互不相让。
死锁一定发生在多线程之间,使用锁的目的就是线程安全的,但是物极必反。
死锁是一个状态,当两个线程互相持有对方的需要的资源的时候,却不主动释放自己的资源的时候,大以至于大家都用不了,线程就无法往下执行了。
开发中能用死锁?不能!!!因为代码走不去!!!
死锁的状态:
线程1 有锁1
线程2 有锁2
线程1 等待锁2释放....
线程2 等待锁1 释放...
package com.abc.deadlock;
/**
* 死锁:一定发生在多线程之间,不同的线程相互占用对方的资源却又不肯释放,所以造成了死锁的的现象
* @author 紫色的贝壳
*
*/
//创建线程
class DeadLockThread implements Runnable{
private boolean flag;//标记
private Object obj1;//obj1对象(Object类的对象),对象是资源
private Object obj2;//obj2对象
//有参构造,为了属性赋值
public DeadLockThread(boolean flag,Object obj1,Object obj2) {
this.flag = flag;
this.obj1 = obj1;
this.obj2 = obj2;
}
//重写run()
@Override
public void run() {
// TODO Auto-generated method stub
if(flag) {//true
synchronized(obj1) {
//线程1锁1
//Thread.currentThread().getName()获取线程的名字
System.out.println(Thread.currentThread().getName()+"锁1");
try {
Thread.sleep(1000);//让线程休眠1s
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"等待锁2释放");
System.out.println("1");
//代码走到这一步还没有走完,此时obj1这个资源不会释放
//obj1还锁着obj2的部分资源没有释放
synchronized(obj2) {
System.out.println(Thread.currentThread().getName()+"拿到了锁2的资源");
}
System.out.println("2");//慈航代码用来测试,程序是否走到了这里
}
}
if(!flag) {
synchronized(obj2) {
System.out.println(Thread.currentThread().getName()+"锁2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"等待锁1的释放");
System.out.println("3");//此行代码用来测试程序是否走到了这一步
//obj2同时也持有obj1的部分资源不予以释放
synchronized(obj1) {
System.out.println(Thread.currentThread().getName()+"拿到了锁1资源");
}
System.out.println("4");
}
}
}
}
public class Demo {
public static void main(String[] args) {
//创建两个对象,对象是共享资源
Object obj1 = new Object();
Object obj2 = new Object();
new Thread(new DeadLockThread(true,obj1,obj2),"线程1").start();
new Thread(new DeadLockThread(false,obj1,obj2),"线程2").start();
}
}
4.8线程的几个重要方法
wait(), notify() ,notifyAll() 方法都是Object 类下面的方法。这几个方法是对象来调用的。
wait();
1.让线程进入到等待状态。
2.wait方法和锁一起使用。
3.需要通过对象调用。锁对象来调用。
notify();
1.唤醒线程。
2.方法和锁一起使用。
3.需要通过对象调用。锁对象来调用。
在一个线程中书写wait方法,此线程就会阻塞,线程处于等待状态。这个时候需要另外一个线程调用notify方法进行唤醒,让等待的线程继续执行。
4.9生产者消费者模式
package com.abc.shengchazhexiaofeizh;
//共享资源, 两个线程都要操作同一个商品
class Goods {
private String name;//商品的名字
private double price;//商品的价格
private boolean shouldProduct;//没有商品是true。 有商品的话就是false
public Goods() {
}
public Goods(String name, double price, boolean shouldProduct) {
this.name = name;
this.price = price;
this.shouldProduct = shouldProduct;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public boolean isShouldProduct() {
return shouldProduct;
}
public void setShouldProduct(boolean shouldProduct) {
this.shouldProduct = shouldProduct;
}
//打印对象的时候是一个字符串能看懂的,如果不写toString方法,打印的是对象的内存地址
@Override
public String toString() {
return "Goods [name=" + name + ", price=" + price + ", shouldProduct=" + shouldProduct + "]";
}
}
//写两个线程
//消费者线程
class Customer implements Runnable {
//由于两个线程需要共享一个资源,商品资源需要实例化,给Customer
private Goods goods;
public Customer(Goods goods) {
this.goods = goods;
}
@Override
public void run() {
// TODO Auto-generated method stub
//消费多个 无限制消费
while (true) {
//让消费者睡一会
try {
Thread.sleep(4000);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
synchronized (goods) {
//false goods 这个对象下面的一个属性叫shouldProduct
if (!goods.isShouldProduct()) {
//执行的不需要生产的代码
//开始直接购买
System.out.println("消费者购买了:" + goods.getName() + ",价格为:" + goods.getPrice());
//购买完以后,这个商品就没有了,需要生产,重新标记为true
goods.setShouldProduct(true);
//购买完以后商品没了,要去唤醒生产者,开始生产
//唤醒生产者
goods.notify();
} else {
//需要生产的代码,消费者进入到等待状态
System.out.println("3");
try {
goods.wait();//没有商品的时候,需要生产的时候,消费者进入到阻状态
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("4");
}
}
}
}
}
//生产者线程
class Productor implements Runnable {
private Goods goods;
public Productor(Goods goods) {
this.goods = goods;
}
@Override
public void run() {
// TODO Auto-generated method stub
int count = 0;
while (true) {
// try {
// //让生产者线程睡一会
// Thread.sleep(4000);
// } catch (InterruptedException e1) {
// // TODO Auto-generated catch block
// e1.printStackTrace();
// }
synchronized (goods) {
if (goods.isShouldProduct()) {
//需要生产 shouldProduct 变量true
if (count % 2 == 0) {
goods.setName("奥迪RS8");
goods.setPrice(54.3);
} else {
goods.setName("五菱神车");
goods.setPrice(20.9);
}
//生产完以后,立马标记为 false
goods.setShouldProduct(false);
System.out.println("生产者生产了:" + goods.getName() + ",价格为:" + goods.getPrice());
count++;
//已经生产好了,去唤醒消费者,让消费者去消费
goods.notify();
} else {
//不需生产 的话,就能直接 让生产者等待
System.out.println("1");
try {
goods.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("2");
}
}
}
}
}
public class Demo1 {
public static void main(String[] args) {
//false 不需要生产,证明有产品
Goods goods = new Goods("保时捷" , 67.8, false);//true 需要被生产
Customer customer = new Customer(goods);
Productor productor = new Productor(goods);
new Thread(productor).start();
new Thread(customer).start();
}
}
4.10关于线程的几个案例
抢票案例:
package com.qfedu.xiancheng;
/**
* 使用多线程模拟买票的操作 三个线程(三个人)抢10张票
* 1.票号不能重复
* 2.不能超卖
*
*
* 现在的代码出现的问题:会出现重复的票号和出现超卖的情况
* 出现这种情况的原因:循环的判断和数据的修改没有绑定在一起
* 解决方案:将条件和数据修改绑定在一起(上锁)
* @author 紫色的贝壳
*
*/
public class BuyTicket3 implements Runnable{
int count = 10;
@Override
public void run() {
//为了解决一个人把票全部买完的情况,可以加个休眠,使得其他线程有机会抢到票
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
while(true) {
synchronized (this) {//this表示的是本类对象,BuyTicket3的对象buyTicket
/*
* 这里的this可以是任意对象,但是要保证锁住的是同一个对象,这个锁,锁的不是{}里面的代码,
* 而是对象,要保证锁的是同一个对象,不能是你锁你的,我锁我的,不同的对象都加的有锁,但是是不同的对象,
* 照样还是不能保证线程的安全,这个锁对他们也没有效果
*/
if(count == 0) {
break;
}
count--;
System.out.println(Thread.currentThread().getName() + "抢到第" + (10 - count) + "张票,还剩余" + count + "张票");
}
}
}
public static void main(String[] args) {
BuyTicket3 buyTicket = new BuyTicket3();//创建实现类的实例,由于这个类是接口的实现类,所以他与线程是没有关系的,这个对象是不能调用线程的相关方法的
/*
* 由于实现类的对象不能对线程进行操作,所以创建一个Thread的对象
* new Thread(buyTicket,"赵四");返回的是一个Thread的对象,
* Thread()括号里面的参数是一个接口,但是接口不能实例化,所以传进来的是这个接口的实现类的对象buyTicket,第二个参数是对线程名字赋值
*/
Thread th1 = new Thread(buyTicket,"赵四");
Thread th2 = new Thread(buyTicket,"张三");
Thread th3 = new Thread(buyTicket,"王五");
th1.start();
th2.start();
th3.start();
}
}
爬山案例:
package com.qfedu.xiancheng;
/**
* 爬山类
* 每个线程代表一个人
* 可设置每人爬山的速度,
* 每爬完100米显示信息
* 爬到终点时给出相应提示
* @author 紫色的贝壳
*
*/
public class ClimbMontion extends Thread{
int time;//爬100米耗时
int length;//总长度赋值1000米
String name;//线程名称
@Override
public void run() {
while(length>0) {
length -= 100;//每执行一次run(),总长度减100米
System.out.println(Thread.currentThread().getName()+"爬了100米,还剩余"+length+"米");
}
System.out.println("恭喜"+Thread.currentThread().getName()+"爬到了山顶");
}
//通过有参构造给属性赋值
public ClimbMontion(int length,int time,String name) {
super(name);//可调用父类的构造方法给线程的名字赋值
this.length = length;
this.time = time;
}
public static void main(String[] args) {
ClimbMontion youngMan = new ClimbMontion(1000, 500, "年轻人");
ClimbMontion oldMan = new ClimbMontion(1000, 1000, "老年人");
youngMan.start();
oldMan.start();
}
}
编写程序实现,子线程循环3次,接着主线程循环5次,接着再子线程循环3次,主线程循环5次,如此反复,循环3次。
package com.abc.d_home;
class ThreadMethod {
private boolean flag = false;
//主要封装两个方法,
//一个是主线程中 打印5遍
//synchronized 对方法加了锁 就意味着只能有进来调用
public synchronized void mainThread () {
while (!flag) {
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
for (int i = 0; i < 5; i++) {
System.out.println("mainThread" + i);
}
//唤醒打印三遍子线程
this.notify();
flag = false;
}
//打印子线程打印3遍的
public synchronized void subThread() {
while (flag) {
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
for (int i = 0; i < 3; i++) {
System.out.println("subThread" + i);
}
//执行完三次以后,就赶紧去唤醒打印5遍的主线程
this.notify();
flag= true;
}
}
public class Demo1 {
//编写程序实现,子线程循环3次,接着主线程循环5次,
//接着再子线程循环3次,主线程循环5次,如此反复,循环3次。
public static void main(String[] args) {
ThreadMethod threadMethod= new ThreadMethod();
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 3; i++) {
//对象.方法();子循环里面循环三次
threadMethod.subThread();
}
}
}).start();
//调用主线程中循环 三次
for (int i = 0; i <3; i++) {
//对象.方法();//主循环循环5次的
threadMethod.mainThread();
}
}
}
设计四个线程,其中两个线程每次对变量i加1,另外两个线程每次对i减1
package com.abc.test;
//设计四个线程,其中两个线程每次对变量i加1,另外两个线程每次对i减1。
class ThreadMethod {
private int i = 0;
/**
* 对变量 i加1
*/
public synchronized void add() {
i++;
System.out.println(Thread.currentThread().getName() + " ---- add():i = " + i);
}
/**
* 对变量 i减1
*/
public synchronized void sub() {
i--;
System.out.println(Thread.currentThread().getName() + " ---- sub():i = " + i);
}
}
public class Demo5 {
public static void main(String[] args) {
ThreadMethod threadMethod = new ThreadMethod();
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
threadMethod.add();
}
});
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
threadMethod.add();
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
threadMethod.sub();
}
});
Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
threadMethod.sub();
}
});
thread.start();
thread1.start();
thread2.start();
thread3.start();
}
}
五、单例模式
在有些系统中,为了节省内存资源、保证数据内容的一致性,对某些类要求只能创建一个实例,这就是所谓的单例模式。 单例模式的定义与特点 单例(Singleton)模式的定义:指一个类只有一个实例,且该类能自行创建这个实例的一种模式。例如,Windows 中只能打开一个任务管理器,这样可以避免因打开多个任务管理器窗口而造成内存资源的浪费,或出现各个窗口显示内容的不一致等错误。
在计算机系统中,还有 Windows 的回收站、操作系统中的文件系统、多线程中的线程池、显卡的驱动程序对象、打印机的后台处理服务、应用程序的日志对象、数据库的连接池、网站的计数器、Web 应用的配置对象、应用程序中的对话框、系统中的缓存等常常被设计成单例。
单例模式在现实生活中的应用也非常广泛,例如公司 CEO、部门经理等都属于单例模型。J2EE 标准中的 ServletContext 和 ServletContextConfig、Spring 框架应用中的 ApplicationContext、数据库中的连接池等也都是单例模式。
单例模式的特点:
- 单例类只有一个实例对象;
- 该单例对象必须由单例类自行创建;
- 单例类对外提供一个访问该单例的全局访问点。
单例模式的优点:
单例模式可以保证内存里只有一个实例,减少了内存的开销。
可以避免对资源的多重占用。
单例模式设置全局访问点,可以优化和共享资源的访问。
单例模式的缺点:
单例模式一般没有接口,扩展困难。如果要扩展,则除了修改原来的代码,没有第二种途径,违背开闭原则。在并发测试中,单例模式不利于代码调试。在调试过程中,如果单例中的代码没有执行完,也不能模拟生成一个新的对象。单例模式的功能代码通常写在一个类中,如果功能设计不合理,则很容易违背单一职责原则。
单例模式的结构与实现
单例模式是设计模式中最简单的模式之一。通常,普通类的构造函数是公有的,外部类可以通过“new 构造函数()”来生成多个实例。但是,如果将类的构造函数设为私有的,外部类就无法调用该构造函数,也就无法生成多个实例。这时该类自身必须定义一个静态私有实例,并向外提供一个静态的公有函数用于创建或获取该静态私有实例。
5.1懒汉式单例
该模式的特点是类加载时没有生成单例,只有当第一次调用 getlnstance 方法时才去创建这个单例。代码如下:
package com.qfedu.danlimoshi;
/*
* 单例模式,线程是不安全的
* 当是多线程时,每个线程就都会创建一个对象,这样会使得线程不同步,所以不安全
*
* 懒汉单例模式的优点:
* 1.节省了内存空间
* 2.保证了数据的一致性
* 缺点:效率低,加锁之后,是同步执行的,只要有一个抢占到了cpu,(运行run()是抢占资源),那么其他的线程只能在外面等待,这样会消耗更多的资源
*/
class LazySingle {
// 静态的成员变量(这个成员变量是个对象),这里设置为静态变量的原因:因为静态方法getInstance里面不能使用非静态的变量
private static LazySingle single = null;
private LazySingle() {
}
// 由于不能new对象,所以是静态的方法(静态方法与对象没有关系,静态方法早于对象的创建)
public static LazySingle getInstance() {// 通过类名,调用getInstance方法,返回一个类对象(因为主函数里面要通过这个方法来创建对象的,所以返回值类型是LazySingle的类对象)
//LazySingle.class是对LazySingle对象加锁,这种方式是通过反射进行加锁的
synchronized(LazySingle.class) {
if (single == null) {
//没有加锁的时候,这两个线程有可能同时进到这个if语句里面了,如果都为空,这两个对象会同时创建对象,这时就会创建2个不同的对象
//加锁后,只能进入一个线程,另一个线程只能在外面等待,只有进入到线程执行完毕,释放资源,另一个线程才能够进入
single = new LazySingle();// 如果single对象为空,则创建类对象
}
return single;// 如果对象不为空,则返回这个已有的对象
}
}
}
public class Single {
// 用来测试是否创建一个对象
public static void main(String[] args) {
//这种是普通的创建对象的方法
/*
* // LazySingle single1 = new LazySingle(); // LazySingle single2 = new
* LazySingle(); //两个对象的内存地址是不一样的,因为new了2次,就开辟了2个不同的内存空间
*
* 不能让new,怎么办?-----》对构造方法加一下private 加了private,一个对象也不能new了,怎么办?-----》专门弄一个公开的方法,
* 给外部提供new的方法
*
* // System.out.println(single1); // System.out.println(single2);
*/
System.out.println("————————————————————————————分隔符——————————————————————————————————");
// 线程不安全的懒汉单例模式写法
/*
* //这种写法线程是不安全的 LazySingle single1 = LazySingle.getInstance();//这次是new 对象
* LazySingle single2 = LazySingle.getInstance();//这次是使用上次new
* 的对象,因为调用getInstance方法,会有if判断,上次已创建这个对象,且没有清除,所以这次是直接使用上次已创建的对象
*
* System.out.println(single1); System.out.println(single2);
*/
//匿名内部类的写法
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
LazySingle single1 = LazySingle.getInstance();
System.out.println(single1);
}
}).start();
new Thread(new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
LazySingle single2 = LazySingle.getInstance();
System.out.println(single2);
}
}).start();
}
}
5.2饿汉式单例
该模式的特点是类一旦被加载就创建一个单例,在保证调用getInstance方法之前就已经单例就已经存在了。
package com.abc.danlimoshi;
/*
* 饿汉式的单例模式
* 线程是安全的
* 在创建对象的时候,创建好了一个静态的对象供系统使用,而且是final修饰的,以后是不可改变的
* 所以线程是安全的,可以直接在多线程中使用,且不会出现问题
*/
class Single1 {
// final修饰的对象,不可再修改
private static final Single1 single1 = new Single1();
private Single1() {
}
public static Single1 getInstance() {
// 此时不用在进行if判断了,因为这个对象是静态的且用final修饰的方法,当类加载的时候,这个方法也就被加载进来了
return single1;
}
}
public class HungerSingle {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
Single1 single1 = Single1.getInstance();
System.out.println(single1);
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
Single1 single2 = Single1.getInstance();
System.out.println(single2);
}
}).start();
}
}
六、反射
反射被视为动态语言的关键,反射机制允许程序在执行期间借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
以下是反射相关的主要API:
类加载过之后,在堆内存的方法区就产生了一个Class类型的对象,并且一个类只有一个Class对象,这个对象就包含了完整类的结构信息。这个对象就像一面镜子,透过镜子看到类的结构,所以我们称其为反射。
下面是正常方式和反射方式的比较:
反射的内存分析:
6.1获取Class对象
java 能够将.java 文件编译成为.class文件,这个.class文件包含原来类的所有的信息。.class文件在运行过程中会被ClassLoader加载到虚拟机。当.class文件加载到虚拟机以后,JVM会随之生成Class对象。字节码文件变成了Class对象。只要获取了Class对象,我就可以通过Class对象获取一个类下面的信息(属性,方法,构造方法)。
6.2获取Constructor类对象
Constructor这个类是反射所对应的构造方法的一个类对象。
- Constructor[] getConstructors();获取当前类下面的所有的public的构造方法;
- Constructor[] getDeclaredConstructors();获取当前类下面的所有的构造方法;
- Constructor getConstructor(Class ...parameterType);获取一个非私有化的构造方法;
- Constructor getDeclaredConstructor(Class ...parameterType);获取一个私有化的构造方法。
以上的方法是Class调用的。
然而,下面这个方法是Constructor对象调用的。
- Object newInstance();通过constructor对象创建出来一个对象;
package com.abc.testreflection;
public class People {
private int id;//id
private String name;//姓名
int age;//年龄
public boolean sex;//性别
//无参构造
public People() {
}
public People(int id,String name) {
this.id = id;
this.name = name;
}
protected People(int id,String name,boolean sex){
this.id = id;
this.name = name;
this.sex = sex;
}
//私有化的构造方法
private People(int age) {
this.age = age;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
private void test() {
System.out.println("我是私有化的方法");
}
public void test1() {
System.out.println("我是public的test1方法");
}
public void test2(String name) {
System.out.println("test2方法:"+name);
}
@Override
public String toString() {
return "People [id=" + id + ", name=" + name + ", age=" + age + ", sex=" + sex + "]";
}
}
package com.abc.testreflection;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class Demo1 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Class<?> clazz = Class.forName("com.qfedu.testreflection.People");
//获取.class字节码文件下面的所有public的构造方法
Constructor<?>[] constructors = clazz.getConstructors();
System.out.println(constructors);
for (Constructor<?> constructor : constructors) {
System.out.println(constructor);
}
System.out.println("-------------------------------------");
//获取.class字节码文件下面的所有的构造方法
Constructor<?>[] constructor2 = clazz.getDeclaredConstructors();
for (Constructor<?> constructor : constructor2) {
System.out.println(constructor);
}
System.out.println("==================================================");
//获取单个的构造方法
/*
* 传的参数是数据类型的Class对象,传的参数是什么类型就会获取到所对应的构造方法
*/
//获取public的一个构造方法
Constructor<?> constructor3 = clazz.getConstructor(null);
System.out.println(constructor3);
Constructor<?> constructor = clazz.getConstructor(int.class,String.class);
System.out.println(constructor);
System.out.println("***************************************");
//获取一个私有化的构造方法
Constructor<?> constructor4 = clazz.getDeclaredConstructor(int.class);
System.out.println(constructor4);
//对象的创建是依靠构造方法进行创建的,构造方法对象.newInstance(null);此时才创建了一个对象
Object object = constructor3.newInstance(null);//返回值是一个Object类型的
//也可以通过有参构造对属性进行赋值
Object object2 = constructor.newInstance(1,"张三");
System.out.println(object2);
//私有化构造方法的对象创建
constructor4.setAccessible(true);//由于私有化的构造方法不能创建对象,所以可通过暴力反射创建
Object newInstance = constructor4.newInstance(24);
System.out.println(newInstance);
}
}
6.3获取Method类对象【用来描述一个类中方法的】
- Method [] getMethods();获取当前.class文件下面的public修饰的方法,和父类的公开的方法;
- Method[] getDeclaredMethods();获取当前.class文件下面的所有方法,父类继承的方法获取不了。
package com.abc.a_reflect;
import java.lang.reflect.Method;
public class Demo{
public static void main(String[] args) throws Exception{
Class<?> aClass = Class.forName("com.qfedu.a_reflect.Person");
//获取方法
Method[] methods = aClass.getMethods();
// for (Method method : methods) {
// System.out.println(method);
// }
Method[] methods2 = aClass.getDeclaredMethods();
// for (Method method : methods2) {
// System.out.println(method);
// }
//获取单独一个方法
//getMethod方法的第一个参数是方法的名字 test1是方法的名字
//test1方法是无参的,
Method test1 = aClass.getMethod("test1");
System.out.println(test1);
Method test2 = aClass.getMethod("test2", String.class);
System.out.println(test2);
//方法对象能获取出来了,调用一下方法执行一下
//invoke(obj, args) 使用方法对象调用本身。test1方法要执行
//参数obj 你得告诉我当前test1方法在哪个对象下面
//第二个参数 是方法的参数
//哪个对象下面
//Person person = new Person();
//person.test1(); 这个之前一种方式
Object obj = aClass.getConstructor(null).newInstance(null);
//invoke 拿方法对象调用invoke方法可以执行类下面的所对应的方法
test1.invoke(obj);//使用反射的一种方式执行咱们的方法
test2.invoke(obj, "goudan");
//获取私有化的方法
Method testMethod = aClass.getDeclaredMethod("test");
testMethod.setAccessible(true);
testMethod.invoke(obj);
}
}
6.4获取Field类对象【属性】
- Field[] getFields();获取public 修饰的属性;
- Field[] getDeclaredFields();获取所有的属性;
- Filed getField(String name);通过属性的名字获取属性对象。
package com.abc.a_reflect;
import java.io.File;
import java.lang.reflect.Field;
public class Demo {
public static void main(String[] args) throws Exception{
Class<?> aClass = Class.forName("com.qfedu.a_reflect.Person");
//获取属性对象 public 修饰的属性
Field[] fields = aClass.getFields();
// for (Field field : fields) {
// System.out.println(field);
// }
//获取所有的属性
Field[] fields2 = aClass.getDeclaredFields();
for (Field field : fields2) {
System.out.println(field);
}
Person person = (Person)aClass.getConstructor(null).newInstance(null);
//获取单个属性 传参是一个属性的名字
Field field = aClass.getField("sex");
//属性进行赋值,我得知道这个属性在哪个类下下面
field.set(person, true);
System.out.println(person);
//私有化的属性进行赋值
Field field2 = aClass.getDeclaredField("name");
//对name属性进行赋值
field2.setAccessible(true);//暴力反射
field2.set(person, "张三");
System.out.println(person);
}
}
案例:
package com.abc.reflection;
public class Person {
private String name;
public int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
private Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
public void show() {
System.out.println("你好,我是紫色的贝壳~");
}
private String showNation(String nation) {
System.out.println("我来自:"+nation);
return nation;
}
}
package com.abc.reflection;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* 1.直接通过new的方式或者反射的方式都可以调用公共的结构,开发中到底用哪个? 建议:直接用new的方式 。java是准动态语言,这就是反射的应用。
* 当我们在编译的时候无法确定new哪个类的对象,这个时候采用反射的方式。此时体现反射的动态性。
* 2.反射机制与面向对象中的封装性是否矛盾,如何看待这两个技术? 不矛盾,封装性体现的是建议怎么去调用的问题,而反射体现的是能不能调用的问题。
* 3.关于java.long.Class类的理解: (1)类的加载过程: 程序经过javac.exe命令以后,会生成一个字节码文件(.class结尾)。
* 我们使用java.exe对某个字节码文件进行解释运行,相当于某个字节码文件加载到内存中,此过程称为类的加载。
* 加载到内存中的类,我们称之为运行时类。运行时类,就作为Class的一个实例 。 其实类也是对象,是Class的对象。(万物万事皆对象)
* 换句话说,Class的实例就对应着一个运行时类。
* 加载到内存中的运行时类,会缓存一定的时间,在此时间内,我们可以通过不同的方法来获取此运行时类
*
*
*
* @author 紫色的贝壳
*
*/
public class TestReflection {
public static void main(String[] args) throws Exception {
// 反射之前对Person的操作
// 1.创建Person类的对象
Person p1 = new Person("Tom", 12);
// 2.通过对象,调用其内部的属性和方法
p1.age = 10;
System.out.println(p1.toString());
p1.show();
System.out.println("*******************************");
// 在Person的外部,我们是不能通过Person的对象调用其内部私有的属性和私有方法
test2();
test3();
}
/*
* 反射之后,对Person的操作
*/
public static void test2() throws Exception {
// 通过反射创建Person类是Class的一个对象
// 通常我们把Class称作反射的源头
Class clazz = Person.class;// 运行时类就作为Class的一个实例,Person类是Class的一个对象
Constructor cons = clazz.getConstructor(String.class, int.class);// 构造器
Object obj = cons.newInstance("张三", 15);// 通过构造器创建一个对象obj,此语句是多态的体现
Person p = (Person) obj;// 向下转型
System.out.println(p.toString());// 调用的是Person的toString
// 2.通过反射,调用对象指定的属性、方法
Field age = clazz.getDeclaredField("age");
age.set(p, 23);// p是对象,23是对age的属性赋的值
System.out.println(p.toString());
// 调用方法
Method show = clazz.getDeclaredMethod("show");// 调用名为show的空参方法
show.invoke(p);// 调用p对象的show方法
System.out.println("---------------------------------");
// 3.通过反射调用Person的私有属性、私有方法、私有构造器
Constructor cons1 = clazz.getDeclaredConstructor(String.class);// 调用构造器
cons1.setAccessible(true);
Person p1 = (Person) cons1.newInstance("李四");
System.out.println(p1);
// 调用私有属性
Field name = clazz.getDeclaredField("name");
name.setAccessible(true);
name.set(p1, "王五");
System.out.println(p1);
// 调用私有的方法
Method showNation = clazz.getDeclaredMethod("showNation", String.class);
showNation.setAccessible(true);
String nation = (String) showNation.invoke(p1, "中国");// 相当于String nation = p1.showNation("中国");
System.out.println(nation);
System.out.println("++++++++++++++++++++++++++++++++++");
}
/**
* 关于java.long.Class的理解 1.类的加载过程
* 程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾),接着我们使用java.exe命令对某个字节码文件进行解释运行
* 相当于将某个字节码文件加载到内存中,此过程称为类的加载。类加载不包括(程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾))这个过程。
* 加载到内存中的类,我们就称为运行时类,此运行时类就作为Class的一个实例。
*/
// 获取Class的实例的方式
public static void test3() throws ClassNotFoundException {
// 方式1 :调用运行时类的属性.class
Class clazz1 = Person.class;
System.out.println("我是方式1的"+clazz1);
// 方式1
Class<Person> clazz = Person.class;// 加泛型可以避免后面的强转
System.out.println("我是带泛型的"+clazz);
//方式2:通过运行时类的对象
Person p2 = new Person();
Class clazz2 = p2.getClass();//getClass()来获取对象是哪个类造的
System.out.println("我是方式2的"+clazz2);
//方式3:调用Class的静态方法,forName(String classPath),指明这个class的全路径
Class clazz3 = Class.forName("com.qfedu.reflection.Person");
System.out.println("我是方式3的"+clazz3);
System.out.println(clazz1 == clazz2);//用来判断指向的是否是同一个实例
//方式4 :使用类的加载器ClassLoader
ClassLoader classLoader = TestReflection.class.getClassLoader();
Class clazz4 = classLoader.loadClass("com.qfedu.reflection.Person");
System.out.println("我是方式4的"+clazz4);
}
}
七、Lambda表达式
我们知道,对于一个java变量,我们可以赋给其一个“值”。但是如果你想把“一块代码”赋给一个Java变量,应该怎么做呢?
比如,我想把右边那块代码,赋给一个叫做aBlockOfCode的Java变量:
在Java 8之前,这个是做不到的。但是Java 8问世之后,利用Lambda特性,就可以做到了。
当然,这个并不是一个很简洁的写法。所以,为了使这个赋值操作更加elegant, 我们可以移除一些没用的声明。
这样,我们就成功的非常优雅的把“一块代码”赋给了一个变量。而“这块代码”,或者说“这个被赋给一个变量的函数”,就是一个Lambda表达式。但是这里仍然有一个问题,就是变量aBlockOfCode的类型应该是什么?在Java 8里面,所有的Lambda的类型都是一个接口,而Lambda表达式本身,也就是”那段代码“,需要是这个接口的实现。这是我认为理解Lambda的一个关键所在,简而言之就是,Lambda表达式本身就是一个接口的实现。直接这样说可能还是有点让人困扰,我们继续看看例子。我们给上面的aBlockOfCode加上一个类型:
这种只有一个接口函数需要被实现的接口类型,我们叫它”函数式接口“。为了避免后来的人在这个接口中增加接口函数导致其有多个接口函数需要被实现,变成”非函数接口”,我们可以在这个上面加上一个声明@FunctionalInterface, 这样别人就无法在里面添加新的接口函数了:
这样,我们就得到了一个完整的Lambda表达式声明:
Lambda表达式最直观的作用就是使得代码变得异常简洁。
我们可以对比一下Lambda表达式和传统的Java对同一个接口的实现:
这两种写法本质上是等价的。但是显然,Java 8中的写法更加优雅简洁。并且,由于Lambda可以直接赋值给一个变量,我们就可以直接把Lambda作为参数传给函数, 而传统的Java必须有明确的接口实现的定义,初始化才行:
案例:
package com.abc.lambda;
interface Myinterface {
void doSomthing(String s);
}
public class Demo1 {
public static void main(String[] args) {
Myinterface a = (s)-> System.out.println(s);
a.doSomthing("heheda");
enact(s-> System.out.println(s), "hello world");
}
public static void enact(Myinterface myinterface, String s){
myinterface.doSomthing(s);
}
}