FileInputStream
FileOutputStream 原理:
//1、创建一个FileOutputStream对象,构造方法中写入数据的目的地
FileOutStream fos = new FileOutputStream("C:\\a.txt");
//2、调用FileOutputStream对象中的方法write,把数据写入文件中
//public abstract void write(int b);//将指定的字节输出
fos.write(97);
//3、释放资源(流使用会占用一定的内存,使用完毕要把内存清空,提供程序的效率)
fos.close();
创建字节输出流对象:
- 参数是字符串表示的路径或者是File对象都是可以的
- 如果文件不存在会创建一个新的文件,但是要保证父级路径是存在的
- 如果文件已经存在,默认会清空文件
写数据:
- write方法的参数是整数,但是实际上写到本地文件中的是整数在ASCII上对应的字符
释放资源:
- 每次使用完都要释放资源
FileOutputStream写数据的三种方式:
void write(int b)
void write(byte[] b)
void write(byte[] b,int off,int len)
import java.io.FileOutputStream;
import java.io.IOException;
public class ByteStreamDemo {
public static void main(String[] args) throws IOException {
FileOutputStream fos=new FileOutputStream("D:\\javaj\\first\\src\\a.txt");
fos.write(97);
fos.close();
}
}
换行写和续写:
如果要传入数字,可以用字符串转为 byte 形式写入;也可以传入 ACSII 对应的值(例如 0 的 ASCII 码值是 48)。
如果写入的数据要换行,在两条写入语句之间加入:
String str="\r\n";byte []arr=str.getBytes();
默认情况下,打开已有文件时,会清空文件。
如果要续写,打开续写开关就可以了:开关位置在创建对象的第二个参数。
FileOutputStream fos=new FileOutputStream("D:\\javaj\\first\\src\\a.txt",true);
FileOutputStream
操作本地文件的字节输入流,可以把本文件中的数据读取到程序中来。
创建字节输入流对象:
- 如果文件不存在,就直接报错
读取数据:
- 一次读一个字节,读出来的就是书籍在 ASCII 码上对应的数字
- 读到文件末尾了,read 方法返回 -1.(问:如果文件当中存在“-1”,怎么处理? 答:read方法将“-1”分为“-”和“1”处理,不会读到“-1”的结果)
释放资源
- 每次使用完流必须要释放资源
循环读取:
import java.io.FileInputStream;
import java.io.IOException;
public class ByteStreamDemo3 {
public static void main(String[] args) throws IOException {
FileInputStream fir =new FileInputStream("D:\\javaj\\first\\src\\a.txt");
int b;
while((b=fir.read())!=-1){
System.out.println((char)b);
}
fir.close();
}
}
问:这里定义的局部变量时多余的吗?
答:不是,如果没有这个局部变量,那输出时将这样写:
System.out.println(fir.read());
并且while语句中也存在 fir.read 语句,所以一次循环将读取两次,此时可以用一个局部变量存放读取的值。
文件拷贝
对于较小的文件,可以一个一个字节读取:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class ByteStreamDemo4 {
public static void main(String[] args) throws IOException {
//创建对象
FileInputStream fis=new FileInputStream("D:\\20230414154402.mp4");
FileOutputStream fos= new FileOutputStream("D:\\javaj\\first\\src\\cope.mp4");
//拷贝(边读边写)
int b;
while((b=fis.read())!=-1){
fos.write(b);
}
//释放资源(先开的后关)
fos.close();
fis.close();
}
}
所以想要一次读多个字节:
一次读多少个字节跟定义的数组长度有关。
import java.io.FileInputStream;
import java.io.IOException;
public class ByteStreamDemo5 {
public static void main(String[] args) throws IOException {
//创建对象(如果文件中只有五个字符:abcde)
FileInputStream fis=new FileInputStream("D:\\javaj\\first\\src\\a.txt");
//读取数据
byte[] bytes1 =new byte[2];
byte[] bytes2 =new byte[2];
byte[] bytes3 =new byte[2];
//判断一次读取多少个字节,跟数组长度有关
int len1=fis.read(bytes1);//第一次读取
System.out.println(len1);//2
int len2=fis.read(bytes2);//第二次读取
System.out.println(len2);//2
int len3=fis.read(bytes3);//第三次读取
System.out.println(len3);//1
//将刚刚读到的转换为字符串类型,并输出
String str1 =new String(bytes1,0,len1);
String str2 =new String(bytes2,0,len2);
String str3 =new String(bytes3,0,len3);
System.out.println(str1);//ab
System.out.println(str2);//cd
System.out.println(str3);//e
//释放资源
fis.close();
}
}
在拷贝大文件时,可以定义一个较大的byte数组,接下来读取的次数就大大减少:
byte[] bytes=new byte[1024*1024*5];
try…catch…finally
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class ByteStreamDemo6 {
public static void main(String[] args) {
FileInputStream fis=null;
FileOutputStream fos=null;
try{
fis =new FileInputStream("D:\\javaj\\first\\src\\cope.mp4");
fos =new FileOutputStream("D:\\20230414154402.mp4");
int len;
byte[]bytes=new byte[1024*1024*5];
while((len=fis.read(bytes))!=-1){
fos.write(bytes);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fos.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
try {
fis.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
字符集
GDK中:
一个英文占一个字节
一个中文占两个字节
Unicode字符集的UTF-8编码格式:
一个英文是用一个字节,二进制第一位是0,转成十进制是正数
一个中文是用三个字节,二进制第一位是1,第一个字节转成十进制是负数
UTF-8不是字符集。
乱码出现的原因:
- 读取数据是未读完整个汉字
- 编码和解码时的方式不统一
所以不要用字节流读取文本文件。
编码解码使用同一个码表,同一个编码方式。
字节流读取中文会乱码,因为是一个一个字节读的,但是拷贝不会出现乱码,数据没有丢失。
编码方式
编码
public byte[] getBytes() //默认方式进行编码(UTF-8)
public byte[] getBytes(String charseName) //指定方式进行编码
解码
String (byte[] bytes) // //默认方式进行解码(UTF-8)
String(byte[] bytes,String charseName) //指定方式进行解码
字符输入流
1.创建字符输入流关联本地文件
public FileReader(file file)
public FileReader(String pathname)
2.读取数据,读到末尾返回-1
public int Read()
public int Read(char[] buffer)//读取多个数据
- 按字节进行读取,遇到中文,一次读多个字节,读取后解码,返回一个整数
- 读到文件末尾了,read方法返回-1
3.释放资源
文本文件:
import java.io.FileReader;
import java.io.IOException;
public class CharStreamDemo {
public static void main(String[] args) throws IOException {
FileReader fr=new FileReader("D:\\javaj\\first\\src\\a.txt");
int ch;
while((ch=fr.read())!=-1){
System.out.print((char)ch);//输出的时候强转,就可以看到文本文件了
}
fr.close();
}
}
空参的构造方法:
int ch=fr.read();//返回的是读到的数字
fr.read(chars);//读取数据、解码、强转合并,将强转之后的字符放到数组中
字符输出流
1、创建字符输出流对象(底层:关联文件,并创建缓冲区(长度为8192的字节数组))
- 参数是字符串表示的路径或者File对象都是可以的
- 如果文件不存在会创建一个新文件,但是父级路径要存在
- 如果文件存在,默认会清空文件,如果要续写可以打开续写开关
2、写数据(底层:
判断缓冲区有没有数据可以读取
缓冲区没有数据:从文件中获取数据,装到缓冲区,如果文件也没有数据了,就返回-1
缓冲区有数据:从缓冲区读取)
- 如果write方法的参数是整数,但是实际上写到本地文件中的是整数在字符集上对应的字符
3、释放资源
flush和close方法
public void flush() 将缓冲区中的数据,刷新到本地文件中
刷新之后还可以继续往文件中写出数据
public void close() 释放资源/关流
断开通道,无法再向文件写出数据
字节流
- 拷贝任意类型的文件
字符流
- 读取纯文本文件中的数据
- 往纯文本文件中写出数据
拷贝的是文件或者是文本
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class Test01 {
public static void main(String[] args) throws IOException {
//拷贝一个文件夹,考虑子文件
//创建对象表示数据源
File test = new File("D:\\javaj\\first\\test");
//创建对象表示目的地
File dest=new File("D:\\dest");
//调用方法开始拷贝
copydir(test,dest);
}
private static void copydir(File test,File dest) throws IOException {
dest.mkdirs();//创建文件夹
File[] files=test.listFiles();
for(File file:files){
if(file.isFile()){
FileInputStream fis=new FileInputStream(file);
FileOutputStream fos =new FileOutputStream(new File(dest,file.getName()));
byte[]bytes=new byte[1024];
int len;
while((len=fis.read(bytes))!=-1){
fos.write(bytes,0,len);
}
fos.close();
fis.close();
}
else{
copydir(file,new File(dest,file.getName()));
}
}
}
}
读取数据排序后存入文件:
FileReader fr=new FileReader("D:\\javaj\\first\\test\\aaa.txt");
StringBuilder sb=new StringBuilder();
int ch;
while((ch=fr.read())!=-1){
sb.append((char)ch);
}
fr.close();
System.out.println(sb);
String str = sb.toString();
String[]arrStr=str.split("-");
ArrayList<Integer> list=new ArrayList<>();
for (String s : arrStr) {
int i=Integer.parseInt(s);
list.add(i);
}
Collections.sort(list);
FileWriter fw=new FileWriter("D:\\test.txt");
for (int i = 0; i < list.size(); i++) {
if(i==list.size()-1){
fw.write(list.get(i)+"");
}
else {
fw.write(list.get(i)+"-");
}
}
fw.close();
字节缓冲流和字符缓冲流
在创建对象时包装了FileOutputStream和FileInputStream。内部创建一个缓冲区数组,使用 8192 个字节的缓冲区,增加缓冲功能,避免频繁读写硬盘。
字节缓冲流,明显加快了文件的读写效率
字符缓冲流,相对比字符流的话,已经存在缓冲区了,但是在缓冲流存在两个有用的方法:
整行读取(遇到换行会停止,但是不会读取到换行符)
BufferedReader br=new BufferedReader(new FileReader("D:\\javaj\\first\\test\\aaa.txt"));
String line=br.readLine();
如果读不到,不是返回的-1,是返回null。
换行方法(用于写入一个行分隔符,方法会根据不同的平台自动写入相应的行分隔符,因此使用newline()方法能够保证写入的文本文件在不同平台下都能以正确的方式表示每一行末尾的分隔符。)
public static void main(String[] args) throws IOException {
BufferedWriter bw=new BufferedWriter(new FileWriter("D:\\javaj\\first\\test\\aaa.txt",true));
bw.write("123");
bw.newLine();
bw.write("456");
bw.close();
}
练习:1.根据文件中的序号排序 2.控制软件运行次数
import java.io.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
//根据文件中的序号排序
public class Test04 {
public static void main(String[] args) throws IOException {
BufferedReader br=new BufferedReader(new FileReader("D:\\javaj\\first\\test\\aaa.txt"));
String line;
ArrayList<String> list=new ArrayList<>();
while((line=br.readLine())!=null){
list.add(line);
}
br.close();
BufferedWriter bw=new BufferedWriter(new FileWriter("D:\\test.txt"));
Collections.sort(list,new Comparator<String>(){
public int compare(String o1,String o2){
int i1=Integer.parseInt(o1.split("\\.")[0]);
int i2=Integer.parseInt(o2.split("\\.")[0]);
return i1-i2;
}
});
System.out.println(list);
for (String s : list) {
bw.write(s);
bw.newLine();
}
bw.close();
}
}
转换流
在Java中,字符是以Unicode编码存储的,但是文件通常是以字节的形式存储的,因此在读写文本文件时就需要使用转换流来进行转换,这样才能正确地读写文件内容。
InputStreamReader将字节输入流转换为字符输入流,它通过指定字符集将字节格式的数据解码成字符格式的数据。OutputStreamWriter则将字符输出流转换为字节输出流,也通过指定字符集将字符格式的数据编码成字节格式的数据。
BufferedReader br=new BufferedReader(new InputStreamReader(new FileInputStream("D:\\javaj\\first\\test\\aaa.txt")));
序列化流
将被序列化的流要继承 Serializable 类,这个类没有抽象类,只是一个标记,有了这个标记才可以被序列化。
序列化:
public class ObjectStudent {
public static void main(String[] args) throws IOException {
Student stu=new Student("zhang",24);
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("D:\\javaj\\first\\aaa.txt"));
oos.writeObject(stu);
oos.close();
}
}
反序列化:
public class ObjectStreamDemo2 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectInputStream ois =new ObjectInputStream(new FileInputStream("D:\\javaj\\first\\aaa.txt"));
Student o=(Student ) ois.readObject();
System.out.println(o);
ois.close();
}
}
版本号的作用在于,在类的定义发生变化时,比如增加或删除了成员变量或方法,版本号也会随之改变,这样就可以避免反序列化时出现异常。
瞬态序列化:
用transient关键字修饰变量,不会把当前数学序列化到本地文件中。
读写多个对象,并且不知道对象的个数,如果循环往下读,读到文件末尾了,并不会返回-1或者null,而是会抛出EOFException
异常,所以循环读取时,可以用try…catch…语句捕获异常,另一个方法是:把对象放在集合中,一次写入文件,然后读取的时候也只要读取一次。
public class ObjectStudentDemo3 {
public static void main(String[] args) throws IOException {
Student2 st1=new Student2("zhang",15,"南京");
Student2 st2=new Student2("wang",17,"重庆");
Student2 st3=new Student2("li",20,"北京");
ArrayList<Student2>list=new ArrayList<>();
list.add(st1);
list.add(st2);
list.add(st3);
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("D:\\javaj\\first\\test\\aaa.txt"));
oos.writeObject(list);
oos.close();
}
}
public class ObjectStudentDemo4 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectInputStream ois=new ObjectInputStream(new FileInputStream("D:\\javaj\\first\\test\\aaa.txt"));
ArrayList<Student2> list = (ArrayList<Student2>) ois.readObject();
for (Student2 student2 : list) {
System.out.println(student2);
}
}
}
打印流
只有输出流(写),没有输入流(读)。
打印流是指:PrintStream(字节打印流)、PrintWrite(字符打印流)两个类。
- 只能操作文件目的地,不操作数据源。
- 特有的方法可以事项,数据原样写出(打印:97,文件中:97)
- 特有的写出方法,可以实现自动刷新,自动换行(打印一次数据=写出+换行+刷新—)
字节打印流:
字节流底层没有缓冲区,开不开自动刷新都一样。
特有方法:
public void println(Xxx xx) //打印任意数据,自动刷新和换行
public void print(Xxx xx) //打印任意数据,不换行
public void printf(String format,Object…args) //带有占位符的打印语句,不换行
字符打印流:
字符流底层有缓冲区,想要自动刷新需要开启,它的效率更高。
特有方法与字节打印流一样。
解压缩流
解压本质:把每一个zipEntry按照层级拷贝到本地另一个文件夹中。
public class ZipStreamDemo1 {
public static void main(String[] args) throws IOException {
File src =new File("D:\\javaj\\first\\test.zip");
File dest=new File("D:\\");//这里要创建一个test的文件夹,否则找不到路径
unzip(src,dest);
}
public static void unzip(File src,File dest) throws IOException {
ZipInputStream zip=new ZipInputStream(new FileInputStream(src));
ZipEntry entry;
while((entry=zip.getNextEntry())!=null){
System.out.println(entry);
if(entry.isDirectory()){
//文件夹:需要创建一个文件夹
File file=new File(dest,entry.toString());
file.mkdirs();//创建一个文件夹
}
else{
//文件:需要读取到压缩包中的文件,并把它存放到目的地dest文件夹中
FileOutputStream fos=new FileOutputStream(new File(dest,entry.toString()));
int b;
while((b=zip.read())!=-1){
fos.write(b);
}
fos.close();
zip.closeEntry();
}
}
zip.close();
}
}
压缩本质:把每一个(文件/文件夹)看成ZipEntry对象放在压缩包中。
//压缩一个文件夹
public class ZipStreamDemo3 {
public static void main(String[] args) throws IOException {
//创建File对象表示要压缩的文件夹
File src =new File("D:\\javaj\\first\\test");
//创建File对象表示压缩包放在哪里
File destParent=src.getParentFile();
//创建File对象表示压缩包的路径
File dest=new File(destParent,src.getName()+".zip");
//创建压缩流关联压缩包
ZipOutputStream zos=new ZipOutputStream((new FileOutputStream(dest)));
//获取src里面的每一个文件,放入压缩包中
toZip(src,zos,src.getName());//test
//释放资源
zos.close();
}
public static void toZip(File src,ZipOutputStream zos,String name) throws IOException {
//进入src文件夹
File[] files =src.listFiles();
for (File file : files) {
if(file.isFile()){
ZipEntry entry=new ZipEntry(name+"\\"+file.getName());
zos.putNextEntry(entry);
FileInputStream fis=new FileInputStream(file);
int b;
while((b=fis.read())!=-1){
zos.write(b);
}
fis.close();
zos.closeEntry();
}
else {
toZip(file,zos,name+"\\"+file.getName());
}
}
}
}
Comments工具包
Hutool工具包
多线程
线程是操作系统能够运行调度的最小单位,他被包含在进程之中,是进程中的实际运作单位。
进程是程序的基本执行实体。
多线程:软件中互相独立独立,可以同时运行的功能
并发:在同一时刻,有多个指令在单个CPU上交替执行。
并行:在同一时刻,有多个指令在单个CPU上
实现方式
-
继承Thread类的方式进行实现
-
实现Runnable接口的方式进行实现
-
利用Callable接口和Future接口方式实现(重写call,可以获取到多线程运行的结果)
//第一个方法
//先创建一个MyTread类继承Thread,然后里面可以是输出的方法
//接着用类去调用
public class Tread {
public static void main(String[] args) {
MyTread t1=new MyTread();
MyTread t2=new MyTread();
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
//第二个方法
//先定义一个Myrun类实现Runnable接口
//接着可以调用
public class ThreadDemo1 {
public static void main(String[] args) {
MyRun mr=new MyRun();
Thread t1=new Thread(mr);
Thread t2=new Thread(mr);
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
//第三个方法
//定义的类实现Callable接口
public class MyCallable implements Callable<Integer> {
public Integer call(){
int sum=0;
for (int i = 0; i < 100; i++) {
sum=sum+i;
}
return sum;
}
}
//接着
public class ThreadDemo2 {
public static void main(String[] args) throws ExecutionException, InterruptedException {//创建一个类MyCallable实现Callable接口
//重写call(有返回值)
//创建MyCallable对象(表示多线程哟啊执行的任务)
MyCallable mc = new MyCallable();
//创建FutureTask对象(作用管理多线程运行的结果)
FutureTask<Integer> ft = new FutureTask<>(mc);
//创建Thread类的对象,并启动(表示线程)
Thread t1 = new Thread(ft);
t1.start();
Integer result = ft.get();
System.out.println(result);
}
}
常用的成员方法
void setName(String name);
- 如果没有给线程设置名字,线程也有默认名字
- 如果要给线程设置名字,可以用set方法设置,也可以构造方法设置
static Thread currentThread();
- 当JVM虚拟机启动之后,会自动的启动多条线程,其中有一条线程就叫main线程,它的作用就是调用main方法,并执行里面的代码
static void sleep(long time);//以毫秒为单位
- 哪条线程执行到这个方法,你们哪条线程就会在这里停留对应的时间
- 当时间到了,线程会自动醒来,继续执行下面的代码
final void setDaemon(boolena on);
- 当其他的非守护线程执行完毕之后,守护线程会陆续结束
public static void yield();
- 让线程的执行更加均匀
public final void join();
- 插入线程
线程的生命周期
sleep方法会让线程睡眠,时间到了之后,不会立马执行下面的的代码,会进入就绪状态。
同步代码块
synchronized(锁){
操作共享数据的代码
}
- 锁默认打开,有一个线程进去了,锁自动关闭
- 里面的代码全部执行完毕,线程出来,锁自动打开
锁对象一定要是唯一的。
同步方法
修饰符 synchronized 返回值类型 方法名(方法参数){…}
- 同步方法是锁住方法里面所有的代码
- 锁对象不能自己指定
非静态:this
静态:当前类的字节码文件
Ctrl+Alt+M 提取方法
StringBuilder 线程不安全,适用于单线程
StringBuffer 线程安全
锁
实现Lock接口
自定义锁:static Lock lock=new ReentrantLock(Lock的实现类)
lock.lock();//锁关闭
lock.unlock();//锁打开
注意循环语句释放锁的时候,可以加上finally语句,否则会导致某一个线程带着锁,并没有打开,所以其他线程在 lock.lock()语句停止,程序不会停止。
也可以在循环语句外面重复写一句 lock.unlock();
死锁:注意不要让两个锁嵌套使用