按照流向的不同, 分为输入流和输出流, 我们应该站在程序的角度
按照数据单位的不同, 分为**字节流(8bit)**和 字符流(16bit)
按照流的角色不同: 节点流和处理流
重点标出了☺
对于文本文件(.txt .java .c .cpp), 用字符流
注意 : 如果文本文件只复制的话, 用字节流也可以, 读取的话可能会出现乱码
输出有问题, 原因 : 某个长度的数组, 对于每一轮都不会是空的, 而是用之后的数据覆盖上一轮, 所以会出现错误
/*
1 下面的hello.text文件内容读取到程序中, 并输出到控制台
2 异常的处理, 为了保证资源一定被关闭, 需要try-catch-finally处理, 如果抛出那么最后面可能不会执行
3 需要确报读入的文件一定要存在, 否者会报FileNotFoundException
*/
@Test
public void FileReaderTest() {
FileReader fr = null;
try {
// 1. 实例化File类的对象, 指明文件
File file = new File("hello.text"); // 相较于当前的Module
// System.out.println(file.getAbsolutePath());
// 2. 提供具体的流
fr = new FileReader(file);
// 3. 数据的读入
// read()返回一个字符串. 如果达到文件末尾, 返回-1
int read = fr.read();
while (read != -1) {
System.out.print((char) read);
read = fr.read(); // 相当于遍历下一个, 指正下移(类似next())
}
// 方式二:
// int date;
// while ((date = fr.read()) != -1) {
// System.out.println((char) read);
//
// }
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
// 4. 流的关闭 后果:内存泄漏
// try {
// if (fr != null)
// fr.close();
// } catch (IOException e) {
// throw new RuntimeException(e);
// }
// 方式二 和方式一没有区别
if (fr != null) {
try {
fr.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
改良版本 : 使用read()的重载构方法
read()也有类似String的public int read(char cbuf[], int off, int len)构造器, 我们一般用的是他的特殊情况len = cbuf.length();
@Test
public void test2() {
FileReader fr = null;
try {
// 1. File类的实例化
File file = new File("hello.text");
// 2. StringBuffer的实例化
fr = new FileReader(file);
// 3. 读入操作
char[] cbuff = new char[5];
// 正确写法一
// int len;
// while ((len = fr.read(cbuff)) != -1) {
// for (int i = 0; i < len; i++) {
// System.out.print(cbuff[i]);
// }
// }
// 正确写法二
// int len;
// while ((len = fr.read(cbuff)) != -1) {
// String s = new String(cbuff, 0, len);
// System.out.print(s);
// }
// 错误写法
// int len;
// while ((len = fr.read(cbuff)) != -1) {
// String s = new String(cbuff);
// System.out.print(s);
// }
// 错误写法二
int len;
while ((len = fr.read(cbuff)) != -1) {
for (int i = 0; i < cbuff.length; i++) {
System.out.print(cbuff[i]);
}
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (fr != null) {
// 4.关闭流
try {
fr.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
把内存中写出的数据到硬盘说明:
输出操作, File对应的文件可以不存在, 并不会报异常
如果不存在, 总结创建个 如果存在, 根据构造器选择不同的方式:
- new FileWriter(“haha.txt”) / new FileWriter(“haha.text”, false) 直接覆盖原文件
- new FileWriter("haha.txt, true) 不覆盖原文件, 而是在原文件中添加内容
把内存中写出的数据到硬盘
说明:
1. 输出操作, File对应的文件可以不存在, 并不会报异常
2. 如果不存在, 总结创建个
如果存在, 根据构造器的不同选择不同的方式:
new FileWriter("haha.txt") / new FileWriter("haha.text" , false) 直接覆盖原文件
new FileWriter("haha.txt, true) 不覆盖原文件, 而是在原文件中添加内容
实例:
@Test
public void testFileWriter() {
FileWriter fw = null;
try {
// 1. 提供File类的对象, 指明写出到的文件
File file = new File("haha.txt");
// 2. 提供FileWriter的实例化对象, 用于数据的写出
fw = new FileWriter(file, true);
// 3. 写入操作
fw.write("I have a dream!");
fw.write(" you need have a dream!");
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (fw != null) {// 可能会有空指针异常
try {
fw.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
读入写出测试:
两个关闭操作, 可以并列的写.
因为: 两个处理异常时候都会执行, 因为try-catch-finally会把异常处理
@Test
public void test() {
FileReader fr = null;
FileWriter fw = null;
try {
// 1. File类
File srcFile = new File("haha.txt");
File destFile = new File("copy.txt");
// 2. 创建对象
fr = new FileReader(srcFile);
fw = new FileWriter(destFile);
// 3. 调用方法
char[] cbuf = new char[5];
int len;
while ((len = fr.read(cbuf)) != -1) {
fw.write(cbuf, 0, len);
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally { // 两个处理异常时候都会执行, 因为try-catch-finally会把异常处理
// if (fw != null) {
// try {
// fw.close();
// } catch (IOException e) {
// throw new RuntimeException(e);
// }
// }
// if (fr != null) {
// try {
// fr.close();
// } catch (IOException e) {
// throw new RuntimeException(e);
// }
// }
if (fw != null) {
try {
if (fw != null) fw.close();
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
if (fr != null) fr.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}
对于非文本文件(.jpj .doc .ppt .mp4), 用字节流
// 封装成方法
public void copyFile(String srcFile, String desFile) {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
File file = new File(srcFile);
File file1 = new File(desFile);
fis = new FileInputStream(file);
fos = new FileOutputStream(file1);
byte[] bytes = new byte[1024];
int len;
while ((len = fis.read(bytes)) != -1) {
fos.write(bytes);
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
if (fis != null) fis.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
try {
if (fos != null) fos.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
/**
* 复制花费的时间为:1184 对应 byte[] bytes = new byte[1024];
*
* 复制花费的时间为:185390 对应 byte[] bytes = new byte[5];
*/
@Test
public void teat2() {
long start = System.currentTimeMillis();
String Start = "D:\\下载\\Video\\鱼皮.mp4";
String End = "D:\\SOFT\\destbook\\yupi.mp4";
copyFile(Start, End);
long end = System.currentTimeMillis();
System.out.println("复制花费的时间为:" +( end - start));
}
是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写。如BufferedReader.处理流的构造方法总是要带一个其他的流对象做参数。 作用在其他流上.
// 创建流
// 1 造节点流
FileInputStream fis = new FileInputStream(srcFile);
FileOutputStream fos = new FileOutputStream(destFile);
// 2 造缓存流
BufferedInputStream bis = new BufferedInputStream(fis);
BufferedOutputStream bos = new BufferedOutputStream(fos);
注意: 关闭流的顺序是由内到外, 但是关闭缓冲流默认关闭节点流
练习三
利用流读取数据, 利用**HashMap<>()**存储数据, 然后利用writer()写入数据 HashMap的 key 是存储的存储数据的类型 利用 **value **存储此类型出现的次数
public void test() {
FileReader fr = null;
FileWriter fw = null;
try {
File file = new File("dbcp.txt");
File desFile = new File("WordCount.txt");
fr = new FileReader(file);
fw = new FileWriter(desFile);
HashMap<Character, Integer> map = new HashMap<>();
int len;
while ((len = fr.read()) != -1) {
char achar = (char) len;
if (map.containsKey(achar)) {
map.put(achar, map.get(achar) + 1);
} else {
map.put(achar, 1);
}
}
Set<Map.Entry<Character, Integer>> entries = map.entrySet();
for (Map.Entry<Character, Integer> set : entries) {
switch (set.getKey()) {
case ' ':
fw.write("空格 = " + set.getValue() + "\n");
break;
case '\t' :
fw.write("制表符 = " + set.getValue() + "\n");
break;
case '\n':
fw.write("换行 = " + set.getValue() + "\n");
break;
case '\r':
fw.write("回车 = " + set.getValue() + "\n");
break;
default:
fw.write(set.getKey() + " = " + set.getValue() + "\n");
break;
}
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
if (fr != null) fr.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
try {
if (fw != null) fw.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
InputStreamReader属于字符流 : 将一个字节输入流转化为字符的输入流
OutputStream**Writer **属于字符流 : 将一个字符的输出流转化为字节的输出流
作用: 提供字节流和字符流的转化
解码 : 字节 , 字节数组 —> 字符, 字符数组
编码: 字符, 字符数组 —> 字节, 字节数组
例子:
// 1. 创建文件和流
File file1 = new File("WordCount.txt");
File file2 = new File("WordCount_gbk.txt");
FileInputStream fis = new FileInputStream(file1);
FileOutputStream fos = new FileOutputStream(file2);
isr = new InputStreamReader(fis, "utf-8");
osw = new OutputStreamWriter(fos, "gbk");
// 2. 操作数据
char[] cbuf = new char[5];
int len;
while ((len = isr.read(cbuf)) != -1) {
osw.write(cbuf, 0, len);
}
ANSI编码,通常指的是平台的默认编码,
例如英文操作系统中是ISO-8859-1,中文系统是GB
修改默认的输入和输出行为:
System类的setIn(InputStream is) / setOut(PrintStream ps)方式重新指定输入和输出的流。
打印流:
PrintStream 和PrintWriter
说明:
提供了一系列重载的print()和println()方法,用于多种数据类型的输出
System.out返回的是PrintStream的实例
数据流:
DataInputStream 和 DataOutputStream
作用:
用于读取或写出基本数据类型的变量或字符串
注意 : 一定要按照写入的数据顺序进行读取
示例代码:
/ *
练习:将内存中的字符串、基本数据类型的变量写出到文件中。
注意:处理异常的话,仍然应该使用try-catch-finally.
*/
@Test
public void test3() throws IOException {
//1.
DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.txt"));
//2.
dos.writeUTF("刘建辰");
dos.flush();//刷新操作,将内存中的数据写入文件
dos.writeInt(23);
dos.flush();
dos.writeBoolean(true);
dos.flush();
//3.
dos.close();
}
/*
将文件中存储的基本数据类型变量和字符串读取到内存中,保存在变量中。
注意点:读取不同类型的数据的顺序要与当初写入文件时,保存的数据的顺序一致!
*/
@Test
public void test4() throws IOException {
//1.
DataInputStream dis = new DataInputStream(new FileInputStream("data.txt"));
//2.
String name = dis.readUTF();
int age = dis.readInt();
boolean isMale = dis.readBoolean();
System.out.println("name = " + name);
System.out.println("age = " + age);
System.out.println("isMale = " + isMale);
//3.
dis.close();
}
序列化 : 将内存中Java的对象保存到磁盘或通过网络传输 使用ObjectOutputStream实现
反序列化 : 将磁盘中的文件还原为java内存中的对象 使用ObjectInputStream实现
要求 :
需要实现 java.io.Serializable
自定义一个序列号,如果不定义那么数据修改系统生成的就会报错
属性必须是可以序列化的, 基本数据类型都是可序列化的
例子:
反序列化是, 输出必须按照写入的顺序
/**
* 序列化 : 将内存中java的对象保存到磁盘或通过网络传输
* 使用ObjectOutputStream实现
*/
@Test
public void ObjectOutputStreamTest() {
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new FileOutputStream("Object.dat"));
oos.writeObject(new String("我爱学习Java"));
oos.flush(); // 刷新操作
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (oos != null) {
try {
oos.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
/**
* 反序列化 : 将磁盘中的文件还原为java内存中的对象
* ObjectInputStream
*/
@Test
public void ObjectInputStreamTest() {
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new FileInputStream("Object.dat"));
Object o = ois.readObject();
String str = (String) o;
} catch (IOException e) {
throw new RuntimeException(e);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} finally {
try {
if(ois != null) ois.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
以后可以用 JSON 字符串, 代替对象流
特点 : 允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。//当其它程序获取了这种二进制流,就可以恢复成原来的Java对象
构造器的参数
注意点:
RandomAccessFileTest 直接继承与java.long.object,
实现了DataInput和DataOutput可以是输入流也可以是输出流(但是需要造两个对象)
如果文件不存在, 那么直接创建
如果文件存在, 那么会覆盖文件, 特点: 默认情况, 覆盖文件的内容从头开始覆盖
如何实现插入效果 ?
/**
* 实现插入的效果
*/
@Test
public void test3() {
RandomAccessFile eaf = null;
RandomAccessFile raf = null;
try {
eaf = new RandomAccessFile("hello.txt", "rw");
eaf.seek(3); // 把指针指到序号为3的位置(从3开始)
StringBuffer sb = new StringBuffer((int) new File("hello.txt").length()); // 存储从序号3以后的数据
byte[] bytes = new byte[20]; // 存储到bytes数组之中
int len;
while ((len = eaf.read(bytes)) != -1) {
sb.append(new String(bytes, 0, len));
}
eaf.seek(3);// 上面操作结束之后, 指针在最后面, 需要把指正放在前面
eaf.write("xyz".getBytes());
eaf.write(sb.toString().getBytes());
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
if(eaf != null) eaf.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
try {
if (raf != null) raf.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
对应的方法