有多种分类方式:
综上所述:流的分类为
Java中的IO流都已经写好了,我们程序员不需要关心,我们最主要还是掌握,在 java 中已经提供了哪些流,每个流的特点是什么,每个流对象上的常用方法有哪些。
java中所有的流都是在:java.io.* ; 下。
java中主要还是研究:怎么new流对象,调用流对象的哪个方法是读,哪个方法是写。
java IO流这块有四大家族:(都是抽象类)
所有的流都实现了:
java.io.Closeable接口,都是可关闭的,都有close()方法。
流毕竟是一个管道,这个是内存和硬盘之间的通道,用完之后一定要关闭,不然会耗费(占用)很多资源。养成好习惯,用完流一定要关闭。
所有的输出流都实现了:
java.io.Flushable接口,都是可刷新的,都有flush()方法。养成一个好习惯,输出流在最终输出之后,一定要记得flush()刷新一下。这个刷新表示将通道/管道当中剩余未输出的数据强行输出完(清空管道!)刷新的作用就是清空管道。
注意:如果没有flush()可能会导致丢失数据。
注意:在 java 中只要"类名"以 stream 结尾的都是字节流,以"Reader / Writer"结尾的都是字符流。
java.io包下需要掌握的流有16个:
文件专属:
java.io.FileInputstream
java.io.Fileoutputstream
java.io.FileReader
java.io.Filewriter
转换流:(将字节流转换成字符流)
java.io.InputstreamReader
java.io.OutputstreamWriter
缓冲流专属:
java.io.BufferedReader
java.io.BufferedWriter
java.io.BufferedInputstream
java.io.BufferedOutputstream
数据流专属:
java.io.DataInputstream
java.io.DataOutputstream
标准输出流:
java.io.PrintWriter
java.io.PrintStream
对象专属流:
java.io.ObjectInputstream
java.io.ObjectOutputstream
package com.dljd.io;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
/**
* java.io.FileInputStream :
* 1、文件字节输入流,万能的,任何类型的文件都可以采用这个流来读。
* 2、以字节的方式,完成输入的操作,完成读的操作(从硬盘--->内存)
*/
public class FileInputstreamTest01 {
public static void main(String[] args){
FileInputStream fis = null;
//文件路径是 D:\test.txt ,IDEA会自动把 "\" 编成 "\\",因为Java中"\"表示转义
try {
fis = new FileInputStream("D:\\test.txt");//写成"D:/test.txt"也行
/**
* read()方法自带一个指针,初始时指向首个字节的前方空白处,
* 每次调用 read(),指针向后移一个字节(8个二进制位),当指向末尾字节的后面空白处时,返回-1,
* 表示没有读取到数据
*
* read()这个方法完成的事情就是从数据源中读取8个二进制位,并将这8个 0或1 转换成十进制的整数,然后将其返回。
* 8个二进制位转换为十进制范围是 0~255
*/
int readData = fis.read();//这个方法的返回值是:读取到的"字节"本身。
System.out.println(readData);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
//在finally语句块当中确保流一定关闭。
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
分析上面这个程序的缺点:
一次读取一个字节byte,这样内存和硬盘交互太频繁,基本上时间/资源都耗费在交互上面了。
package com.dljd.io;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
/**
* int read( byte[] b)
* 将读到的字节存到 byte[]数组当中,每次调用都会覆盖 byte[] 重新存一遍
* 一次最多读取 b.Length个字节。
* 减少硬盘和内存的交互,提高程序的执行效率。
*/
public class FileInputstreamTest02 {
public static void main(String[] args) {
FileInputStream fis = null;
try {
/**
* 1.相对路径一定是从当前所在的位置作为起点开始找!
* 2.IDEA默认的当前路径是哪里?工程Project的根就是IDEA的默认当前路径。
* 3.test.txt中的内容为:abcdef
*/
fis = new FileInputStream("test.txt");//相对路径
byte[] bytes = new byte[4];//4个字节
int readCount = fis.read(bytes);//返回读到了多少个字节
System.out.println(readCount);//第一次可以读到4个字节
System.out.println(new String(bytes));//读到abcd放到bytes数组中
readCount = fis.read(bytes);
System.out.println(readCount);//第二次只能读到2个字节
/**
* 重要!
* 1.第二次只能读到 ef ,会放到 bytes数组的前面,
* 覆盖掉 ab,所以此时 bytes={efcd}
* 2.每次覆盖都是从下标0开始,所以可以用 new String(bytes, 0, readCount)
* 打印读到的字节数据,读到多少就打印多少
*/
System.out.println(new String(bytes, 0, readCount));
readCount = fis.read(bytes);
System.out.println(readCount);//第三次读不到字节了,返回-1
System.out.println(new String(bytes));
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
将上面的代码改为训循环:
public class FileInputstreamTest02 {
public static void main(String[] args) {
FileInputStream fis = null;
try {
fis = new FileInputStream("test.txt");//相对路径
byte[] bytes = new byte[4];//4个字节
int readCount = 0;
while ((readCount = fis.read(bytes)) != -1) {
System.out.println(readCount);
System.out.println(new String(bytes, 0, readCount));
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
其他方法:
available()
public class FileInputstreamTest03 {
public static void main(String[] args) throws Exception {
FileInputStream fis = new FileInputStream("test.txt");
//int data = fis.read();//读取一个字节
//System.out.println("剩下多少个字节没有读: " + fis.available());
//这个方法有啥用?直接指定 bytes 长度,读一次就全读完了
byte[] bytes = new byte[fis.available()];//不适合太大的文件,因为bytes数组不能太大
int readCount = fis.read(bytes);
System.out.println(new String(bytes));
}
}
skip()
public class FileInputstreamTest03 {
public static void main(String[] args) throws Exception {
FileInputStream fis = new FileInputStream("test.txt");//abcdef
fis.skip(3);//跳过3个字节不读,此时 read()的指针指向c
int data = fis.read();//100--->d
System.out.println(data);
}
}
package com.dljd.io;
import java.io.FileOutputStream;
/**
* 文件字节输出流,负责写。
* 从内存到硬盘。
*/
public class FileOutputstreamTest01 {
public static void main(String[] args) throws Exception {
//true表示"追加"写入,不会将目标文件先清空再写入
FileOutputStream fos = new FileOutputStream("1.txt", true);
byte[] datas = {97, 98, 99, 100};
//将数组全部写出去
fos.write(datas);//这种方式会将目标文件先清空再写入
//将数组指定位置的数据写出
fos.write(datas, 0, 2);
String s = "鸡你太美!";
fos.write(s.getBytes());//将字符串转为字节数组写出去
//写完之后一定要刷新
fos.flush();
fos.close();
}
}
/**
* 使用 FileInputStream + FileOutputStream完成文件的拷贝。拷贝的过程应该是一边读,一边写。
* 使用以上的字节流拷贝文件的时候,文件类型随意,万能的。什么样的文件都能拷贝。
*/
public class Copy01 {
public static void main(String[] args) throws Exception {
FileInputStream fis = new FileInputStream("1.txt");
FileOutputStream fos = new FileOutputStream("1-copy.txt");
byte[] bytes = new byte[1024];//一次最多拷贝1k
int readCount = 0;
while ((readCount = fis.read(bytes)) != -1) {
fos.write(bytes, 0, readCount);
}
fos.flush();
fos.close();
fis.close();
}
}
/**
* FiLeReader :
* 文件字符输入流,只能读取普通文本。
* 读取文本内容时,比较方便,快捷。
*/
public class FileReaderTest {
public static void main(String[] args) throws Exception {
FileReader read = new FileReader("1.txt");
char[] chars = new char[4];//一次读取4个字符
int readCount = 0;
while ((readCount = read.read(chars)) != -1) {
System.out.println(new String(chars, 0, readCount));
}
read.close();
}
}
/**
* Filewriter :
* 文件字符输出流。写。只能输出普通文本。
*/
public class FileWriterTest {
public static void main(String[] args) throws IOException {
FileWriter writer = new FileWriter("1.txt");
char[] data = {'我', '是', '中', '国', '人'};
writer.write(data, 2, 3);//从下标2开始,写3个字符
writer.write("我是一个码农");
writer.flush();
writer.close();
}
}
能用记事本编辑的都是普通文本文件
public class Copy02 {
public static void main(String[] args) throws Exception {
FileReader reader = new FileReader("test.txt");//.java 也是普通文本
FileWriter writer = new FileWriter("test-copy.txt");
char[] data = new char[4];
int readCount = 0;
while ((readCount = reader.read(data)) != -1) {
writer.write(data, 0, readCount);
}
writer.flush();
writer.close();
reader.close();
}
}
/**
* BufferedReader :
* 带有缓冲区的字符输入流。
* 使用这个流的时候不需要自定义char数组,或者说不需要自定义byte数组。自带缓冲。|
*/
public class BufferedReaderTest01 {
public static void main(String[] args) throws Exception {
FileReader reader = new FileReader("test.txt");
//当一个流的构造方法中需要一个流的时候,这个被传进来的流叫做∶节点流。
//外部负责包装的这个流,叫做:包装流,还有一个名字叫做:处理流。
//像当前这个程序来说:FileReader就是一个节点流。BufferedReader就是包装流/处理流。
BufferedReader br = new BufferedReader(reader);//构造方法只能传字符流对象
//String line = br.readLine();
//System.out.println(line);
String line = null;
//br.readLine()方法读取一个文本行,但不带换行符。
while ((line = br.readLine()) != null) {
System.out.println(line);
}
//对于包装流来说,只需要关闭最外层流就行,里面的节点流会自动关闭。(可以看源代码)
br.close();
}
}
/**
* 字节输入流也想用BufferedReader怎么办?
* 用转换流 InputStreamReader转换为 字符输入流
*/
public class BufferedReaderTest02 {
public static void main(String[] args) throws Exception {
FileInputStream fis = new FileInputStream("1.txt");//字节流
//此时fis是节点流,reader是包装流
InputStreamReader reader = new InputStreamReader(fis);//转换为字符流
//reader是节点流,br是包装流
BufferedReader br = new BufferedReader(reader);
}
}
public class BufferedWriterTest {
public static void main(String[] args) throws IOException {
//带有缓冲区的字符输出流
BufferedWriter bw = new BufferedWriter(new FileWriter("11.txt"));
bw.write("hello,world");
bw.flush();
//关闭最外层
bw.close();
}
}
/**
* java.io.DataOutputStream :数据专属的流。这个流可以将数据连同数据的类型一并写入文件。
* 注意:这个文件不是普通文本文档。(这个文件使用记事本打不开)
*/
public class DataOutputStreamTest {
public static void main(String[] args) throws Exception {
DataOutputStream dos = new DataOutputStream(new FileOutputStream("data"));
//准备数据
byte b = 100;
short s = 20;
int i = 90;
long l = 400L;
float f = 1.0F;
double d = 3.14;
boolean bl = false;
char c = 'a';
//写出数据
dos.writeByte(b);//把数据以及数据的类型一并写入到文件当中。
dos.writeShort(s);
dos.writeInt(i);
dos.writeLong(l);
dos.writeFloat(f);
dos.writeDouble(d);
dos.writeBoolean(bl);
dos.writeChar(c);
dos.flush();
//包装流,关闭最外层即可
dos.close();
}
}
/**
* DataInputStream:数据字节输入流。
* DataOutputStream写的文件,只能使用DataInputStream去读。
* 并且读的时候你需要提前知道写入的顺序。读的顺序需要和写的顺序一致。才可以正常取出数据。
*/
public class DataInputStreamTest {
public static void main(String[] args) throws Exception {
DataInputStream dis = new DataInputStream(new FileInputStream("data"));
byte b = dis.readByte();
short s = dis.readShort();
int i = dis.readInt();
long l = dis.readLong();
float f = dis.readFloat();
double d = dis.readDouble();
boolean bl = dis.readBoolean();
char c = dis.readChar();
System.out.println(b);
System.out.println(s);
System.out.println(i);
System.out.println(l);
System.out.println(f);
System.out.println(d);
System.out.println(bl);
System.out.println(c);
}
}
/**
* java.io.PrintStream :标准的字节输出流。默认输出到控制台。
*/
public class PrintStreamTest {
public static void main(String[] args) throws FileNotFoundException {
System.out.println("hello");
PrintStream ps = System.out;
ps.println("张三");
//标准输出流不需要关闭
//可以改变标准输出流的输出方向吗?可以
//标准输出流不再指向控制台,指向"log"文件。
PrintStream ps2 = new PrintStream(new FileOutputStream("log"));
System.setOut(ps2);
System.out.println("你好张三");//不会再输出到控制台
}
}
File 是文件和目录路径名的抽象表示形式。
/**
* File
* 1、File类和四大家族没有关系,所以File类不能完成文件的读和写。
* 2、File对象代表什么?
* 文件和目录路径名的抽象表示形式。
* C:\Drivers 这是一个File对象
* C:\Drivers\Lan\ReaLtek\Readme.txt也是FiLe对象。一个File对象有可能对应的是目录,也可能是文件。
* File只是一个路径名的抽象表示形式。
* 3、需要掌握ile类中常用的方法
*/
public class FileTest01 {
public static void main(String[] args) throws IOException {
File f1 = new File("D://file");
//判断是否存在
System.out.println(f1.exists());
//如果不存在
if (!f1.exists()) {
//f1.createNewFile();//则以文件的形式创建
f1.mkdir();//则以目录的形式创建
}
File f2 = new File("D://a//b//c");
if (!f2.exists()) {
//以多重目录的形式新建
f2.mkdirs();
}
}
}
public static void main(String[] args) throws IOException {
File f = new File("C:\\Users\\15642\\Desktop\\论文相关\\大论文\\注意.txt");
String parentPath = f.getParent();//获取文件父路径
System.out.println(parentPath);
File parentFile = f.getParentFile();//返回File对象
System.out.println(parentFile.getAbsolutePath());
}
public class FileTest01 {
public static void main(String[] args) throws IOException {
File f = new File("C:\\Users\\15642\\Desktop\\论文相关\\大论文\\注意.txt");
//获取文件名
System.out.println("文件名:" + f.getName());
//判断是否是个目录
System.out.println(f.isDirectory());
//判断是否是个文件
System.out.println(f.isFile());
//获取文件最后一次修改时间
long ms = f.lastModified();//返回毫秒:从1970年到现在的毫秒数
Date time = new Date(ms);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
String strTime = sdf.format(time);
System.out.println("最后修改于:" + strTime);
//获取文件大小
System.out.println("文件大小为:" + f.length() + "字节");
}
}
listFiles()
public class FileTest01 {
public static void main(String[] args) throws IOException {
File f = new File("C:\\Users\\15642\\Desktop\\狂神说笔记");
File[] files = f.listFiles();//获取当前目录下的所有子文件(文件或文件夹)
for (File file : files) {
//System.out.println(file.getAbsolutePath());
System.out.println(file.getName());
}
}
}
package com.dljd.io;
import java.io.*;
/**
* 拷贝目录
*/
public class CopyAll {
public static void main(String[] args) {
//拷贝源,从哪拷贝
File srcFile = new File("E:\\Java项目\\04_乐字节Java项目中信CRM客户管理系统\\crm");
//拷贝目标,拷贝到哪
File destFile = new File("D:\\");
copyDir(srcFile, destFile);
}
private static void copyDir(File srcFile, File destFile) {
if (srcFile.isFile()) {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream(srcFile);
String destPath = destFile.getAbsolutePath();
String path = (destPath.endsWith("\\") ? destPath : destPath + "\\") + srcFile.getAbsolutePath().substring(3);
fos = new FileOutputStream(path);
byte[] bytes = new byte[1024 * 1024];
int readCount = 0;
while ((readCount = fis.read(bytes)) != -1) {
fos.write(bytes, 0, readCount);
}
fos.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return;//如果是文件,就不用递归了
}
//获取源下面的子目录
File[] files = srcFile.listFiles();
for (File file : files) {
//获取所有文件的(包括目录和文件)绝对路径
//System.out.println(file.getAbsolutePath());
if (file.isDirectory()) {
String srcDir = file.getAbsolutePath();
String destPath = destFile.getAbsolutePath();
//三目运算符加个括号,避免出现优先级问题,从下标3开始截,去掉D:\
String destDir = (destPath.endsWith("\\") ? destPath : destPath + "\\") + srcDir.substring(3);
File newFile = new File(destDir);
if (!newFile.exists()) {
newFile.mkdirs();
}
}
//递归
copyDir(file, destFile);
}
}
}
package com.dljd.bean;
import java.io.Serializable;
public class Student implements Serializable {//实现序列化接口才能序列化
private int no;
private String name;
public Student() {
}
public Student(int no, String name) {
this.no = no;
this.name = name;
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"no=" + no +
", name='" + name + '\'' +
'}';
}
}
package com.dljd.io;
import com.dljd.bean.Student;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
/**
* 1.参与序列化和反序列化的对象,必须实现Serializable接口。
* 2.注意:通过源代码发现,Serializable接口只是一个标志接口∶
* public interface Serializable { }
* 这个接口当中什么代码都没有。那么它起到一个什么作用呢?
* 起到标识的作用,java虚拟机看到这个类实现了这个接口,可能会对这个类进行特殊待遇。
* Serializable这个标志接口是给java虚拟机参考的,java虚拟机看到这个接口之后,会为该类自动生成—个序列化版本号。
*/
public class ObjectOutputStreamTest01 {
public static void main(String[] args) throws IOException {
Student s = new Student(1, "张三");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("stu"));
//序列化对象
oos.writeObject(s);//将对象 s 写到 stu 文件
oos.flush();
oos.close();
}
}
package com.dljd.io;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
public class ObjectInputStreamTest {
public static void main(String[] args) throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("stu"));
//开始反序列化
Object s = ois.readObject();
System.out.println(s);
ois.close();
}
}
package com.dljd.io;
import com.dljd.bean.Student;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;
/**
* 一次序列化多个对象呢?
* 可以,可以将对象放到集合当中,序列化集合。
* 提示:
* 参与序列化的ArrayList集合以及集合中的元素都需要实现 java.io.SeriaLizable接口
*/
public class ObjectOutputStreamTest02 {
public static void main(String[] args) throws Exception {
List<Student> stus = new ArrayList<>();
stus.add(new Student(1, "张三"));
stus.add(new Student(2, "李四"));
stus.add(new Student(3, "王五"));
stus.add(new Student(4, "老六"));
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("stu2"));
oos.writeObject(stus);
}
}
package com.dljd.io;
import com.dljd.bean.Student;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.util.List;
public class ObjectInputStreamTest02 {
public static void main(String[] args) throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("stu2"));
List<Student> stus = (List<Student>)ois.readObject();
for (Student student : stus) {
System.out.println(student);
}
ois.close();
}
}
//transient关键字表示游离的,不参与序列化
private transient String name;//name不会被序列化
序列化版本号有什么用?
假设过了很久,Student 这个类源代码改动了。源代码改动之后,需要重新编译,编译之后生成了全新的字节码文件。并且class文件再次运行的时候,java 虚拟机生成的序列化版本号也会发生相应的改变,如果两次版本号不一样,再次序列化就会出错
举个例子:
第一次,你将 Student 对象 stu1 序列化为 文件 “s1”,此时的 Student 类有一个序列化版本号 number1,你可以对 “s1” 进行反序列化。
后来,因为业务发展,你修改了 Student 类的源码,此时的 Student 类有一个序列化版本号 number2,你再对文件 “s1” 进行反序列化就会出错,因为 Student 类的版本号不一样了,使得虚拟机认为修改后的 Student 类不再是 Student 类了 。
java语言中是采用什么机制来区分类的?
第一:首先通过类名进行比对,如果类名不一样,肯定不是同一个类。
第二:如果类名一样,靠序列化版本号进行区分。
小三编写了一个类: com. bjpowernode .java.bean.Student implements Serializable
小四编写了一个类: com.bjpowernode.java.bean.Student implements Serializable
不同的人编写了同一个类,但这两个类确实不是同一个类。这个时候序列化版本就起上作用了。
对于java虚拟机来说,java虚拟机是可以区分开这两个类的,因为这两个类都实现了serializable接口,都有默认的序列化版本号,他们的序列化版本号不一样。所以区分开了。(这是自动生成序列化版本号的好处)
这种自动生成的序列化版本号缺点是:一旦代码确定之后,不能进行后续的修改,因为只要修改,必然会重新编译,此时会生成全新的序列化版本号,这个时候java虚拟机会认为这是一个全新的类。(这样就不好了!)
最终结论∶
凡是一个类实现了Serializable接口,建议给该类提供一个固定不变的序列化版本号。这样,以后这个类即使代码修改了,但是版本号不变,java虚拟机会认为是同一个类。
public class Student implements Serializable {
private int no;
//transient关键字表示游离的,不参与序列化
private transient String name;
private int age;
//建议将序列化版本号手动的写出来。不建议自动生成
private static final long serialVersionUID = 8683452581122892189L;
...
}
IO流:文件的读和写。
Properties:是一个 Map 集合,key 和 value 都是 String 类型
准备一个 userinfo.properties 文件
# 在.properties文件中,#号表示注释,=两边最好不要有空格
username=admin
password=123
# "="可以换成":",不建议使用":"
age:13
package com.dljd.io;
import java.io.FileReader;
import java.util.Properties;
/**
* 非常好的一个设计理念:
* 以后经常改变的数据,可以单独写到一个文件中,使用程序动态读取。将来只需要修改这个文件的内容,
* java代码不需要改动,不需要重新编译,服务器也不需要重启。就可以拿到动态的信息。
*
* 类似于以上机制的这种文件被称为配置文件。并且当配置文件中的内容格式是:
* key1=value
* key2=value
* 的时候,我们把这种配置文件叫做属性配置文件。
* java规范中有要求:属性配置文件建议以.properties结尾,但这不是必须的。
* 这种以.properties结尾的文件在java中被称为:属性配置文件。
* 其中Properties是专门存放属性配置文件内容的一个类。
*/
public class IoPropertiesTest01 {
public static void main(String[] args) throws Exception {
//将userinfo文件中的数据加载到Properties对象当中。
FileReader reader = new FileReader("userinfo.properties");
Properties pro = new Properties();
//调用Properties对象的Load方法将文件中的数据加载到Nap集合中。
pro.load(reader);//文件中的数据顺着管道加载到Map集合中,其中等号左边做key,右边做value
String username = pro.getProperty("username");//传入key,返回value
System.out.println(username);
String pwd = pro.getProperty("password");
System.out.println(pwd);
String age = pro.getProperty("age");
System.out.println(age);
}
}