package org.example.File和IO;
import java.io.File;
import java.text.SimpleDateFormat;
public class Test {
public static void main(String[] args) {
// 创建对象
// 根据文件路径创建文件对象
File file = new File("E:\\studyproject\\text1\\demo02\\src\\aaa.txt");
System.out.println(file.getName()); // 文件名
System.out.println(file.length()); // 文件字节大小
// 也可以指向文件夹
File file1 = new File("E:\\studyproject\\text1\\demo02\\src");
System.out.println(file1.length()); // 文件夹的大小
// 注意:File对象可以指代一个不存在的文件路径
File file2 = new File("E:\\studyproject\\text1\\demo02\\src\\bbb.txt");
System.out.println(file2.length()); // 不存在的文件默认0
System.out.println(file2.exists()); // false 判断文件是否存在
System.out.println("-----------");
// 判断当前文件对象对应的文件路径是否存在 存在返回true
System.out.println(file.exists()); // true
// 判断当前文件对象指代的是否是文件,是文件返回true
System.out.println(file.isFile()); // true
// 判断当前文件对象指代的是否是文件夹,是文件夹返回true
System.out.println(file.isDirectory()); // false
// 获取文件的名称(包含后缀)
System.out.println(file.getName());
// 获取文件的大小,返回字节个数
System.out.println(file.length());
// 获取文件的最后修改时间(毫秒时间戳)
long time = file.lastModified();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
System.out.println(simpleDateFormat.format(time));
// 获取创建文件对象时,使用的路径
System.out.println(file.getPath());
// 获取绝对路径
System.out.println(file.getAbsolutePath());
}
}
package org.example.File和IO;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
public class Test1 {
public static void main(String[] args) throws IOException {
// 创建文件,创建成功返回true(文件内容为空)
File file = new File("E:\\studyproject\\java\\aaa.txt");
System.out.println(file.createNewFile()); // true,可以看到文件夹下创建了aaa.txt(存在就创建失败,不存在则创建成功)
// 创建文件夹,注意只能创建一级文件夹
File file1 = new File("E:\\studyproject\\java\\bbb\\ccc");
System.out.println(file1.mkdir()); // fasle 因为创建了两层bbb和ccc文件夹
// 创建文件夹,注意可以创建多级文件夹
File file2 = new File("E:\\studyproject\\java\\bbb\\ccc");
System.out.println(file1.mkdirs()); // true
// 删除文件或者空文件,注意:不能删除非空文件夹(删除文件不进入回收站)
File file3 = new File("E:\\studyproject\\java\\aaa.txt");
System.out.println(file3.delete()); // true
}
}
package org.example.File和IO;
import java.io.File;
import java.io.IOException;
public class Test2 {
public static void main(String[] args) throws IOException {
// 获取当前目录下所有的1一级文件名称"到一个字符电数组中去返回。
File file = new File("E:\\studyproject\\java");
String[] list = file.list();
for (String s : list) {
System.out.println(s); // 目录名
}
// (重点)获取当前目录下所有的"一级文件对象"到一个文件对象数组中去返回(重点)
// 没有权限的话会返回null
// 主调是文件,或者路径不存在时返回null
// 当为空文件夹时,返回空数组
// 当为文件夹时,且里面有隐藏文件时,将里面所有文件和文件夹的路径放在file数组中返回,包含隐藏文件
// 当主调是一个有内容的文件夹时,且里面有隐藏文件时,将里面所有一级文件和文件夹的路径放在file数组中返回
File[] files = file.listFiles();
for (File file1 : files) {
// file1.delete(); // 删除遍历到的每个文件 // 不建议用
System.out.println(file1); // 全部一级文件对象的路径
System.out.println(file1.getAbsolutePath()); // 获取绝对路径
}
}
}
**分类
分为四大类
字节输入流(InputStream,FilelnputStream),
字节输出流(Outputstream,FileOutputSteam)
字符输入流(Reader,FileReader),
字符输出流(Writer,FileWriter)
括号内是(抽象类,实现类)
package org.example.File和IO;
import java.io.*;
public class Test3 {
public static void main(String[] args) throws IOException {
// 字节流
// 创建管道
FileInputStream is = new FileInputStream("E:\\studyproject\\text1\\demo02\\src\\main\\java\\org\\example\\File和IO\\xiaoji.txt");
// 文件字节输入流: 每次读取一个字节(下面文件有:ab)
// System.out.println((char) is.read()); // a
// System.out.println((char) is.read()); // b
// System.out.println(is.read()); // -1 读取不到返回-1
// 文件字节输入流: 每次读取多个字节
// 1.读取性能差的案例
// 读取汉字会乱码,无法避免的
// int b;//用于记录已经读取的字节
// while ((b=is.read())!=-1){
// System.out.println((char) b); // a b
// }
// // 流使用之后必须关闭,释放系统资源
// is.close();
// 2.文件字节输入流: 一次读取完全部字节
// 不做演示代码了,直接使用循环改造
// 性能直接得到提升,依旧读取汉字会乱码,无法避免的
// byte[] bufer = new byte[3]; // 记住每次读取了多少个字节。
// int len;
// while ((len= is.read(bufer))!=-1){
// // 注意:读取多少,倒出多少。
// String rs = new String(bufer,0,len);
// System.out.println(rs);
// }
// 3.一次读取多行文件,更优雅写法(中文不会乱码)
// 准备一个字节数组,大小与文件的大小正好一样大
// File file = new File("E:\\studyproject\\text1\\demo02\\src\\main\\java\\org\\example\\File和IO\\xiaoji.txt");
// long s = file.length(); // 获取文件的字节
// byte[] buff = new byte[(int) s]; // 不适合读取超大文件
// int len1 = is.read(buff);
// System.out.println(new String(buff));
// System.out.println(s);
// System.out.println(len1);
// 4. 一次读取全部字节(中文不会有乱码,推荐使用)
byte[] bytes = is.readAllBytes();
System.out.println(new String(bytes));
is.close();
// 文件字节输出流: 写字节出去
// 创建管道(创建一个电脑内不存在的文件,会自动生成)
// 覆盖管道,每次写入数据都会覆盖里面原有的内容
// OutputStream os = new FileOutputStream("E:\\studyproject\\text1\\demo02\\src\\main\\java\\org\\example\\File和IO\\xiaoji1.txt");
// 追加管道,每次写入数据都会向里面追加内容(只需要将第二个参数改为true)
OutputStream os = new FileOutputStream("E:\\studyproject\\text1\\demo02\\src\\main\\java\\org\\example\\File和IO\\xiaoji1.txt",true);
os.write(97); // 代表a
os.write('b'); // 代表b
os.write('靳'); // 汉字会发生乱码
os.write("我爱你".getBytes()); // 将字节数组作为参数传入进去,字符串.getBytes会转成byte字节数组
os.write("从字节数组哪个地方开始读取到哪个地方结束".getBytes(),0,15); // 一个汉字占用三个字节
// 写完数据后换行(如果是对文件进行追加内容的话,建议加个换行符)
os.write("\r\n".getBytes());
os.close(); // 执行完新创建的目标文件应该就有了:abs我爱你从字节数组
}
}
package org.example.File和IO;
import java.io.*;
public class Test5 {
public static void main(String[] args) throws IOException {
InputStream is = new FileInputStream("E:\\studyproject\\java\\JAVA相关知识点.md"); // 源文件路径
OutputStream os = new FileOutputStream("E:\\studyproject\\java\\222.md"); // 复制到目标文件的路径
// 读取源文件数据写入到目标文件
// 创建一个字节数组,负责转移字节数据
byte[] bytes = new byte[1024]; // 1kb
// 读多少写多少
int len; // 记住每次读取了多少字节
while ((len=is.read(bytes))!=-1){
os.write(bytes,0,len); // 向目标文件写入读取到的数据
}
// 关闭资源(先创建谁的流就最后再关,最后创建的先关)
os.close();
is.close();
System.out.println("复制完成,此方法适用于大部分文件复制操作");
}
}
字节流非常适合做一切文件的复制操作
package org.example.File和IO;
import java.io.*;
public class Test6 {
public static void main(String[] args) {
InputStream is = null;
OutputStream os = null;
try {
is = new FileInputStream("E:\\studyproject\\java\\JAVA相关知识点.md"); // 源文件路径
os = new FileOutputStream("E:\\studyproject\\java\\222.md"); // 复制到目标文件的路径
// 读取源文件数据写入到目标文件
// 创建一个字节数组,负责转移字节数据
byte[] bytes = new byte[1024]; // 1kb
// 读多少写多少
int len; // 记住每次读取了多少字节
while ((len=is.read(bytes))!=-1){
os.write(bytes,0,len); // 向目标文件写入读取到的数据
}
} catch (IOException e) {
e.printStackTrace();
}finally {
// 关闭资源(先创建谁的流就最后再关,最后创建的先关)
try {
if(os!=null) os.close();
} catch (IOException ex) {
throw new RuntimeException(ex);
}
try {
if(is!=null) is.close();
} catch (IOException ex) {
throw new RuntimeException(ex);
}
System.out.println("复制完成,此方法适用于大部分文件复制操作");
}
}
}
package org.example.File和IO;
import java.io.*;
public class Test7 {
public static void main(String[] args) {
try (
// 资源异常处理的资源类都放到这里
// 使用资源异常处理不需要手动关闭释放资源,会自动释放,也就是说我们只需要关心代码逻辑即可
InputStream is = new FileInputStream("E:\\studyproject\\java\\JAVA相关知识点.md"); // 源文件路径
OutputStream os = new FileOutputStream("E:\\studyproject\\java\\222.md"); // 复制到目标文件的路径
){
// 读取源文件数据写入到目标文件
// 创建一个字节数组,负责转移字节数据
byte[] bytes = new byte[1024]; // 1kb
// 读多少写多少
int len; // 记住每次读取了多少字节
while ((len=is.read(bytes))!=-1){
os.write(bytes,0,len); // 向目标文件写入读取到的数据
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
package org.example.File和IO;
import java.io.*;
public class Test8 {
public static void main(String[] args) {
// 字符输入流
// 创建文件字符流道
try (
Reader fr = new FileReader("E:\\studyproject\\text1\\demo02\\src\\main\\java\\org\\example\\File和IO\\xiaoji1.txt");
){
// 第一种方式
// int c; // 记住每次读取的字符编号
// while ((c=fr.read())!=-1){
// System.out.print((char) c); // 这里打印的即使有中文也不会乱码,每次读取一个字符,性能较差
// }
// 第二种方式
// 每次读取多个字符,性能较好
char[] buffer = new char[3];
int len; // 记录每次读取的字符数
while ((len=fr.read(buffer))!=-1){
System.out.print(new String(buffer,0,len));
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
package org.example.File和IO;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.Reader;
import java.io.Writer;
public class Test9 {
public static void main(String[] args) {
// 字符输出流
// 创建文件字符流道
try (
// 会覆盖原有数据
// Writer fr = new FileWriter("E:\\studyproject\\text1\\demo02\\src\\main\\java\\org\\example\\File和IO\\xiaoji1.txt");
// 追加内容,而不是覆盖内容
Writer fr = new FileWriter("E:\\studyproject\\text1\\demo02\\src\\main\\java\\org\\example\\File和IO\\xiaoji1.txt",true);
){
// 1. 写一个字符出去
fr.write('a'); // a
fr.write(97); // a
// 2. 写一个汉字
fr.write('靳');
// 3. 写一个字符串
fr.write("萧寂173");
// 4. 写字符串的一部分出去,从n-m
fr.write("我爱你",0,2); // 我爱
// 5.写一个字符数组出去
char[] buffer = {'萧','寂','1','7','3'};
fr.write(buffer); // 萧寂173
// 6. 写一个字符数组的一部分出去
fr.write(buffer,0,2); // 萧寂
// 7.写一个换行符
fr.write("\r\n");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
因为这里使用了资源异常处理了,会自动回收流,如果使用try-catch-finally或者其它代码内使用一定要close()关闭流或者调用flush()这个进行刷新流,否则读取或者写出不生效,切记
对原始流进行包装,以提高原始流读写数据的性能
原理:字节缓冲输入流自带了8KB缓冲池;字节缓冲输出流也自带了8KB缓冲池
还是对上面的文件复制进行改造
package org.example.File和IO;
import java.io.*;
public class Test10 {
public static void main(String[] args) {
try (
InputStream is = new FileInputStream("E:\\studyproject\\java\\JAVA相关知识点.md"); // 源文件路径
// 创建字节缓冲输入流包装原始的输入流,参数二是自定义缓冲池大小,默认是有8kb
InputStream bis = new BufferedInputStream(is,8192*2);
OutputStream os = new FileOutputStream("E:\\studyproject\\java\\222.md"); // 复制到目标文件的路径
// 创建字节缓冲输出流包装原始的输出流,参数二是自定义缓冲池大小,默认是有8kb
OutputStream bos = new BufferedOutputStream(os,8192*2);
) {
// 读取源文件数据写入到目标文件
// 创建一个字节数组,负责转移字节数据
byte[] bytes = new byte[1024]; // 1kb
// 读多少写多少
int len; // 记住每次读取了多少字节
while ((len = bis.read(bytes)) != -1) {
bos.write(bytes, 0, len); // 向目标文件写入读取到的数据
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
package org.example.File和IO;
import java.io.*;
public class Test11 {
public static void main(String[] args) {
// 字符输入流
// 创建文件字符流道
try (
Reader fr = new FileReader("E:\\studyproject\\text1\\demo02\\src\\main\\java\\org\\example\\File和IO\\xiaoji1.txt");
// 创建字符缓冲输入流包装原始的输入流,参数二是自定义缓冲池大小,默认是有8kb
BufferedReader br = new BufferedReader(fr,8192*2);
){
// 按字符读取
// char[] buffer = new char[3];
// int len; // 记录每次读取的字符数
// while ((len=br.read(buffer))!=-1){
// System.out.print(new String(buffer,0,len));
// }
// 按行读取
String line; // 记录每次读取的一行数据
while ((line=br.readLine())!=null){
System.out.println(line);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
package org.example.File和IO;
import java.io.*;
public class Test12 {
public static void main(String[] args) {
// 字符输出流
// 创建文件字符流道
try (
Writer fr = new FileWriter("E:\\studyproject\\text1\\demo02\\src\\main\\java\\org\\example\\File和IO\\xiaoji1.txt",true);
// 创建字符缓冲输出流包装原始的输入流,参数二是自定义缓冲池大小,默认是有8kb
BufferedWriter bw = new BufferedWriter(fr,8192*2);
){
bw.write('a');
bw.write(97);
// 新增换行符
bw.newLine();
bw.write('靳');
bw.write("萧寂173");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
package org.example.File和IO;
import java.io.*;
public class Test13 {
public static void main(String[] args) {
// 缓冲字符输入流
// 创建文件字符流道
try (
// 代码编码为utf-8 文件编码是GBK 就乱码了
// 必须代码编码和文件编码一致
Reader fr = new FileReader("E:\\studyproject\\text1\\demo02\\src\\main\\java\\org\\example\\File和IO\\xiaoji1.txt");
// 创建字符输入流包装成缓冲字符输入流
BufferedReader br = new BufferedReader(fr);
){
String line;
while ((line=br.readLine())!=null){
System.out.println(line);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
package org.example.File和IO;
import java.io.*;
public class Test14 {
public static void main(String[] args) {
// 字符输入转换流
// 创建文件字符流道
try (
// 1. 得到文件原始字节流(GBK字节流形式)
InputStream fr = new FileInputStream("E:\\studyproject\\text1\\demo02\\src\\main\\java\\org\\example\\File和IO\\xiaoji1.txt");
// 2、把原始的字节输入流按照指定的字符渠编码转换成字符输入
Reader isr = new InputStreamReader(fr, "GBK");
// 3.把字符输入流包装成缓冲字符输入流
BufferedReader br = new BufferedReader(isr);
) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line); // 不同编码不会乱码了
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
package org.example.File和IO;
import java.io.*;
public class Test15 {
public static void main(String[] args) {
// 字符输出转换流
// 创建文件字符流道
try (
// 指定写出去的字符编码
// 1.创建一个文件字节输出流
OutputStream fr = new FileOutputStream("E:\\studyproject\\text1\\demo02\\src\\main\\java\\org\\example\\File和IO\\xiaoji2.txt");
// 2. 把原始的字节输出流按照指定的字符集编码转换成字符输出转换流
Writer osw = new OutputStreamWriter(fr, "GBK");
// 3. 将字符输出流包装成缓冲字符输出流
BufferedWriter bw = new BufferedWriter(osw);
) {
bw.write("我爱中国");
bw.write("我是xiaoji");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
package org.example.File和IO;
import java.io.*;
import java.nio.charset.Charset;
public class Test16 {
public static void main(String[] args) {
// 打印流
try (
// 1.创建一个打印流管道(写入数据)
// PrintStream ps = new PrintStream("E:\\studyproject\\text1\\demo02\\src\\main\\java\\org\\example\\File和IO\\xiaoji1.txt");
// 2. 指定字符集编码(写入数据)
// PrintStream ps = new PrintStream("E:\\studyproject\\text1\\demo02\\src\\main\\java\\org\\example\\File和IO\\xiaoji1.txt", Charset.forName("GBK"));
// 3. 使用PrintWriter(和上面的PrintStream用法一致)
// PrintWriter ps = new PrintWriter("E:\\studyproject\\text1\\demo02\\src\\main\\java\\org\\example\\File和IO\\xiaoji1.txt", Charset.forName("GBK"))
// 4. 由于高级流不支持追加,只支持覆盖,因此如果需要追加的话需要把参数转换为低级流再追加(参数二true代表追加数据)
PrintWriter ps = new PrintWriter(new FileOutputStream("E:\\studyproject\\text1\\demo02\\src\\main\\java\\org\\example\\File和IO\\xiaoji1.txt",true))
) {
// 注意:不想换行就不要加ln
// 写入什么就是什么
ps.println("97");
ps.println("a");
ps.println("我爱你");
ps.println("爱着你");
ps.println(true);
ps.println(99.5);
// 这个是写入字节
ps.write(97); // a
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
案例,将系统的打印语句改到某一文件内
package org.example.File和IO;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
public class Test17 {
public static void main(String[] args) {
// 将系统的System.out.println();打印改到打印到某一文件内
System.out.println("111你好");
System.out.println("222你好");
try (
PrintStream ps = new PrintStream("E:\\studyproject\\text1\\demo02\\src\\main\\java\\org\\example\\File和IO\\xiaoji.txt");
) {
// 把系统默认的打印流对象改成自己设置的
System.setOut(ps);
System.out.println("333你好");
System.out.println("444你好");
// 可以发现系统只打印了111你好和222你好,而下面的打印语句都在指定的文件内
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
package org.example.File和IO;
import java.io.DataOutputStream;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
public class Test18 {
public static void main(String[] args) {
// 打印流
try (
// 1.创建一个数据输出流包装低级的字节输出流
DataOutputStream dos = new DataOutputStream(new FileOutputStream("E:\\studyproject\\text1\\demo02\\src\\main\\java\\org\\example\\File和IO\\xiaoji1.txt"));
) {
dos.writeInt(97);
dos.writeDouble(99.5);
dos.writeBoolean(true);
dos.writeUTF("萧寂真帅");
// 注意:里面数据不是乱码,可以通过此数据流读取出来
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
package org.example.File和IO;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
public class Test19 {
public static void main(String[] args) {
// 打印流
try (
// 1.创建一个数据输入流包装低级的字节输出流
DataInputStream dos = new DataInputStream(new FileInputStream("E:\\studyproject\\text1\\demo02\\src\\main\\java\\org\\example\\File和IO\\xiaoji1.txt"));
) {
// 下面读取的顺序要和写入时的数据一致,否则会报错
int i = dos.readInt();
System.out.println(i);
double i1 = dos.readDouble();
System.out.println(i1);
boolean i2 = dos.readBoolean();
System.out.println(i2);
String i3 = dos.readUTF();
System.out.println(i3);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
package org.example.File和IO;
import java.io.*;
// 注意:对象如果需要序列化,必须实现序列化接口。
class User implements Serializable {
private String loginname;
private String username;
private int age;
// transient代表不被序列化,此时就算密码有值也是为null
private transient String password;
public User() {
}
public User(String loginname, String username, int age, String password) {
this.loginname = loginname;
this.username = username;
this.age = age;
this.password = password;
}
/**
* 获取
*
* @return loginname
*/
public String getLoginname() {
return loginname;
}
/**
* 设置
*
* @param loginname
*/
public void setLoginname(String loginname) {
this.loginname = loginname;
}
/**
* 获取
*
* @return username
*/
public String getUsername() {
return username;
}
/**
* 设置
*
* @param username
*/
public void setUsername(String username) {
this.username = username;
}
/**
* 获取
*
* @return age
*/
public int getAge() {
return age;
}
/**
* 设置
*
* @param age
*/
public void setAge(int age) {
this.age = age;
}
/**
* 获取
*
* @return password
*/
public String getPassword() {
return password;
}
/**
* 设置
*
* @param password
*/
public void setPassword(String password) {
this.password = password;
}
public String toString() {
return "User{loginname = " + loginname + ", username = " + username + ", age = " + age + ", password = " + password + "}";
}
}
public class Test20 {
public static void main(String[] args) {
// 打印流
try (
// 创建对象字节输出流包装原始的字节输出流
ObjectOutputStream dos = new ObjectOutputStream(new FileOutputStream("E:\\studyproject\\text1\\demo02\\src\\main\\java\\org\\example\\File和IO\\xiaoji1.txt"));
) {
// 创建对象
User u = new User("admin", "张三", 32, "666");
// 序列化对象到文件中(注意:对象如果需要序列化,必须实现序列化接口。)
dos.writeObject(u);
System.out.println("序列化对象成功");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
package org.example.File和IO;
import java.io.*;
public class Test21 {
public static void main(String[] args) {
// 打印流
try (
// 创建对象字节输入流包装原始的字节输入流
ObjectInputStream dos = new ObjectInputStream(new FileInputStream("E:\\studyproject\\text1\\demo02\\src\\main\\java\\org\\example\\File和IO\\xiaoji1.txt"));
) {
User u = (User) dos.readObject();
System.out.println(u); // 写入的对象
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
一次性序列化多个对象的方案:将多个对象使用ArrayList进行存储,直接对集合序列化即可
ArrayList集合已经实现了序列化接口!
推荐一个框架,名字为: commons-io
- 特点
1、都只能是键值对
2、键不能重复
3、文件后缀一般是.properties结尾的
Properties
是一个Map集合(键值对集合),但是我们一般不会当集合使用。
核心作用:Properties是用来代表属性文件的,通过Properties可以读写属性文件里的内容
使用Properties读取属性文件里的键值对数据
读取Properties文件
package org.example.特殊文件;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.Properties;
import java.util.Set;
public class Test {
public static void main(String[] args) throws Exception {
// 1. 创建一个properties的对象出来(键值对集合,空容器)
Properties properties = new Properties();
System.out.println(properties);
// 2. 开始加载属性文件中的键值对数据到properties对象中去
properties.load(new FileReader("E:\\studyproject\\text1\\demo02\\src\\aaa.properties"));
System.out.println(properties); // // 拿到了全部Properties文件键值对转成的对象
// 3.根据键取值
System.out.println(properties.getProperty("username"));
System.out.println(properties.getProperty("password"));
// 4.遍历全部的键和值
Set<String> keys = properties.stringPropertyNames();
for (String key : keys) {
String value = properties.getProperty(key);
System.out.println(key+"---->"+value);
}
System.out.println("---------------");
// 也可以通过foreach
properties.forEach((k,v)->{
System.out.println(k+"---->"+v);
});
}
}
将键值对数据存储到Properties文件中去
package org.example.特殊文件;
import java.io.FileReader;
import java.io.FileWriter;
import java.util.Properties;
import java.util.Set;
public class Test2 {
public static void main(String[] args) throws Exception {
// 1. 将键值对数据存入到Properties文件中去
Properties properties = new Properties();
properties.setProperty("张无忌","minmin");
properties.setProperty("张翠山","susu");
properties.setProperty("name","你好");
properties.setProperty("age","18");
// 2. 将Properties对象中的键值对数据存入到属性文件中去
// 参数二是注释信息,随便填写就行(这个管道不需要关闭,内部会自动关闭)
properties.store(new FileWriter("E:\\studyproject\\text1\\demo02\\src\\aaa.properties"),"练习");
}
}
优缺点:
多线程的注意事项
package org.example.多线程;
// 1.子类继承Thread线程类
class MyThread extends Thread{
// 2. 必须重写run方法
@Override
public void run() {
// 描述线程的执行任务
for (int i = 0; i <= 100; i++) {
System.out.println("子线程MyThread输出"+i);
}
}
}
class Test {
public static void main(String[] args) {
// 目标:掌握线程的创建方式一:继承Thread类
// 3. 创建MyThread线程类的对象代表一个线程
Thread t = new MyThread();
t.start(); // 4. 启动线程(目前两个线程,main线程和t线程)(start实际调用了run方法)
for (int i = 0; i <= 100; i++) {
System.out.println("主线程main输出"+i);
}
// 上述代码可以发现每次执行代码主线程和子线程随机不定输出,即相互不影响各自执行各自的
}
}
方式二的优缺点
package org.example.多线程;
// 1、定义一个任务类,实现Runnable接口
class MyRunnable implements Runnable{
// 2、重写runnable的run方法
@Override
public void run() {
for (int i = 0; i < 55; i++) {
System.out.println("子线程输出"+i);
}
}
}
public class Test2 {
public static void main(String[] args) {
// 3.创建任务对象
Runnable target = new MyRunnable();
// 4.把任务对象交给线程对象来处理
new Thread(target).start();
// 主线程执行
for (int i = 0; i < 55; i++) {
System.out.println("主线程main输出 ==="+i);
}
}
}
线程创建方式二的匿名内部类写法
package org.example.多线程;
public class Test2_1 {
public static void main(String[] args) {
// 1. 直接创建Runnable接口的匿名内部类形式(任务对象)
Runnable target = new Runnable() {
@Override
public void run() {
for (int i = 1; i < 5; i++) {
System.out.println("子线程1输出"+i);
}
}
};
new Thread(target).start();
// 1-1: 简化形式
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i < 5; i++) {
System.out.println("子线程2输出"+i);
}
}
}).start();
// 1-2: 再次简化的形式
new Thread(()->{
for (int i = 1; i < 5; i++) {
System.out.println("子线程3输出"+i);
}
}).start();
// 主线程执行代码
for (int i = 0; i < 5; i++) {
System.out.println("主线程main输出"+i);
}
}
}
前两种线程创建方式都存在的一个问题
怎么解决这个问题?
多线程的第三种创建方式:利用Callable接口、FutureTask类来实现
package org.example.多线程;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
// 1、让这个类实现Callable接口
class MyCallable implements Callable<String> {
private int n;
public MyCallable(int n) {
this.n = n;
}
// 2. 重写call方法
@Override
public String call() throws Exception {
// 描述线程的任务,返回线程执行返回后的结果,
// 需求:求1-n的和返回。
int sum = 0;
for (int i = 0; i < n; i++) {
sum += i;
}
return "线程求出了1-" + n + "的和是: " + sum;
}
}
class Test3 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 3.创建一个Callable对象
Callable<String> call = new MyCallable(5);
// 4.把Callable的对象封装成一个FutureTask对象(任务对象)
// 未来任务对象的作用?
// 1、是一个任务对象,实现了Runnable对象
// 2、可以在线程执行完毕之后,用未来任务对象调用get方法获取线程执行完毕后的结果。
FutureTask<String> f1 = new FutureTask<>(call);
// 5. 把任务对象交给一个Thread对象
new Thread(f1).start();
// 3.创建一个Callable对象
Callable<String> call2 = new MyCallable(10);
FutureTask<String> f2 = new FutureTask<>(call2);
new Thread(f2).start();
// 6. 获取线程执行完毕后返回的结果
// 注意:如果执行到这儿,假如上面的线程还没有执行完毕这里的代码会暂停,等待上面线程执行完毕后才会获取结果。
System.out.println(f2.get()); // 实际调用的就是cell方法
System.out.println(f1.get()); // 实际调用的就是cell方法
}
}
线程创建方式三的优缺点
package org.example.多线程;
class MyThread1 extends Thread{
// 无参构造,方便使用默认名字(有的不想传参数)
public MyThread1(){
}
// 有参构造器
public MyThread1(String name){
// 给线程设置名字(将名字传递给父类调用)
super(name);
}
@Override
public void run() {
// 哪个线程执行它,它就会得到哪个线程对象。
Thread m = Thread.currentThread();
for (int i = 0; i <= 5; i++) {
System.out.println(m.getName()+"子线程输出"+i);
}
}
}
class Test4 {
public static void main(String[] args) throws InterruptedException {
// 目标:掌握sleep方法,join方法的作用。
for (int i = 0; i < 5; i++) {
System.out.println(i);
// 到3时休眠五秒再继续执行
if(i==3){
Thread.sleep(5000);
}
}
// join方法作用:让当前调用这个方法的线程先执行完。
// 这段代码意思就是当thread1执行完毕后会去执行thread2,当thread2执行完毕后才会去执行thread3,相当于一个异步等待
// 哪个线程带有join后面的代码都要等这个线程结束才执行
Thread thread1 = new MyThread1("子线程1");
thread1.start();
thread1.join();
Thread thread2 = new MyThread1("子线程2");
thread2.start();
thread2.join();
Thread thread3 = new MyThread1("子线程3");
thread3.start();
thread3.join();
Thread t1 = new MyThread1("子线程");
// 给子线程设置名字(上面也是相同效果,使用了有参构造,需要在类上使用有参构造),必须设置在start之前,默认名为Thread-0
// t1.setName("子线程1");
t1.start();
// 打印子线程对象的名字
System.out.println(t1.getName()); // 子线程1
Thread t2 = new MyThread1();
t2.start();
System.out.println(t2.getName()); // Thread-1
// 主线程对象
// 哪个线程执行它,它就会得到哪个线程对象。
Thread m = Thread.currentThread();
System.out.println(m); // Thread[main,5,main]
// 主线程对象的名字
System.out.println(m.getName()); // main
for (int i = 0; i <= 5; i++) {
System.out.println(m.getName()+"主线程输出"+i);
}
}
}
线程安全问题出现的原因?
上面取钱案例的bug复现(小红小明都取100000,银行只有100000,导致之后结果为-100000)
分析:
package org.example.多线程;
// 账户类
class Account {
private String CardId; // 卡号(这个案例没啥用)
private double money; // 余额
public Account(String cardId, double money) {
CardId = cardId;
this.money = money;
}
public Account() {
}
public void setCardId(String cardId) {
CardId = cardId;
}
public void setMoney(double money) {
this.money = money;
}
public String getCardId() {
return CardId;
}
public double getMoney() {
return money;
}
@Override
public String toString() {
return "Account{" +
"CardId='" + CardId + '\'' +
", money=" + money +
'}';
}
// 小明和小红同时来取钱
public void drawMoney(double money) {
// money代表的是取钱的数量
// 先搞明白谁先来取钱(取线程名字)
String name = Thread.currentThread().getName();
System.out.println(name); // 得到线程名称
// 1. 判断余额是否足够
if (this.money >= money) {
System.out.println(name + "来取钱" + money + "成功!");
this.money -= money;
System.out.println(name + "取钱后余额剩余" + this.money);
} else {
System.out.println(name + "来取钱,余额不足");
}
}
}
// 线程类
class DrawThead extends Thread {
private Account acc;
// 有参构造器
public DrawThead(Account acc, String name) {
// 设置线程名字,用于保存记录是谁在取钱
super(name);
this.acc = acc;
}
@Override
public void run() {
// 取钱(小明和小红)
acc.drawMoney(100000);
}
}
class Test5 {
public static void main(String[] args) {
// 模拟取钱(模拟线程安全问题)
// 1. 创建一个账户对象,代表两个人的共享账户.
Account acc = new Account("TCBC-110", 100000);
// 2. 创建两个线程, 分别代表小明和小红,再去同一个账户对象中取10000
new DrawThead(acc, "小明").start(); // 小明
new DrawThead(acc, "小红").start(); // 小红
}
}
线程同步的常见方案
synchronized(同步锁){
访问共享资源的核心代码
}
同步锁的注意事项
把上面取钱的案例稍加改造,将下面这段代码替换到上面的取钱的案例里面即可,然后功能就正常了
package org.example.多线程;
// 账户类
class Account {
private String CardId; // 卡号(这个案例没啥用)
private double money; // 余额
public Account(String cardId, double money) {
CardId = cardId;
this.money = money;
}
public Account() {
}
public void setCardId(String cardId) {
CardId = cardId;
}
public void setMoney(double money) {
this.money = money;
}
public String getCardId() {
return CardId;
}
public double getMoney() {
return money;
}
@Override
public String toString() {
return "Account{" +
"CardId='" + CardId + '\'' +
", money=" + money +
'}';
}
public static void test(){
synchronized (Account.class){
// 由于静态方法在计算机里面只有一份,因此官方建议静态方法使用类名.class作为锁对象
}
}
// 小明和小红同时来取钱
public void drawMoney(double money) {
// money代表的是取钱的数量
// 先搞明白谁先来取钱(取线程名字)
String name = Thread.currentThread().getName();
System.out.println(name); // 得到线程名称
// 1. 判断余额是否足够
// 加锁
// "萧寂" 字符串在计算机里面只存一份,也就是相当于给锁对象,然后这个锁在计算机里面也只有一份,后面无论创建多少对象,只要是这个线程的都会是同一把锁,例如下面的小黑和小白就是新创建了的对象但是由于钱被小明和小红取完了,导致小黑和小白没钱取了,但是对象是新创建的,按照逻辑来讲,小黑和小白应该是针对新一轮的10万元去取钱,但是由于走的同一线程,同一把锁,导致数据错乱
// 为了解决上面的问题,官方建议使用账户类对象作为锁对象,这样,小红和小明共用同一个账户,小黑和小白共用同一个账户
synchronized (this) {
if (this.money >= money) {
System.out.println(name + "来取钱" + money + "成功!");
this.money -= money;
System.out.println(name + "取钱后余额剩余" + this.money);
} else {
System.out.println(name + "来取钱,余额不足");
}
}
}
}
// 线程类
class DrawThead extends Thread {
private Account acc;
// 有参构造器
public DrawThead(Account acc, String name) {
// 设置线程名字,用于保存记录是谁在取钱
super(name);
this.acc = acc;
}
@Override
public void run() {
// 取钱(小明和小红)
acc.drawMoney(50000);
}
}
class Test5 {
public static void main(String[] args) {
// 模拟取钱(模拟线程安全问题)
// 1. 创建一个账户对象,代表两个人的共享账户.
Account acc = new Account("TCBC-110", 100000);
// 2. 创建两个线程, 分别代表小明和小红,再去同一个账户对象中取10000
new DrawThead(acc, "小明").start();// 小明
new DrawThead(acc, "小红").start(); // 小红
Account acc1 = new Account("TCBC-110", 100000);
new DrawThead(acc1, "小黑").start();
new DrawThead(acc1, "小白").start();
}
}
锁对象随便选择一个唯一的对象好不好呢?
锁对象的使用规范
修饰符 synchronized 返回值类型 方法名称(形参列表){
操作共享资源的代码
}
把上面的取钱的方法替换成下面这个,完美解决(即使和上面的小黑小白一样新创建账户类对象也是没问题的),只需要在方法前面加synchronized
// 小明和小红同时来取钱
public synchronized void drawMoney(double money) {
// money代表的是取钱的数量
// 先搞明白谁先来取钱(取线程名字)
String name = Thread.currentThread().getName();
System.out.println(name); // 得到线程名称
// 1. 判断余额是否足够
if (this.money >= money) {
System.out.println(name + "来取钱" + money + "成功!");
this.money -= money;
System.out.println(name + "取钱后余额剩余" + this.money);
} else {
System.out.println(name + "来取钱,余额不足");
}
}
同步方法底层原理
同步代码块范围小点,性能稍微好点,同步方法范围大,性能稍微差点,但是这点性能计算机几乎忽略不计
可读性:同步方法更好。
将上面的账户类替换成下面这个
// 账户类
class Account {
private String CardId; // 卡号(这个案例没啥用)
private double money; // 余额
private final Lock lk = new ReentrantLock(); // 1.创建锁对象(每个账户都应该有自己的锁对象,并且不能被替换,因此使用final修饰)
public Account(String cardId, double money) {
CardId = cardId;
this.money = money;
}
public Account() {
}
public void setCardId(String cardId) {
CardId = cardId;
}
public void setMoney(double money) {
this.money = money;
}
public String getCardId() {
return CardId;
}
public double getMoney() {
return money;
}
@Override
public String toString() {
return "Account{" +
"CardId='" + CardId + '\'' +
", money=" + money +
'}';
}
// 小明和小红同时来取钱
public void drawMoney(double money) {
String name = Thread.currentThread().getName();
// 2.加锁(程序运行到这里先加锁,执行完毕后再解锁,这里要考虑异常,因为如果加锁和解锁中间的程序出现了bug,就直接跳走了,不会执行到下面的解锁操作,因此需要使用try-catch-finally包裹)
lk.lock();
System.out.println(name); // 得到线程名称
try {
// 1. 判断余额是否足够
if (this.money >= money) {
System.out.println(name + "来取钱" + money + "成功!");
this.money -= money;
System.out.println(name + "取钱后余额剩余" + this.money);
} else {
System.out.println(name + "来取钱,余额不足");
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
// 3. 程序运行完解锁
lk.unlock();
}
}
}
什么是线程池?
不使用线程池的问题
如何创建线程池
谁代表线程池?
如何得到线程池对象?
ThreadPoolExecutor构造器
public ThreadPoolExecutor(int corePoolsize, int maximumPoolsize, long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable>workQueue, ThreadFactory threadFactory,RejectedExecutionHandler handler)
创建线程池对象
package org.example.多线程;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class Test7 {
public static void main(String[] args) {
// 通过ThreadPoolExecutor创建一个线程池对象。
// 参数一:核心线程数量(复用线程数量)
// 参数二:最大线程数量(临时线程 = 最大线程 - 核心线程)
// 参数三:临时线程存活时间
// 参数四:临时线程存活时间的单位(秒,分,时,天) TimeUnit.SECONDS 是秒
// 参数五:声明任务队列(为线程池缓存处理任务) ArrayBlockingQueue是基于数组实现的,也是常用的,参数是任务队列的大小,代表最多只能缓存几个任务
// 参数六:线程工厂,是负责为线程池创建线程的 Executors.defaultThreadFactory() 是获取默认的线程池工厂
// 参数七:任务拒绝策略(当核心线程在工作,临时线程也在工作,那么多余的任务拒绝的处理方式) new ThreadPoolExecutor.AbortPolicy() 代表新任务来了之后直接抛出异常告诉新任务无法处理
ThreadPoolExecutor pool = new ThreadPoolExecutor(3, 5, 8, TimeUnit.SECONDS, new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
}
}
线程池的注意事项
1、临时线程什么时候创建?
2、什么时候会开始拒绝新任务?
3、任务拒绝策略的方案
new ThreadPoolExecutor.AbortPolicy(); 丢弃任务并抛出RejectedExecutionException异常。是默认的策略
new ThreadPoolExecutor.DiscardPolicy(); 丢弃任务,但是不抛出异常 这是不推荐的做法
new ThreadPoolExecutor.DiscardOldestPolicy(); 抛弃队列中等待最久的任务 然后把当前任务加入队列中
new ThreadPoolExecutor.CallerRunsPolicy(); 由主线程负责调用任务的run()方法从而绕过线程池直接执行
package org.example.多线程;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
class MyRunnables implements Runnable{
@Override
public void run() {
// 任务是干啥的
System.out.println(Thread.currentThread().getName()+"===>输出666"); // 获取当前线程名字,可以清晰看到哪一个线程在执行
try {
// Thread.sleep(1000); // 每个线程都工作一秒就开始下一轮任务处理
Thread.sleep(Integer.MAX_VALUE); // 这样就可以把核心线程停留到最长时间,以便模拟当核心线程都在忙时临时线程会出来工作
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
class Test7 {
public static void main(String[] args) {
// 通过ThreadPoolExecutor创建一个线程池对象。
// 参数一:核心线程数量(复用线程数量)
// 参数二:最大线程数量(临时线程 = 最大线程 - 核心线程) 临时线程执行条件(线程池溢出后执行,例如有8个任务,核心线程执行了3个,此时线程池还剩下5个,我们设置线程池最大数量为4,就相当于超出线程池了,这时,临时线程会处理多出部分的,例如当前代码临时线程为2,设置任务条数为10个,则核心线程处理3个,四个放在线程池,多余出来3个,但是临时线程由于只有两条则只能处理两个任务,那多出来的一个任务则会被参数七的任务拒绝策略处理( 可以在第七个参数设置拒绝处理策略 ))
// 参数三:临时线程存活时间
// 参数四:临时线程存活时间的单位(秒,分,时,天) TimeUnit.SECONDS 是秒
// 参数五:声明任务队列(为线程池缓存处理任务) ArrayBlockingQueue是基于数组实现的,也是常用的,参数是任务队列的大小,代表最多只能缓存几个任务
// 参数六:线程工厂,是负责为线程池创建线程的 Executors.defaultThreadFactory() 是获取默认的线程池工厂
// 参数七:任务拒绝策略(当核心线程在工作,临时线程也在工作,那么多余的任务拒绝的处理方式)
// 参数七的拒绝策略:
// new ThreadPoolExecutor.AbortPolicy(); 丢弃任务并抛出RejectedExecutionException异常。是默认的策略
// new ThreadPoolExecutor.DiscardPolicy(); 丢弃任务,但是不抛出异常 这是不推荐的做法
// new ThreadPoolExecutor.DiscardOldestPolicy(); 抛弃队列中等待最久的任务 然后把当前任务加入队列中
// new ThreadPoolExecutor.CallerRunsPolicy(); 由主线程负责调用任务的run()方法从而绕过线程池直接执行
ThreadPoolExecutor pool = new ThreadPoolExecutor(3, 5, 8, TimeUnit.SECONDS, new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());
// 创建任务对象
MyRunnables myRunnables = new MyRunnables();
pool.execute(myRunnables); // 线程池会自动创建一个新线程,自动处理这个任务,自动执行的!
pool.execute(myRunnables);
pool.execute(myRunnables);
pool.execute(myRunnables);
pool.execute(myRunnables);
pool.execute(myRunnables);
pool.execute(myRunnables);
// 到了临时线程的创建时机
pool.execute(myRunnables);
pool.execute(myRunnables);
pool.execute(myRunnables); // 这个超出的任务会被任务拒绝策略处理
pool.execute(myRunnables); // 这个超出的任务会被任务拒绝策略处理
// 当任务执行完毕后关闭线程池
// pool.shutdown(); // 可以发现当以上6个任务都结束才关闭线程池
// 无论任务是否执行完毕,立即关闭线程池
// pool.shutdownNow(); // 会报错 "pool-1-thread-2" Exception in thread "pool-1-thread-1" Exception in thread "pool-1-thread-3" java.lang.RuntimeException: java.lang.InterruptedException: sleep interrupted
}
}
package org.example.多线程;
import java.util.concurrent.*;
// 1、让这个类实现Callable接口
class MyCallable1 implements Callable<String> {
private int n;
public MyCallable1(int n) {
this.n = n;
}
// 2. 重写call方法
@Override
public String call() throws Exception {
// 描述线程的任务,返回线程执行返回后的结果,
// 需求:求1-n的和返回。
int sum = 0;
for (int i = 0; i < n; i++) {
sum += i;
}
return Thread.currentThread().getName()+"线程求出了1-" + n + "的和是: " + sum;
}
}
public class Test8 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建线程池对象
ThreadPoolExecutor pool = new ThreadPoolExecutor(3, 5, 8, TimeUnit.SECONDS, new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());
// 使用线程处理Callable任务
Future<String> f1 = pool.submit(new MyCallable1(100));
Future<String> f2 = pool.submit(new MyCallable1(200));
Future<String> f3 = pool.submit(new MyCallable1(300));
Future<String> f4 = pool.submit(new MyCallable1(400));
Future<String> f5 = pool.submit(new MyCallable1(500));
System.out.println(f1.get());
System.out.println(f2.get());
System.out.println(f3.get());
System.out.println(f4.get());
System.out.println(f5.get());
// 上面打印的都是123线程的,此时还未使用到临时线程,因为线程池未溢出
}
}
工具类:Executors
注意:这些方法的底层,都是通过线程池的实现类ThreadPoolExecutor创建的线程池对象。
Executors.newFixedThreadPool(int nThreads) 创建固定线程数量的线程池,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程替代它。
Executors.newSingleThreadExecutor() 创建只有一个线程的线程池对象,如果该线程出现异常而结束,那么线程池会补充一个新线程。
Executors.newCachedThreadPool() 线程数量随着任务增加而增加,如果线程任务执行完与且空闲了60s则会被回收掉。
Executors.newScheduledThreadPool(int corePoolSize) 创建一个线程池,可以实现在给定的延迟后运行任务,或者定期执行任务。
package org.example.多线程;
import java.util.concurrent.*;
// 1、让这个类实现Callable接口
class MyCallable1 implements Callable<String> {
private int n;
public MyCallable1(int n) {
this.n = n;
}
// 2. 重写call方法
@Override
public String call() throws Exception {
// 描述线程的任务,返回线程执行返回后的结果,
// 需求:求1-n的和返回。
int sum = 0;
for (int i = 0; i < n; i++) {
sum += i;
}
return Thread.currentThread().getName()+"线程求出了1-" + n + "的和是: " + sum;
}
}
public class Test8 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// Executors.newFixedThreadPool(int nThreads) 创建固定线程数量的线程池,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程替代它。
// Executors.newSingleThreadExecutor() 创建只有一个线程的线程池对象,如果该线程出现异常而结束,那么线程池会补充一个新线程。
// Executors.newCachedThreadPool() 线程数量随着任务增加而增加,如果线程任务执行完与且空闲了60s则会被回收掉。
// Executors.newScheduledThreadPool(int corePoolSize) 创建一个线程池,可以实现在给定的延迟后运行任务,或者定期执行任务。
// 创建固定数量的线程池对象
ExecutorService pool = Executors.newFixedThreadPool(3);
// 核心线程数量应该配置多少???
// 计算密集型的任务: 核心线程数量 = CPU的核数 + 1
// IO密集型的任务: 核心线程数量 = CPU的核数 + 2
// 使用线程处理Callable任务
Future<String> f1 = pool.submit(new MyCallable1(100));
Future<String> f2 = pool.submit(new MyCallable1(200));
Future<String> f3 = pool.submit(new MyCallable1(300));
Future<String> f4 = pool.submit(new MyCallable1(400));
Future<String> f5 = pool.submit(new MyCallable1(500));
System.out.println(f1.get());
System.out.println(f2.get());
System.out.println(f3.get());
System.out.println(f4.get());
System.out.println(f5.get());
// 上面打印的都是123线程的,此时还未使用到临时线程,因为线程池未溢出
}
}
核心线程数量应该配置多少???
计算密集型的任务: 核心线程数量 = CPU的核数 + 1
IO密集型的任务: 核心线程数量 = CPU的核数 + 2
CPU核数查看:
windows电脑:Ctrl+Shift+Esc调出任务管理器,点击性能,点击CPU,界面有个逻辑处理器(也就是线程的数量),这就是核数
Executors使用可能存在的陷阱
大型并发系统环境中使用Executors如果不注意可能会出现系统风险
悲观锁: 一上来就加锁,没有安全感。每次只能一个线程进入访问完毕后,再解锁。线程安全,性能较差!
乐观锁: 开始不上锁,认为是没有问题的,大家一起跑,等要出现线程安全问题的时候才开始控制。线程安全,性能较好。
带有线程安全问题的代码,后面的乐观悲观锁会在这个代码基础上改造
package org.example.多线程;
class MyTest9 implements Runnable{
private int count; // 记录浏览人次
@Override
public void run() {
// 100次
for (int i = 1; i <= 100; i++) {
System.out.println("count========>"+(++count));
}
}
}
public class Test9 {
public static void main(String[] args) {
// 需求:1变量,100个线程,每个线程对其加100次。
Runnable runnable = new MyTest9();
// 主线程100次(一共10000次,多次启动代码发现有时候打印10000有时候打印9999,有时候是9998,这就造成了线程安全问题)
// 多个线程访问统一资源会造成线程安全问题
for (int i = 1; i <= 100; i++) {
new Thread(runnable).start();
}
}
}
悲观锁示例代码
一上来就加锁,没有安全感。每次只能一个线程进入访问完毕后,再解锁。线程安全,性能较差!
package org.example.多线程;
class MyTest9 implements Runnable{
private int count; // 记录浏览人次
@Override
public void run() {
// 100次
for (int i = 1; i <= 100; i++) {
// 悲观锁(每次循环都上锁都要排队,相当于10000个人排了一万次队,性能较差)
synchronized (this){
System.out.println(Thread.currentThread().getName()+"count========>"+(++count));
}
}
}
}
public class Test9 {
public static void main(String[] args) {
// 需求:1变量,100个线程,每个线程对其加100次。
Runnable runnable = new MyTest9();
// 主线程100次(一共10000次,多次启动代码发现有时候打印10000有时候打印9999,有时候是9998,这就造成了线程安全问题)
// 多个线程访问统一资源会造成线程安全问题
for (int i = 1; i <= 100; i++) {
new Thread(runnable).start();
}
}
}
乐观锁示例代码
开始不上锁,认为是没有问题的(因为计算机计算性能很高,例如刚刚的代码计算一万次,可能运行两三次才会出现一次线程不安全),大家一起跑,等要出现线程安全问题的时候才开始控制。线程安全,性能较好。
利用了CSA算法,相当于一个单向链表,每次循环都记住上一次累加的值,如果下一次循环得到的值跟期望的值不一致,就会把当前修改的值作废,就会加锁,重新匹配所记录的值
package org.example.多线程;
import java.util.concurrent.atomic.AtomicInteger;
class MyTest9 implements Runnable {
// 整数修改的乐观锁:原子类实现的。
private AtomicInteger count = new AtomicInteger(); // 记录浏览人次
@Override
public void run() {
// 100次
for (int i = 1; i <= 100; i++) {
// incrementAndGet()加一后再返回(乐观锁的方法)
System.out.println(Thread.currentThread().getName() + "count========>" + count.incrementAndGet());
}
}
}
public class Test9 {
public static void main(String[] args) {
// 需求:1变量,100个线程,每个线程对其加100次。
Runnable runnable = new MyTest9();
// 主线程100次(一共10000次,多次启动代码发现有时候打印10000有时候打印9999,有时候是9998,这就造成了线程安全问题)
// 多个线程访问统一资源会造成线程安全问题
for (int i = 1; i <= 100; i++) {
new Thread(runnable).start();
}
}
}
后面有框架封装了,这里我只学习不做笔记了
就是针对最小的功能单元(方法),编写测试代码对其进行正确性测试
可以用来对方法进行测试,它是第三方公司开源出来的(很多开发工具已经集成了Junit框架,比如IDEA)
可以灵活的编写测试代码,可以针对某个方法执行测试,也支持一键完成对全部方法的自动化测试,且各自独立。
不需要程序员去分析测试的结果,会自动生成测试报告出来不需要程序员去分析测试的结果,会自动生成测试报告出来
具体步骤