流的分类
按数据流的流向不同分为:
从程序的角度来定义输入与输出的。
输入流
读取外部数据(磁盘、光盘等存储设备的数据) 到 程序(内存中)
输出流
程序(内存)中数据 输出到磁盘、光盘等存储设备中
按照操作数据单位不同分为:
字节流(8 bit)
优势操作二进制文件。
字符流(按字符)
不同编码方式下,一个字符对应的字节数可能不同。
优势在操作文本文件。
(抽象基类) | 字节流 | 字符流 |
---|---|---|
输入流 | InputStream | Reader |
输出流 | OutputStream | Writer |
按流的角色不同分为:
节点流
对特定的数据源进行操作的流,叫做节点流。
比如FileInputStream、FileReader,操作某个路径确定的文件。
处理流/包装流
例如BufferedReader,操作其他的流,可以操作其他继承Reader的流。
1.采用装饰器模式,通过对其他流进行包装,消除了操作其他流时的底层差异。
2.关闭时关闭外层流就可以。
下面举例说明:
创建FileReader_类模拟FileReader,仅示意。
public class FileReader_ extends Reader_{
public void readFile(){
System.out.println("读文件.......");
}
}
创建StringReader_类模拟StringReader,仅示意。
public class StringReader_ extends Reader_{
public void readString(){
System.out.println("读字符串.......");
}
}
创建Reader_类,
public abstract class Reader_ {
/**
* 抽象类
*/
public void readFile(){}
public void readString(){}
}
创建BufferReader_类,在BufferReader_中,不仅实现Reader_类中的所有方法,还创建多个其他的方法以加强Reader_的功能。
public class BufferReader_ extends Reader_{
private Reader_ reader_;
public BufferReader_(Reader_ reader_) {
this.reader_ = reader_;
}
public void readFile(){
reader_.readFile();
}
public void readFiles(){
for (int i = 0; i < 3; i++) {
reader_.readFile();
}
}
public void readString(){
reader_.readString();
}
}
创建Test_类:
在Test_中,创建FileReader_以及BufferReader_的对象,可见,BufferReader_生成的对象可以实现所有FileReader_对象的功能,并且对其有扩充。
public class Test_ {
@Test
public void Test1(){
FileReader_ fileReader_ = new FileReader_();
BufferReader_ bufferReader_ = new BufferReader_(fileReader_);
bufferReader_.readFile();
bufferReader_.readFiles();
}
}
result
读文件.......
读文件.......
读文件.......
读文件.......
字节流
字节输入流 InputStream
字节输入流常见的子类有:
文件输入流 FileInputStream
继承关系图:
测试的hello.txt中内容为
12346579欢乐斗地主
因为fileInputStream.read()读取的内容长度为一个字节,为对应的ascii码。所以sout时将其转化为char。并且使用int来承接
@Test
public void readFile01(){
int readData = 0;
String filePath = "D:\\hello.txt";
fileInputStream = null;
try {
fileInputStream = new FileInputStream(filePath);
while ((readData = fileInputStream.read())!= -1){
System.out.println((char) readData);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
fileInputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
如果使用byte[8]来读取文件
@Test
public void readFile02(){
int readDataLength = 0;
// 使用8字节的容器读取每次的数据
byte[] buf = new byte[8];
String filePath = "D:\\hello.txt";
fileInputStream = null;
try {
fileInputStream = new FileInputStream(filePath);
while ((readDataLength = fileInputStream.read(buf))!= -1){
/**
* 每次最多读取8字节,当读取8字节时,readDataLength=8
* 最后一次读取数据不足8字节时,则readDataLength等于剩余数据的字节长度
* 然后再次读取时,readDataLength=-1 对应退出程序
* 由于utf-8形式,一个汉字等于3个字节,所以最后的汉字部分会是乱码,因为认为汉字为三个字符了
*/
System.out.println(new String(buf,0,readDataLength));
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
fileInputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
缓冲字节输入流 BufferedInputStream
对象字节输入流 ObjectInputStream
- 将保存的数据(值和数据类型)重新恢复为Dog对象,称之为反序列化。
@Test
public void readData(){
String filePath = "D:\\ideaProject\\JJ\\untitled\\src\\stream\\outputstream\\3.txt";
// 反序列化,反序列化时的顺序需要与序列化时一致,否则会出异常
try{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath));
// dog的编译类型为object dog的运行类型为Dog
Object o = ois.readObject();
System.out.println(o.getClass());
System.out.println(o);
// 目前运行的结果也是正确的
// 1. 如果需要调用dog的方法,需要向下转型
Dog dog = (Dog) o;
System.out.println();
ois.close();
}catch (Exception e){
e.printStackTrace();
}
}
对象处理流注意事项
- 读写顺序要一致。
- 要求序列化或者反序列化对象,需要实现Serializable。
- 序列化的类建议添加SerialVersionUID,提高版本的兼容性。
- 序列化对象时,默认将里面所有属性都进行序列化,但除了static或transient修饰的成员
- 序列化对象时,要求里面属性的类型也需要实现序列化接口
- 序列化具备可继承性,某类实现了序列化,则他的所有子列也已经默认实现了序列化。
标准输入 System.in
比较特殊,其编译类型为InputStream,运行类型为BufferedInputStream。
// 表示的是标准输入,默认对应位置为键盘
// System.in 编译类型 InputStream
// System.in 运行类型 BufferedInputStream
InputStream in = System.in;
System.out.println(System.in.getClass());
字节输出流 OutputStream
文件输出流 FileOutputStream
继承结构如下图:
写入数据时,需要先将数据转化为bytes。
@Test
public void writeFile(){
// 创建FIleOutputStream
String filePath = "d:\\b.txt";
try {
/**
* 创建方式有两种
* 覆盖 new FileOutputStream(filePath);
* 追加 new FileOutputStream(filePath,true);
*/
fileOutputStream = new FileOutputStream(filePath,true);
/**
* 将数据写入到写入流中
* public void write(int b) Writes the specified byte to this file output stream.
* public void write(byte b[]) Writes b.length bytes from the specified byte array to this output stream. The general contract for write(b) is that it should have exactly the same effect as the call write(b, 0, b.length).
*/
fileOutputStream.write("hello world".getBytes());
} catch (IOException e) {
throw new RuntimeException(e);
}finally {
try {
fileOutputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
缓冲字节输出流 BufferedOutputStream
对象字节输出流 ObjectOutputStream
- 保存数据时即保存值又保存数据类型叫做序列化。
序列化需要实现以下两个接口之一:Serializable
@Test
public void writeData() {
// BufferedOutputStream
// 序列化后,保存的文件格式不是纯文本,而是序列化之后的格式
String filePath = "D:\\ideaProject\\JJ\\untitled\\src\\stream\\outputstream\\3.txt";
try {
oos = new ObjectOutputStream(new FileOutputStream(filePath));
// oos.writeInt(100); // int -> Integer (实现了Serializable)
// oos.writeBoolean(true);
// oos.writeChar('a');
// oos.writeDouble(9.5);
// 保存一个对象
oos.writeObject(new Dog("修狗",123,"中国红","中国"));
} catch (IOException e) {
e.printStackTrace();
}finally {
if (oos != null){
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
序列化对象时,要求对象必须实现Serializable
public class Dog implements Serializable {
private String name;
private Integer age;
private static String nation;
private transient String color;
private static Long SerialVersionUID = 1L;
public Dog(String name, Integer age, String color,String nationInput) {
this.name = name;
this.age = age;
this.color = color;
nation = nationInput;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age=" + age +
", color='" + color + '\'' +
'}' + nation;
}
}
标准输出 System.out
// 表示的是标准输出,默认对应位置为显示器
// 编译类型 PrintStream
// 运行类型 PrintStream
PrintStream out = System.out;
System.out.println(System.out.getClass());
打印流 PrintStream
@Test
public void writeDate3(){
// 在默认情况下,PrintStream打印流输出数据的位置是 标准输出即显示器
// 打印流,默认是输出流
PrintStream out = System.out;
out.print("jj");
// 我们也可以使用 write方法进行打印
try {
PrintStream printStream = new PrintStream("D:\\ideaProject\\JJ\\untitled\\src\\stream\\outputstream\\c.txt");
printStream.write("竞技精神".getBytes());
// 可以修改系统输出日志的地方
System.setOut(new PrintStream(new PrintStream("D:\\ideaProject\\JJ\\untitled\\src\\stream\\outputstream\\d.txt")));
} catch (IOException e) {
throw new RuntimeException(e);
}
try {
out.write("竞技精神".getBytes());
} catch (IOException e) {
e.printStackTrace();
}finally {
out.close();
}
}
字符流
字符输入流 FileReader
读取单个字符并将其输出处理。
- 这里需要注意的是,编程环境的编码方式与电脑环境的编码方式是否一致,编程环境的编码方式可以通过fileReader.getEncoding()获得到。
- 电脑txt的编码方式在创建的时候就可以获取到,如果二者编码方式相同,则读取出的txt文本数据不是乱码,否则为乱码。
@Test
public void fileReaderTest(){
String filePath = "d:\\hello.txt";
try {
int data = 0;
fileReader = new FileReader(filePath);
System.out.println(fileReader.getEncoding());
while ((data = fileReader.read()) != -1){
System.out.print((char) data);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
fileReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
也可以设置字符串数组存储读取的数据,程序如下。
@Test
public void fileReaderTest2() {
String filePath = "d:\\hello.txt";
try {
int dataLen = 0;
char[] buf = new char[8];
fileReader = new FileReader(filePath);
System.out.println(fileReader.getEncoding());
while ((dataLen = fileReader.read(buf)) != -1) {
System.out.print(new String(buf, 0, dataLen));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fileReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
缓冲字符输入流 BufferedReader
@Test
public void bufferReaderTest(){
String filePath = "d:\\hello.txt";
try {
bufferedReader = new BufferedReader(new FileReader(filePath));
String line;
while ((line = bufferedReader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
/**
* 关闭外层的流,内存的流会自动关闭
*/
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
转换输入流 InputStreamReader
// 创建BufferedReader对象读取a.txt
String filePath = "D:\\ideaProject\\JJ\\untitled\\src\\stream\\outputstream\\a.txt";
try {
// 字节流转化为字符流
// FileInputStream 转化为 inputStreamReader
// inputStreamReader在读取数据时,声明文件读取格式为"utf-8"
InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream(filePath), "utf-8");
br = new BufferedReader(inputStreamReader);
String s = br.readLine();
System.out.println("读取到内容=" + s);
} catch (IOException e) {
throw new RuntimeException(e);
}finally {
if (br != null){
try {
br.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
字符输出流 FileReader
@Test
public void FileWriteTest(){
String filePath = "d:\\note.txt";
try {
fileWriter = new FileWriter(filePath);
fileWriter.write("1");
System.out.println("写入结束");
} catch (IOException e) {
throw new RuntimeException(e);
}finally {
try {
if (fileWriter != null){
/**
* 为什么会有flush?
* 可能是因为如果数据量很大时,读取过程中需要将数据不停的读取到内存中,占用内存过大,需要将数据及时写入硬盘保存。
* flush 会调用到 this.writeBytes(); 将写入内存中的数据写入到硬盘中
* close 相当于 flush + close
*/
fileWriter.flush();
fileWriter.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
缓冲字符输出流 BufferedWriter
public class BufferedWriter_ {
public static void main(String[] args) {
String filePath = "d:\\node.txt";
BufferedWriter bufferedWriter = null;
try {
/**
* 如果想要实现追加写入,BufferedWriter本身是不具备追加写入功能的,需要在节点流创建环节
* 声明追加
*/
bufferedWriter = new BufferedWriter(new FileWriter(filePath,true));
// bufferedWriter = new BufferedWriter(new FileWriter(filePath));
bufferedWriter.write("hello,竞技世界");
bufferedWriter.newLine();
bufferedWriter.write("hello, JJWorld");
} catch (IOException e) {
throw new RuntimeException(e);
}finally {
try {
// 仅关闭外层的流就可以
bufferedWriter.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
转换输出流 OutputStreamWriter
String filePath = "D:\\ideaProject\\JJ\\untitled\\src\\stream\\outputstream\\b.txt";
try {
gbk = new OutputStreamWriter(new FileOutputStream(filePath), "gbk");
gbk.write("hello, JJ世界");
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
gbk.close();
} catch (IOException e) {
e.printStackTrace();
}
}
打印字符流 PrintWriter
@Test
public void writeDate4(){
try {
// printWriter = new PrintWriter(System.out);
// printWriter.print("你好,竞技精神!!!");
printWriter = new PrintWriter(new FileWriter("D:\\ideaProject\\JJ\\untitled\\src\\stream\\outputstream\\f.txt"));
printWriter.write("你好,竞技精神!!!");
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
printWriter.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
配置文件 properties
@Test
private void readProperties(){
Properties properties = new Properties();
try {
properties.load(new FileReader("D:\\ideaProject\\JJ\\untitled\\src\\stream\\outputstream\\mysql.properties"));
// 将键值对显示到控制台
properties.list(System.out);
// 获取属性
String company = properties.getProperty("company");
System.out.println(company);
properties.setProperty("age","18");
properties.setProperty("say","你好");
properties.store(new FileOutputStream("D:\\ideaProject\\JJ\\untitled\\src\\stream\\outputstream\\mysql2.properties"),null);
properties.store(new FileOutputStream("D:\\ideaProject\\JJ\\untitled\\src\\stream\\outputstream\\mysql3.properties"),"JJ");
} catch (IOException e) {
e.printStackTrace();
}
}
测试
字节流实现文件拷贝
实现文件拷贝,将d盘下的picture拷贝到 d:\picture中
思路,每读取部分文件,就将文件写入到目标文件中。
/**
* 实现文件拷贝,将d盘下的picture拷贝到 d:\\picture中
*/
@Test
public void fileCopy() {
String sourceFilePath = "d:\\picture1.jpg";
String targetFilePath = "d:\\picture\\picture1.jpg";
try {
fileInputStream = new FileInputStream(sourceFilePath);
fileOutputStream = new FileOutputStream(targetFilePath);
int dataLength = 0;
byte[] buf = new byte[1024];
while ((dataLength = fileInputStream.read(buf)) != -1) {
/**
* 因为最后一次读取数据时,数据的长度可能不够1024,所以写入数据
* 需要限制数据的长度为dataLength
*/
fileOutputStream.write(buf, 0, dataLength);
}
System.out.println("拷贝完成!!!");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (fileOutputStream != null) {
fileOutputStream.close();
}
if (fileInputStream != null) {
fileInputStream.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
缓冲字符流实现文件拷贝
字符流拷贝的局限:字符流不能拷贝以二进制存储的文件,否则拷贝的文件无法打开。
以字符编码UTF8为例,本身文件为字节为单位,现在将三个字节组合一起组合成一个字符,会导致拷贝的内容如法还原。
@Test
public void copyFile() {
String sourceFilePath = "d:\\hello.txt";
String targetFilePath = "d:\\hello1.txt";
String line = null;
try {
// 字符流不能处理二进制文件,例如视频、图片、音频等
bufferedReader = new BufferedReader(new FileReader(sourceFilePath));
bufferedWriter = new BufferedWriter(new FileWriter(targetFilePath));
while ((line = bufferedReader.readLine()) != null) {
bufferedWriter.write(line);
bufferedWriter.newLine();
bufferedWriter.flush();
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
if (bufferedReader != null) {
bufferedReader.close();
}
if (bufferedWriter != null) {
bufferedWriter.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
缓冲字节流实现文件拷贝
public void func(){
// String srcFilePath = "D:\\ideaProject\\JJ\\untitled\\src\\stream\\outputstream\\1.jpg";
String srcFilePath = "D:\\ideaProject\\JJ\\untitled\\src\\stream\\outputstream\\1.txt";
// String destFilePath = "D:\\ideaProject\\JJ\\untitled\\src\\stream\\outputstream\\2.jpg";
String destFilePath = "D:\\ideaProject\\JJ\\untitled\\src\\stream\\outputstream\\2.txt";
try {
bufferedInputStream = new BufferedInputStream(new FileInputStream(srcFilePath));
bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(destFilePath));
byte[] bytes = new byte[1024];
int readLen = 0;
while ((readLen = bufferedInputStream.read(bytes))!=-1){
bufferedOutputStream.write(bytes,0,readLen);
}
System.out.println("文件拷贝完成!!!");
}catch (Exception e){
e.printStackTrace();
}finally {
try {
if (bufferedInputStream!=null){
bufferedInputStream.close();
}
if (bufferedOutputStream!=null){
bufferedOutputStream.close();
}
}catch (Exception e){
e.printStackTrace();
}
}
}