一、字节流
一般处理二进制文件
1、输入流
InputStream(抽象类)
包含子类 |
---|
FileInputStream |
FilterInputStream(实用子类BufferedInputStream) |
ObjectInputStream |
PipedInputStream |
ByteArrayInputStream |
FilterInputStream |
DataInputStream |
BufferedInputStream |
2、输出流
子类几乎和字节输入流一样
OutputStream(抽象类)
包含子类(InputStream没有的) |
---|
PrintStream |
二、字符流
一般处理文本文件和中文。
1、输入流
Reader(抽象类)
包含子类 |
---|
FileReader |
BufferedReader |
PipedReader |
StringReader |
CharArrayReader |
FilterReader |
InputStreamReader(输出流没有) |
BufferedReader |
2、输出流
Writer(抽象类)
子类几乎和字符输入流一样
包含子类(Reader没有的) |
---|
PrintWriter |
OutputStreamWriter |
三、节点流
(针对数据源操作)
1、文件流
字节流
FileInputStream
FileOutputStream
读写文件的常用方法:
- 输入流read()方法:读取流中字节,返回值为-1表示到达文件末尾。参数可以为字节数组,表示读取到指定字节数组中,返回读取字节数。
- 输出流write()方法:按字节写入流,无返回值。
- close()方法:关闭流,释放资源。
构造器
有参构造,参数为文件路径,如:
FileInputStream fis = new FileInputStream("D://test1.txt");
FileOutputStream fos = new FileOutputStream("D://test2.txt");
第二个参数默认为false(覆盖模式),可手动改为true(追加模式),在追加模式下,流的指针在文件末尾。
调用构造器会产生FileNotFoundException
的异常,read()、write()、close()会有IOException
的异常,但是后者包含前者,所以仅抛出后者即可。
读写示例:
//D盘下有个叫test1.txt的文件,读取并输出
FileInputStream fis = new FileInputStream("D://test1.txt");
byte[] fileContents = new byte[1024];
fis.read(fileContents);
String str = new String(fileContents);
System.out.println(str);
fis.close();
//修改text2.txt的内容
FileOutputStream fos = new FileOutputStream("D://test2.txt");
String source = "FileOutputStream";
byte[] writeContents = source.getBytes();
fos.write(writeContents);
fos.close();
字符流
FileReader
FileWriter
和字节流类似,读取的是字符,常用于读取中文。参数为byte[]字节数组的变为char[]
字符数组。
- FileWriter使用后必须调用close()或者flush()方法,否则写入不到指定文件。
读写示例:
//读取文件并输出
FileReader fr = new FileReader("D://test1.txt");
char[] chars = new char[1024];
fr.read(chars);
System.out.println(chars);
fr.close();
//写入文件
FileWriter fw = new FileWriter("D://test2.txt");
String str = "FileWriter";
char[] ch = str.toCharArray();
fw.write(ch);
//fw.flush();
fw.close();
2、数组流
包括字节数组流和字符数组流,这两个里面还分输入和输出流。
示例:
//字符流
//输入流
String text1 = "123";
byte[] bytes = text1.getBytes();//字符串文本转字节数组
byte[] b = new byte[1024];//存放读取的字节数组
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
bais.read(b);
String str = new String(b);//字节数组转字符串,方便打印
System.out.println(str);
bais.close();
//输出流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
String text2 = "abc";
bytes = text2.getBytes();//bytes中的内容重新赋值为
baos.write(bytes);//写入baos到对象
System.out.println(baos);
baos.close();
//字节流
//输入流
char[] chars = text1.toCharArray();//字符串文本转字符数组
CharArrayReader car = new CharArrayReader(chars);
char[] c = new char[1024];//存放读取的字符数组
car.read(c);
str = new String(c);//字符数组转字符串,方便打印
System.out.println(str);
car.close();
//输出流
CharArrayWriter caw = new CharArrayWriter();
chars = text2.toCharArray();//字符数组重新赋值
caw.write(chars);//写入到caw对象
System.out.println(caw);
caw.close();
3、管道流
- 用于多线程,单线程可能会出现死锁
- 每一个管道输入流都对应一个输出流
以字节流为例,字符流类似:
定义发送数据的类
class SenderThread extends Thread{
private PipedOutputStream pos;
public SenderThread(PipedOutputStream pos) {
super();
this.pos = pos;
}
@Override
public void run() {
byte[] receiveBytes = "这是来自SenderThread的数据".getBytes();
try {
pos.write(receiveBytes);//管道输出流直接把数据写到连接的管道输入流的缓冲区
pos.flush();
pos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
定义接收数据的类
class ReceiveThread extends Thread{
private PipedInputStream pis;
public ReceiveThread(PipedInputStream pis) {
super();
this.pis = pis;
}
@Override
public void run() {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] receiveBytes = new byte[1024];
int len = 0;
while ((len = pis.read(receiveBytes))!=-1){
baos.write(receiveBytes);//把receiveBytes的数据写入到baos里
}
baos.flush();
System.out.println(baos.toString());
baos.close();
pis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
写一下main()方法
PipedInputStream pipedI = new PipedInputStream();
PipedOutputStream pipedO = new PipedOutputStream();
pipedI.connect(pipedO);
SenderThread senderThread = new SenderThread(pipedO);
ReceiveThread receiveThread = new ReceiveThread(pipedI);
//用线程池让两个线程同步
//ExecutorService service = Executors.newFixedThreadPool(2);//线程池
//service.execute(senderThread);
//service.execute(receiveThread);
//service.shutdown();
senderThread.start();
receiveThread.start();
//pipedI.close();
//pipedO.close();
//service.shutdown();
这里是将管道流的close()方法放在了线程中,以实现两个线程的数据传输。也可以用注释掉的方法,用线程池实现两个线程的同时运行,这样close()方法就可以写在main()方法里了。
4、字符串流
只有字符流,没有字节流。
输入流把字符串读成字符数组,输出流把字符数组写入对象的内存。
//输入流
String text1 = "aaa";
StringReader sr = new StringReader(text1);
char[] buffer = new char[1024];//定义读到的字符数组
sr.read(buffer);//字符串流把字符串读成字符数组
String str = new String(buffer);//读到的数据转成字符串便于打印
System.out.println(str);
//输出流
String text2 = "bbb";
char[] chars = text2.toCharArray();
StringWriter sw = new StringWriter();
sw.write(chars);//把chars的内容写进sw对象
System.out.println(sw);
四、处理流
包装了节点流。节点流直接跟数据源相接,而处理流可以接收多种节点流类型,消除不同节点流的实现差异。
1、缓冲流
以BufferedReader/BufferedWriter为例,对应的字节流BufferedInputStream/BufferedOutputStream类似。
//读
char[] chars = "abc".toCharArray();
BufferedReader br = new BufferedReader(new CharArrayReader(chars));//参数类型为Reader,即可以在里面封装一个节点流
char[] buffer = new char[1024];
br.read(buffer);//把数据读进buffer字符数组
String str = new String(buffer);//转成字符串以便输出查看
System.out.println(str);
br.close();
//写
BufferedWriter bw = new BufferedWriter(new FileWriter("D://DemoBufferedWriter.txt"));//打开D盘文件
bw.write("Test BufferedWriter Class!".toCharArray());//将字符串写入
bw.close();
D盘文件内容如下:
2、对象流
ObjectInputStream/ObjectOutputStream
需求分析:在保存一个值的时候,还希望保存它的数据类型。
举个例子,保存100在文本文档里,你不能确定它到底是int 100还是String “100”。
再举个例子,你有一个People对象,包含参数 “张三”,18 ,分别是这个对象的name和age属性对应的值,在保存后,下次要用还得还原成一个对象,而不是一堆数据。
序列化和反序列化
序列化:在保存数据时,保存数据的类型和值。
反序列化:在恢复数据时,恢复数据的类型和值。
要使类可序列化,必须实现接口Serializable
或Externalizable
Serializable是一个标记接口,没有任何方法。常用这个接口。
- 序列化可继承
- 序列化的对象,其属性也必须是序列化类型的
- 序列化类中的属性默认序列化,除了
static
和transient
修饰的属性
对象流的示例:
//对象输出流
Man man = new Man("张三",20);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\obj_os.data"));
oos.writeUnshared(man);//writeUnshared是用来写对象的,同样的write用来写Integer和Byte[]······
oos.write(100);
oos.writeBoolean(true);
oos.writeChar('a');
oos.writeDouble(3.141592653589793);
oos.writeUTF("abc");//写String
oos.close();
//对象输入流
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\obj_os.data"));
//读取顺序要和写的顺序一样,不一样的话会产生格式异常
System.out.println(ois.readUnshared());//com.IOStream.Man@5fd0d5ae
System.out.println(ois.read());//100
System.out.println(ois.readBoolean());//true
System.out.println(ois.readChar());//a
System.out.println(ois.readDouble());//3.141592653589793
System.out.println(ois.readUTF());//abc
ois.close();
Man类:
class Man implements Serializable {
private String name;
private int age;
public Man(String name, int age) {
this.name = name;
this.age = age;
}
}
D:\obj_os.data 文件截图:
用文本文件打开:
3、转换流
InputStreamReader/OutputStreamWriter
InputStreamReader isr = new InputStreamReader(new ByteArrayInputStream("Text".getBytes()));
OutputStreamWriter osw = new OutputStreamWriter(new ByteArrayOutputStream());
System.out.println(isr.getClass().getSuperclass());//class java.io.Reader
System.out.println(osw.getClass().getSuperclass());//class java.io.Writer
将一个字节流对象转成字符流
4、打印流
PrintStream和PrintWriter 只含输出流,不含输入流。
以PrintStream为例:
PrintStream console = System.out;//事先声明变量console为标准输出流
PrintStream ps = new PrintStream("D:\\PrintStreamTest.txt");//构造器参数可以为文件路径
ps.write("print something to file.".getBytes());
System.setOut(ps);//设置输出的属性,这里将标准输出指向了ps输出流,而不是默认的显示器
System.out.println("\nThe print path have been changed.");
ps.close();
PrintStream ps2 = console;
System.setOut(console);//将标准输出的路径改回到控制台
ps2.write("Print content using write method.\n".getBytes());//print方法的底层是write方法,所以不是按行输出
ps2.println("Print content using normal output.");
ps2.close();
输出流可以修改标准输出的路径,但是改之前得先声明变量保留默认的标准输出流,否则修改之后默认的被回收就改不回来了。(也有可能有其他改回来的方法,但是我不会)
然后看一下D盘的那个修改标准输出路径生产的文件:
PrintWriter的构造器有略微差别,有参构造需传入一个OutputStream对象,也就是说不可以直接赋值为标准输出流System.out了。用法如下:
PrintWriter pw = new PrintWriter(System.out);
pw.close();
五、标准输入输出
System.in
默认设备是键盘
它的运行类型是BufferedInputStream,是一个缓冲处理流
所以Scanner对象读取的是键盘输入的内容
System.out
默认设备是显示器
所以System.out.println()方法是将内容按行输出到显示器