流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。 I : Input输入 O : output输出
按处理数据类型不同,分为字节流和字符流
按数据流向的不同,分为输入流和输出流。(入和出是相对于内存来讲的)
按功能不同,分为节点流(直接操作数据源)和处理流(对其它流进行处理)
字节流
输入流:InputStream
输出流:OutputStream
字符流
输入流:Reader
输出流:Writer
以字节的方式进行文件中的内容的读取操作,要想读取文件需要先找到文件的位置,文件位置可以通过绝对路径或相对路径找到。
绝对路径 : 绝对路径是从系统的根目录开始,一级一级指向目标文件或资源的完整路径.
例如:D:/eclipse/workspace/day19_0120/src/day19_12/Cow.java*
相对路径 : 相对路径是以当前文件或目录为基准,指向目标文件或资源的简化路径.
例如:../day19_0120/src/day19_12/Cow.java
注:./ 为当前目录, ../ 为上级目录, ../../ 为上上级目录,依次类推,在eclipse中./定位到当前目录。
read()方法: 从输入流中读取一个字节,返回一个int值,如果到达流的末尾,返回-1。
read(byte[] b)方法: 从输入流中读取一定数量的字节,并将其存储在字节数组b中,返回实际读取的字节数,如果到达流的末尾,返回-1。
read(byte[] b, int off, int len)方法:从输入流中读取最多len个字节,并将其存储在字节数组b中,从偏移量off开始,返回实际读取的字节数,如果到达流的末尾,返回-1。
available()方法:返回从输入流中可以读取的字节数目。
skip(long n)方法:跳过并丢弃输入流中的n个字节。
close()方法:关闭输入流,释放与之关联的资源。
问:为什么read方法返回的是int类型,而不是byte类型?
因为字节流可以操作任意类型的文件,这些文件在底层都是以二进制存储的,一个字节是八位二进制,也就是说我如果返回类型为byte类型,是有可能在读取的过程中出现连续的八个1,而11111111是byte类型的-1,我们的程序在读到-1时,就不会再往下执行,导致文件没有读完。所以再读取的时候采用int类型的接受,每次读取一个字节都会补0凑足32位,一旦遇到11111111就会在前面补24个0,那么byte类型的-1就变成了int类型的255,就可以保证整个数据读完,而结尾的-1就是int类型的-1,用来判断流的结尾。
public static void main(String[] args) {
FileInputStream file = null;
try {
// 打开对应文件数据
file = new FileInputStream("../day19_0120/src/day19_12/Cow.java");
// read:读取文件中的一个字节,返回当前字节对应的ASCII码值(int),并指向下一个字节
// 到达文件末尾没有数据返回-1
System.out.println((char)file.read());
System.out.println((char)file.read());
System.out.println((char)file.read());
System.out.println((char)file.read());
System.out.println((char)file.read());
System.out.println((char)file.read());
} catch (Exception e) {
e.printStackTrace();
}finally {
// 关闭资源
if(file != null) {
try {
file.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
// 从java7开始,支持自动关闭资源
try(FileInputStream file = new FileInputStream(
"../day19_0120/src/day19_12/Cow.java")){
int temp = 0;
while((temp = file.read()) != -1) {
System.out.print((char)file.read());
}
}catch(Exception e) {
e.printStackTrace();
}
}
// Read 重载使用
public class IO_FileInputStream_02 {
public static void main(String[] args) {
// 从java7开始,支持自动关闭资源
try (FileInputStream file = new FileInputStream("../day19_0120/src/day19_12/Cow.java")) {
// read方法重载,可以传入byte数组,用来提高读取效率,一次读取一个数组
// 返回当前次,读取的个数(这一次读取了几个字节),到达文件末尾返回-1
// avaliable():获取和读取的字节数
System.out.println(file.available());
// byte[] bytes = new byte[file.available()];
byte[] bytes = new byte[1024];
int temp = 0;
while ((temp = file.read(bytes)) != -1) {
System.out.print(new String(bytes,0,temp));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
字符输入流一般适用于纯文本文件,用来解决读取文字乱码问题,像压缩包,视频,图片等都使用字节流而不是字符流。
read():一次读取一个字符,返回对应的ASCII码,到达文件末尾返回-1
read(char[]):一次读取一个字符数组,返回本次读取的个数
public class IO_FileInputStream_03 {
public static void main(String[] args) {
try (FileReader file = new FileReader("../day19_0120/src/day19_12/Cow.java")) {
// 创建字符数组
char[] chars = new char[1024];
int temp = 0;
// 传入字符数组读取
while ((temp = file.read(chars)) != -1) {
System.out.print(new String(chars,0,temp));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
字节输出流,用于将文件中的数据写出到硬盘里, 如果文件不存在就会自动创建,但是不会创建目录,如果目录不存在就会报错。构造方法可以传入两个参数,一个是文件所在的目录,一个是写出方式(true 表示追加写出,false表示覆盖写出)。如果构造方法只传入文件路径,默认是覆盖写出,覆盖写出会在创建输出流对象的时候,把该文件里的内容清空。
write(int b)方法:将指定的字节写入此文件输出流。
write(byte[] b)方法:将指定字节数组中的所有字节写入此文件输出流。
write(byte[] b, int off, int len)方法:将指定字节数组中从偏移量off开始的len个字节写入此文件输出流。
close()方法:关闭此文件输出流并释放与此流关联的所有系统资源。
flush()方法:刷新此文件输出流并强制将所有缓冲的输出字节写入目标。
public static void main(String[] args) {
try ( // true 追加写出
// FileOutputStream fos = new FileOutputStream("./src/a.txt",true);
// false 覆盖写出
FileOutputStream fos1 = new FileOutputStream("./src/a.txt",false);
){
byte[] bytes = {97,98,99,100,101,102};
// 写出字符数组中所有内容
fos1.write(bytes);
// 写出字符数组中0~3的内容
fos1.write(bytes, 0, 3);
// 写出int类型,数字会显示为对应的字符
fos1.write(97);
// 字符串不能直接写出,需要转换为字节数组
fos1.write("你好。".getBytes());
} catch (Exception e) {
e.printStackTrace();
}
}
1.4.4 FileWriter
public static void main(String[] args) {
try (FileWriter fw = new FileWriter("./src/a.txt")){
// 写出int
fw.write(65);
// 写出字符数组
char[] c = {65,66,67,68,69};
fw.write(c);
fw.write(c,0,3);
// 写出字符串
fw.write("你好");
// 写出字符串中特定个数的字符
fw.write("你好吗",0,2);
// 刷缓存
fw.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
字节流:
输入流:BufferedInputStream
输出流:BufferedOutputStrea
字符流:
输入流:BufferedReader
输出流:BufferedWriter
缓冲流的特点:
1.主要是为了提高效率二存在的,减少物理读取次数
2.提供readLine(),newLine()这样的便捷方法(针对字符缓冲流)
3.在读取和写入时,会有缓存部分,调用fluse为刷新缓存,将数据写入硬盘
// BufferedReader
// 节点流的构造方法传入的是文件路径
// 处理流的构造方法传入的是节点流对象
// 只要处理流关闭,节点流也会关闭
public class IO_BufferedReader_00 {
public static void main(String[] args) {
// 创建字符输入流
try(FileReader fr = new FileReader("./src/a.txt");
// 创建字符输入缓冲流,传入字符流
BufferedReader br = new BufferedReader(fr);
) {
// readLine()读取一行字符串,到达文件末尾返回null
String temp = null;
while ((temp = br.readLine()) != null) {
System.out.println(temp);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
//BufferedWriter
public class IO_BufferedWriter_00 {
public static void main(String[] args) {
try ( // 创建字符输出流
FileWriter fw = new FileWriter("./src/a.txt");
// 创建字节输出缓冲流,传入字符输出流
BufferedWriter bw = new BufferedWriter(fw);
){
// 输出字符串
bw.write("字符输出缓冲流");
// 输出整数,会显示为对应的字符
bw.write(99);
// 输出字符数组
char[] chars = {48,49,50,51};
bw.write(chars);
// 新增换行方法
bw.newLine();
bw.write("换行");
} catch (IOException e) {
e.printStackTrace();
}
}
}
输入流:InputStreamReader
输出流:OutputStreamWriter
转换流特点:
1. 转换流是指将字节流像字符流的转换,主要有InputStreamReader和OutputStreamWriter
2. InputStreamReader:将字节输入流转换为字符输入流
3. OutputStreamWriter:将字节输出流转换为字符输出流
//InputStreamReader
public class IO_InputStreamReader {
public static void main(String[] args) {
try ( // 字节输入流
FileInputStream fis = new FileInputStream("./src/a.txt");
// 转换为字符流
InputStreamReader isr = new InputStreamReader(fis);
// 字符输入缓冲流
BufferedReader br = new BufferedReader(isr);
){
System.out.println(br.readLine());
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 解决乱码问题
// 如果文件编码方式与开发环境使用的编码方式不同,读取时会出现乱码问题
// 在不改变开发环境使用的编码方式和文件原来的编码方式的情况下,可以将字节流转换为字符流时指定与文件对应的编码格式
public class IO_InputStreamReader_00 {
public static void main(String[] args) {
try ( // 字节输入流,文件a.txt使用的时GBK编码,开发环境使用的时UTF-8编码
FileInputStream fis = new FileInputStream("D:/a.txt");
// 转换为字符流,指定编码格式为GBK,防止出现乱码
InputStreamReader isr = new InputStreamReader(fis,"GBK");
// 字符输入缓冲流
BufferedReader br = new BufferedReader(isr);) {
System.out.println(br.readLine());
} catch (Exception e) {
e.printStackTrace();
}
}
}
特点:
1.打印流是输出最方便的类,包含字节打印流PrintStream和字符打印流PrintWriter
2.PrintStream是OutputStream的子类,把一个输出流的实例传递到打印流之后,可以更加方便的,输出内容,相当于重新包装了一边输出流
3.PrintStream类的print()方法被重载很多次,print(int i),print(boolean i)等。
标准输入输出:
java的标准输入/输出分别通过System.in和System.out来代表,在默认情况下分别代表键盘和显示器,当程序通过System.in获取输入时,实际上是通过键盘获取输入, 当程序通过System.out获取输出时,实际上是通过显示器获取输出。
在System类中提供三个重定向标准输入/输出的方法
1.static void setErr(PrintStream err) 重定向标准输出错误流
2.static void setIn(InputStream in) 重定向标准输入流
3.static void setOut(PrintStream out) 重定向标准输出流
public static void main(String[] args) {
try(FileOutputStream fos = new FileOutputStream("./src/a.txt");
PrintStream ps = new PrintStream(fos);
) {
// 打印流中提供了很多print方法,可以更加方便的打印各种类型
ps.print(false);
ps.print(48);
ps.print("啦啦啦");
ps.print(32.34);
// 平常用的打印语句就是这个打印流,只不过System中有三个标准流
// in 标准输入(控制台) out 标准输出(控制台) err 标准错误打印
System.out.println(123);
// 使用重定向方法,可以指定System中打印流的输出对象
System.setOut(ps); // 指定输出到./src/a.txt
System.out.println(12345);
System.out.println("你好");
} catch (Exception e) {
e.printStackTrace();
}
}
数据流: 为了方便操作java中的基本数据类型和字符串类型数据,可以使用数据流来进行操作,数据流以二进制形式进行数据传递,并且读写顺序和类型必须一致,常用来网络数据传输。
// 写入
public class IO_DataOutputStream {
public static void main(String[] args) {
try (
FileOutputStream fos = new FileOutputStream("./src/test");
DataOutputStream dos = new DataOutputStream(fos);
){
// 可写入多种类型
dos.writeInt(12);
dos.writeByte(2);
dos.writeLong(123456);
dos.writeShort(122);
dos.writeUTF("写入");
dos.writeDouble(12.4);
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 读取
public class IO_DataInputStream {
public static void main(String[] args) {
try(
FileInputStream fis = new FileInputStream("./src/test");
DataInputStream dis = new DataInputStream(fis);
) {
// 读取顺序需和写入的顺序和类型一致
System.out.println(dis.readInt());
System.out.println(dis.readByte());
System.out.println(dis.readLong());
System.out.println(dis.readShort());
System.out.println(dis.readUTF());
System.out.println(dis.readBoolean());
} catch (Exception e) {
e.printStackTrace();
}
}
}
创建对象方式:1. new 2.反射机制 3.clone 4.反序列化
序列化: 把堆内存对象进行持久化,保存到硬盘里
反序列化: 把硬盘中的对象,载入到内存中
要序列化的类必须实现Serializable接口,否则会报错
// 要序列化的实体类
public class User implements Serializable{
private String name;
// transient 修饰符,修饰的属性不能被序列化
transient int age;
// serialVersionUID 简单来说就是class的版本控制
// 每次修改class时,UID都会自动进行修改,导致反序列化出的对象不能使用修改后的变量或方法,
// 所以一般我们会手动设置一个固定的UID值
private static final long serialVersionUID = 1L;
@Override
public String toString() {
return "User [name=" + name + ", age=" + age + "]";
}
public User() {
super();
}
public User(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public class ObjectOutputStream_00 {
public static void main(String[] args) {
User user = new User("张三",19);
try (
FileOutputStream fos = new FileOutputStream("./src/day21_0123/temp");
ObjectOutputStream oos = new ObjectOutputStream(fos);
){
// 将对象写入
oos.writeObject(user);
// 刷新缓存
oos.flush();
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 反序列化
public class ObjectInputStream_00 {
public static void main(String[] args) {
try(
FileInputStream fis = new FileInputStream("./src/day21_0123/temp");
ObjectInputStream ois = new ObjectInputStream(fis);
) {
User user = (User)ois.readObject();
System.out.println(user);
} catch (Exception e) {
e.printStackTrace();
}
}
}
// serialVersionUID 简单来说就是class的版本控制
// 每次修改class时,UID都会自动进行修改,导致反序列化出的对象不能使用修改后的变量或方法,
// 所以一般我们会手动设置一个固定的UID值
private static final long serialVersionUID = 1L;
// transient 修饰符,修饰的属性不能被序列化
transient int age;
File类提供了文件和目录相关的操作(增删改查)
File对象需要传入文件路径的,但是路径可以是不存在的
public File(String pathname)以pathname为路径创建Fiel对象,可以是绝对路径或相对路径, 如果pathname是相对路径,则默认的当前路径在系统属性user.dir中存储。
public File(String parent, String child)以parent为父路径,child为子路径创建File对象。
public File(File parent, String child) 根据一个父对象和子路径创建File对象。
获取功能:
public String getAbsolutePath(): 获取绝对路径
public String getPath(): 获取路径
public String getName(): 获取名称
public String getParent(): 获取上级文件路径,若无,返回null
public long length() : 获取文件长度(即:字节数),不能获取目录的长度
public long lastModified() : 获取最后一次的修改时间
public String[] list() : 获取指定目录下所有文件或者文件目录的名称数组
public File[] listFiles() :获取指定目录下的所有文件或者文件目录的File数组
重命名功能:
public boolean renameTo(File dest):把文件重命名为指定的文件路径
判断功能:
public boolean isDirectory():判断是否是文件目录
public boolean isFile() :判断是否是文件
public boolean exists() :判断是否存在
public boolean canRead() :判断是否可读
public boolean canWrite() :判断是否可写
public boolean isHidden() :判断是否隐藏
创建删除功能:
public boolean createNewFile() :创建文件。若文件存在,则不创建,返回false
public boolean mkdir() :创建文件目录。如果此文件目录存在,就不创建了。 如果此文件目录的上层目录不存在,也不创建。
public boolean mkdirs() :创建文件目录。如果上层文件目录不存在,一并创建
注意事项:如果你创建文件或者文件目录没有写盘符路径,那么,默认在项目 路径下。
public boolean delete():删除文件或者文件夹
删除注意事项:
Java中的删除不走回收站。
要删除一个文件目录,请注意该文件目录内不能包含文件或者文件目录
public static void main(String[] args) throws Exception {
// 创建文件对象
File file = new File("E:/com/test");
// 获取全路径
System.out.println(file.getAbsolutePath());
// 获取文件名
System.out.println(file.getName());
// 根据不同操作系统,调整分隔符,windows系统是\ Linux是/
System.out.println(File.separator);
// 获取父路径,返回值为字符串
System.out.println(file.getParent());
// 获取父路径文件对象
File supFile = file.getParentFile();
// 判断是否为文件
System.out.println(file.isFile());
// 判断是否为目录
System.out.println(file.isDirectory());
// 获取最后一次修改时间,返回毫秒数
System.out.println(file.lastModified());
// 判断是否存在
System.out.println(file.exists());
// 创建对应文件,但是不会创建文件夹
// 如果该文件已经存在,就不在创建,返回false,否则返回true
System.out.println(file.createNewFile());
// 删除,删除成功返回true
file.delete();
// 执行结束后,JVM退出时,删除
file.deleteOnExit();
// 创建文件夹
file = new File("E:/com/test/a/b/c/test.txt");
supFile = file.getParentFile();
if(!supFile.exists()) {
// 创建文件夹路径,只能创建c,如果a,b不存在会报错
supFile.mkdir();
// 递归创建所有文件夹
supFile.mkdirs();
}
file.createNewFile();
// 获取所有文件对象
File[] files = file.listFiles();
for (File file2 : files) {
System.out.println(file2.getAbsolutePath());
}
}
文件夹复制
1 文件复制,本质就是输入和输出 , 先完成单个文件复制的方法
2 获取该目录下所有子文件
3 接收文件对象
4 判断该文件对象是否是文件,如果是,调用单个文件复制方法,终止
5 如果是目录,则获取该目录下所有子文件,然后遍历所有子文件,依次递归传入该方法
6 对所有文件对象进行以上相同操作
public class IO_Copy {
public static void main(String[] args) {
checkMenu(new File("D:/com/java"));
System.err.println("复制成功");
}
public static void checkMenu(File file) {
if(file.isFile()) {
// 判断文件对象是否时文件,如果是就进行复制
// 起始路径和目标路径不能相同,否则会导致文件清空
// 获取文件路径
String filePath = file.getAbsolutePath();
// 目标路径
String newFilePath = "F" + filePath.substring(1);
// 获取目标路径的父目录对象
File supFile = new File(newFilePath).getParentFile();
// 判断目标目录是否存在
if(!supFile.exists()) {
supFile.mkdirs();
}
// 复制
copyFile(filePath,newFilePath);
return;
}
// 到这里说明是目录
// 获取所有子文件并递归调用
File[] files = file.listFiles();
for (File file2 : files) {
checkMenu(file2);
}
}
// 文件复制方法
public static void copyFile(String srcPath,String destPath) {
try( FileInputStream input = new FileInputStream(srcPath);
BufferedInputStream in = new BufferedInputStream(input);
FileOutputStream output = new FileOutputStream(destPath);
BufferedOutputStream out = new BufferedOutputStream(output);
) {
byte[] bytes = new byte[10240];
int temp = 0;
while((temp = in.read(bytes)) != -1) {
out.write(bytes, 0, temp);
}
out.flush();
} catch (Exception e) {
e.printStackTrace();
}
}
}