IO流
1. 什么是IO
生活中,你肯定经历过这样的场景。当你编辑一个文本文件,忘记了ctrl+s ,可能文件就白白编辑了。当你电脑上插入一个U盘,可以把一个视频,拷贝到你的电脑硬盘里。那么数据都是在哪些设备上的呢?键盘、内存、硬盘、外接设备等等。
我们把这种数据的传输,可以看做是一种数据的流动,按照流动的方向,以内存为基准,分为输入input 和输出output ,即流向内存是输入流,流出内存的输出流。
Java中I/O操作主要是指使用java.io包下的内容,进行输入、输出操作。输入也叫做读取数据,输出也叫做作写出数据。
2. IO的分类
2.1 根据数据的流向分为:输入流和输出流。
输入流 :把数据从其他设备上读取到内存中的流。
输出流 :把数据从内存 中写出到其他设备上的流。
2.2 格局数据的类型分为:字节流和字符流。
字节流 :以字节为单位,读写数据的流。
字符流 :以字符为单位,读写数据的流。
3. IO的流向说明图解
4. 顶级父类们
输入流 输出流
字节流 字节输入流 InputStream
字节输出流 OutputStream
字符流 字符输入流 Reader
字符输出流 Writer
以Stream后缀结尾就是字节流
以er后续结尾的就是字符流
字节流
1. 一切皆为字节
一切文件数据(文本、图片、视频等)在存储时,都是以二进制数字的形式保存,都一个一个的字节,那么传输时一样如此。所以,字节流可以传输任意文件数据。在操作流的时候,我们要时刻明确,无论使用什么样的流对象,底层传输的始终为二进制数据。
小贴士
字节流可以操作任何格式的数据。
2. 字节输出流【OutputStream】
java.io.OutputStream 抽象类是表示字节输出流的所有类的超类,将指定的字节信息写出到目的地。它定义了字节输出流的基本共性功能方法。
public void close() :关闭此输出流并释放与此流相关联的任何系统资源。
public void flush() :刷新此输出流并强制任何缓冲的输出字节被写出。
public void write(byte[] b):将 b.length字节从指定的字节数组写入此输出流。
public void write(byte[] b, int off, int len) :从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流。
public abstract void write(int b) :将指定的字节输出流。
小贴士:
close方法,当完成流的操作时,必须调用此方法,释放系统资源。
public void test() throws IOException {
/*
1 写一个a.txt 到 c:/a.txt 写入内容为 一个字节,一个字节数组
*/
File file = new File("c:/a.txt");
//判断
if(!file.exists()) {
file.createNewFile();
}
//创建输出流对象
OutputStream os = new FileOutputStream(file);
//写出一个字节的方法
os.write(97);
os.write(98);
System.out.println("写出了一个字节");
//写出一个字节数组
byte[] b = {99,100,101,102,103};//98是b
os.write(b);
System.out.println("写出一个字节数组");
//从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流
os.write(b, 2, 3);//abcdefgefg
}
}
3. 【FileOutputStream】类
OutputStream有很多子类,我们从最简单的一个子类开始。
java.io.FileOutputStream 类是文件输出流,用于将数据写出到文件。
构造方法
public FileOutputStream(File file):创建文件输出流以写入由指定的 File对象表示的文件。
public FileOutputStream(String name): 创建文件输出流以指定的名称写入文件。
当你创建一个流对象时,必须传入一个文件路径。该路径下,如果没有这个文件,会创建该文件。如果有这个文件,会清空这个文件的数据。
构造举例,代码如下:
public void test2() throws IOException {
//写一个hello追加到a.txt的末尾处
File file = new File("c:/a.txt");
//输出流对象
//FileOutputStream(File file, boolean append)
//OutputStream os = new FileOutputStream(file, true);
OutputStream outputStream=new FileOutputStream(file);//会清空原来的文本数据
//定义一个字符串
String str = "hello";
byte[] b = str.getBytes();
//直接写一个字节数组
os.write(b);
//换行
os.write("\r\n".getBytes());
os.write(98);//abchellohello
//b
}
小贴士:
虽然参数为int类型四个字节,但是只会保留一个字节的信息写出。
流操作完毕后,必须释放系统资源,调用close方法,千万记得。
在JDK7以前和JDK7以后是两种方式释放系统资源的
JDK7前处理
之前的入门练习,我们一直把异常抛出,而实际开发中并不能这样处理,建议使用try…catch…finally 代码块,处理异常部分,代码使用演示:
catch (Exception e) {
e.printStackTrace();
}finally {//总有一部分程序员会忘记在这里写上finally
//能否最后关闭? 不能!!
System.out.println("关闭之前打印...");
r.close();
}
JDK7的处理(扩展知识点)
还可以使用JDK7优化后的try-with-resource 语句,该语句确保了每个资源在语句结束时关闭。所谓的资源(resource)是指在程序完成后,必须关闭的对象。
try (创建流对象语句,如果多个,使用’;'隔开) {
// 读写数据
} catch (IOException e) {
e.printStackTrace();
}
public void test3() throws IOException {
File file = new File("c:/test.txt");
//字符输入流
//try(流对象){} try...with...resource语法,自动关闭流对象,作用只在try块里面
try(Reader r = new FileReader(file)) {
//Map
Map<String,Integer> map = new HashMap<>();
int len;
while((len = r.read()) > 0) {
char c = (char)len;
//判断
if(map.containsKey(c+"")) {
map.put(c+"", map.get(c+"") + 1);
}else {
map.put(c+"", 1);
}
}
int i = 5/0;
//打印
Set<String> keySet = map.keySet();
for (String key : keySet) {
System.out.println("字符:" + key + "出现" + map.get(key)+"次");
}
} catch (Exception e) {
e.printStackTrace();
}
}
数据追加续写
经过以上的演示,每次程序运行,创建输出流对象,都会清空目标文件中的数据。如何保留目标文件中数据,还能继续添加新数据呢?
答:public FileOutputStream(File file, boolean append): 创建文件输出流以写入由指定的 File对象表示的文件。
public FileOutputStream(String name, boolean append): 创建文件输出流以指定的名称写入文件。
这两个构造方法,参数中都需要传入一个boolean类型的值,true 表示追加数据,false 表示清空原有数据。这样创建的输出流对象,就可以指定是否追加续写了,代码使用演示:
public void test2() throws IOException {
//写一个hello追加到a.txt的末尾处
File file = new File("c:/a.txt");
//输出流对象
//FileOutputStream(File file, boolean append)
OutputStream os = new FileOutputStream(file, true);//加ture后不会清除原来的数据,表示在后尾追加内容
//OutputStream outputStream=new FileOutputStream(file);//会清空原来的文本数据
//定义一个字符串
String str = "hello";
byte[] b = str.getBytes();
//直接写一个字节数组
os.write(b);
//换行
os.write("\r\n".getBytes());
os.write(98);//abchellohello
//b
}
小贴士换行符:
Windows系统里,换行符号是\r\n 。把
回车符\r和换行符\n :
回车符:回到一行的开头(return)。
换行符:下一行(newline)。
系统中的换行:
Windows系统里,每行结尾是 回车+换行 ,即\r\n;
Unix系统里,每行结尾只有 换行 ,即\n;
Mac系统里,每行结尾是 回车 ,即\r。从 Mac OS X开始与Linux统一。
4. 字节输入流【InputStream】
java.io.InputStream 抽象类是表示字节输入流的所有类的超类,可以读取字节信息到内存中。它定义了字节输入流的基本共性功能方法。
-
public void close() :关闭此输入流并释放与此流相关联的任何系统资源。
-
public abstract int read(): 从输入流读取数据的下一个字节。
-
public int read(byte[] b): 从输入流中读取一些字节数,并将它们存储到字节数组 b中 。
package com.gec.io;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import org.junit.Test;
public class InputStreamDemo2 {
@Test
public void test() throws IOException {
//创建文件实例
File file = new File("c:/a.txt");
//创建输入对象
InputStream is = new FileInputStream(file);
//输出
System.out.println(is);
//read() 每次只返回一个字节
// is.read():会自动提升为int
int hasRead = is.read();
System.out.println((char)hasRead);//a
System.out.println((char)is.read());//b
System.out.println((char)is.read());//c
System.out.println(is.read());//-1 代表读取到内容的末尾
}
@Test
public void test2() throws IOException {
//创建文件实例
File file = new File("c:/a.txt");
//创建输入对象
InputStream is = new FileInputStream(file);
//输出
System.out.println(is);
int b;
//循环
/*
* is.read() :每次只读一个字节,然后赋值给b
*/
//while((b= is.read()) != -1) {
while((b = is.read()) >0) {
System.out.println((char)b);
}
}
}
例题:
//要copy的文件
File srcFile =new File("d:/s.jpg");
File desFile = new File("d:/s2.jpg");
//输入流
InputStream is = new FileInputStream(srcFile);
//输出流对象
OutputStream os = new FileOutputStream(desFile);
int hasRead = 0;
byte[] b = new byte[1024];
int count = 0;
/*
is.read(b):每次读1024字节赋值给hasRead
第14次读取的字节:13741-13312 = 429
*/
while((hasRead = is.read(b)) != -1) {
//每次写一个字节到b.txt中
os.write(b,0,hasRead);
count++;
}
System.out.println("一共读写了" + count + "次");
System.out.println("写好了");
5. 【FileInputStream】类
java.io.FileInputStream 类是文件输入流,从文件中读取字节。
构造方法
FileInputStream(File file): 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的 File对象 file命名。
FileInputStream(String name): 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的路径名 name命名。
当你创建一个流对象时,必须传入一个文件路径。该路径下,如果没有该文件,会抛出FileNotFoundException 。
InputStream is = new FileInputStream(srcFile);
小贴士:
使用数组读取,每次读取多个字节,减少了系统间的IO操作次数,从而提高了读写的效率,建议开发中使用。
.io.FileInputStream 类是文件输入流,从文件中读取字节。
构造方法
FileInputStream(File file): 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的 File对象 file命名。
FileInputStream(String name): 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的路径名 name命名。
当你创建一个流对象时,必须传入一个文件路径。该路径下,如果没有该文件,会抛出FileNotFoundException 。
InputStream is = new FileInputStream(srcFile);
小贴士:
使用数组读取,每次读取多个字节,减少了系统间的IO操作次数,从而提高了读写的效率,建议开发中使用。
1.字符流
为什么要有字符流?
当使用字节流读取文本文件时,可能会有一个小问题。就是遇到中文字符时,可能不会显示完整的字符,那是因为一个中文字符可能占用多个字节存储。
Java中字符是采用Unicode标准(内存中),Unicode 编码中,一个英文为一个字节,一个中文为两个字节。
而在UTF-8编码中,一个中文字符是3个字节。例如下面图中,“云深不知处”5个中文对应的是15个字节:-28-70-111-26-73-79-28-72-115-25-97-91-27-92-124
如果使用字节流处理中文,如果一次读写一个字符对应的字节数就不会有问题,一旦将一个字符对应的字节分裂开来,就会出现乱码了。为了更方便地处理中文这些字符,Java就推出了字符流。以字符为单位读写数据,专门用于处理文本文件。
1. 字符输入流【Reader】
java.io.Reader抽象类是表示用于读取字符流的所有类的超类,可以读取字符信息到内存中。它定义了字符输入流的基本共性功能方法。
public void close() :
关闭此流并释放与此流相关联的任何系统资源。
public int read():
从输入流读取一个字符。
public int read(char[] cbuf):
从输入流中读取一些字符,并将它们存储到字符数组 cbuf中 。
读取字符数据
读取字符:read方法,每次可以读取一个字符的数据,提升为int类型,读取到文件末尾,返回-1,循环读取,
小贴士:虽然读取了一个字符,但是会自动提升为int类型。
使用字符数组读取:read(char[] cbuf),每次读取b的长度个字符到数组中,返回读取到的有效字符个数,读取到末尾时,返回-1
//创建File实例
File file = new File("d:/abc.txt");
//创建字符输入流对象
Reader r = new FileReader(file);
//调用read
int hasRead = r.read();
System.out.println((char)hasRead);
hasRead = r.read();
//使用char[]数组来读取
int len = 0;
char[] cs = new char[1024];
while((len = r.read(cs)) >0) {
//组装字符串
String str = new String(cs,0,len);
System.out.println(str);
}
StringBuffer
public void test3() throws IOException {
//组装一个字符串
StringBuffer sb = new StringBuffer();
sb.append("从明天开始");
//sb.append("一夜暴富");
//sb.append("财富自由");
//海子
sb.append("喂马,辟柴");
sb.append("面朝大海,春暖花开");
StringReader reader = new StringReader(sb.toString());
//使用StringReader读取这个sb,把这个字符串输出来
char[] cs = new char[1024];
int hasRead = 0;
while((hasRead = reader.read(cs)) >0) {
System.out.println(new String(cs,0,hasRead));
}
}
2. FileReader类
java.io.FileReader 类是读取字符文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区。
- FileReader(File file): 创建一个新的 FileReader ,给定要读取的File对象。
2. FileReader(String fileName): 创建一个新的 FileReader ,给定要读取的文件的名称。
当你创建一个流对象时,必须传入一个文件路径。类似于FileInputStream 。
//创建File实例
File file = new File("d:/abc.txt");
//创建字符输入流对象
Reader r = new FileReader(file);
Reader r = new FileReader("d:/abc.txt");
小贴士:
字符编码:字节与字符的对应规则。Windows系统的中文编码默认是GBK编码表。
3.字符输出流【Writer】
java.io.Writer 抽象类是表示用于写出字符流的所有类的超类,将指定的字符信息写出到目的地。它定义了字节输出流的基本共性功能方法。
-
void write(int c) 写入单个字符。
-
void write(char[] cbuf) 写入字符数组。
-
abstract void write(char[] cbuf, int off, int len) 写入字符数组的某一部分,off数组的开始索引, len写的字符个数。
-
void write(String str) 写入字符串。
-
void write(String str, int off, int len) 写入字符串的某一部分,off字符串的开始索引,len写的字符个数。
-
void flush() 刷新该流的缓冲。
-
void close() 关闭此流,但要先刷新它。
@Test
public void test() throws IOException {
//写若干个字符到d:/ab.txt中 你 你好 你们好
File file = new File("d:/ab.txt");
//创建字符输出流对象
Writer w = new FileWriter(file);
//调用方法 写一个int
w.write(97);//a
//写一个字符出去
w.write('你');
//写一个字符数组出去
char[] cs = {'你','好'};
w.write(cs);
//写一整个字符串出去
w.write("你们好");
//定义一个字符串
char[] cs2 = {'你','好','中','国','人'};
//写入指定数组,从0开始,写3个
w.write(cs2,0,3);
//刷新
w.flush();
System.out.println("写出ok...");
}
4. FileWriter类
java.io.FileWriter 类是写出字符到文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区。
构造方法
-
FileWriter(File file): 创建一个新的 FileWriter,给定要读取的File对象。
-
FileWriter(String fileName): 创建一个新的 FileWriter,给定要读取的文件的名称。
当你创建一个流对象时,必须传入一个文件路径,类似于FileOutputStream。
File file = new File("d:/ab.txt");
//创建字符输出流对象
Writer w = new FileWriter(file);
小贴士:
虽然参数为int类型四个字节,但是只会保留一个字符的信息写出。
未调用close方法,数据只是保存到了缓冲区,并未写出到文件中。
关闭和刷新
因为内置缓冲区的原因,如果不关闭输出流,无法写出字符到文件中。但是关闭的流对象,是无法继续写出数据的。如果我们既想写出数据,又想继续使用流,就需要flush 方法了。
flush() :刷新缓冲区,流对象可以继续使用。
close ():先刷新缓冲区,然后通知系统释放资源。流对象不可以再被使用了。
小贴士:字符流,只能操作文本文件,不能操作图片,视频等非文本文件。当我们单纯读或者写文本文件时 使用字符流 其他情况使用字节流
字符串输出流
StringWriter
继承Writer,处理字符串的流
2.属性集(Properties)
1. 概述
java.util.Properties 继承于 Hashtable ,来表示一个持久的属性集。它使用键值结构存储数据,每个键及其对应值都是一个字符串。该类也被许多Java类使用,比如获取系统属性时,System.getProperties 方法就是返回一个Properties对象。
注意:在泛型异常笔记也有一些概述代码
2. Properties类
构造方法
public Properties() :创建一个空的属性列表。
基本的存储方法
public Object setProperty(String key, String value) : 保存一对属性。
public String getProperty(String key) :使用此属性列表中指定的键搜索属性值。
public Set stringPropertyNames() :所有键的名称的集合。
与流相关的方法
public void load(InputStream inStream): 从字节输入流中读取键值对。
参数中使用了字节输入流,通过流对象,可以关联到某文件上,这样就能够加载文本中的数据了。文本数据格式:
filename**=**a.txt
length**=**209385038
location**=**D:\a.txt
加载代码演示:
小贴士:文本中的数据,必须是键值对形式,可以使用空格、等号、冒号等符号分隔。
具体演示代码如下:
package com.gec.ioex;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Properties;
import java.util.Set;
import org.junit.Test;
public class PropertiesDemo2 {
@Test
public void test() {
Properties p = new Properties();
// /test.properties eclipse下的src下的文件 idea: 建议在工程创建一个文件夹,后面给文件夹标志为resource,灰黄色
// idea:test.properties直接放文件夹里面 读取文件时不要加斜杠
try(InputStream is = PropertiesDemo2.class.getResourceAsStream("/test.properties")){
//调用load
p.load(is);
//获取属性集
Set<String> propertyNames = p.stringPropertyNames();
for (String name : propertyNames) {
System.out.println(name + "=" + p.getProperty(name));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
3.缓冲流
1. 概述
我们知道,程序与磁盘的交互相对于内存运算是很慢的,容易成为程序的性能瓶颈。减少程序与磁盘的交互,是提升程序效率一种有效手段。缓冲流,就应用这种思路:普通流每次读写一个字节,而缓冲流在内存中设置一个缓存区,缓冲区先存储足够的待操作数据后,再与内存或磁盘进行交互。这样,在总数据量不变的情况下,通过提高每次交互的数据量,减少了交互次数。
联想一下生活中的例子,我们搬砖的时候,一块一块地往车上装肯定是很低效的。我们可以使用一个小推车,先把砖装到小推车上,再把这小推车推到车前,把砖装到车上。这个例子中,小推车可以视为缓冲区,小推车的存在,减少了我们装车次数,从而提高了效率
缓冲流,也叫高效流,是对4个基本的FileXxx 流的增强,所以也是4个流,按照数据类型分类:
字节缓冲流:BufferedInputStream(创建一个 新的缓冲输入流),BufferedOutputStream(创建一个新的缓冲输出流。)
字符缓冲流:BufferedReader,BufferedWriter
缓冲流的基本原理,是在创建流对象时,会创建一个内置的默认大小的缓冲区数组,通过缓冲区读写,减少系统IO次数,从而提高读写的效率。
2. 字节缓冲流构造方法
// 创建字节缓冲输入流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("bis.txt"));
// 创建字节缓冲输出流
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("bos.txt"));
3. 字符缓冲流构造方法
// 创建字符缓冲输入流
BufferedReader br = new BufferedReader(new FileReader("br.txt"));
// 创建字符缓冲输出流
BufferedWriter bw = new BufferedWriter(new FileWriter("bw.txt"));
字符流特有方法。
BufferedReader:public String readLine(): 读一行文字。
BufferedWriter:public void newLine(): 写一行行分隔符,由系统属性定义符号。
缓冲流总体代码:
package com.gec.ioex;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import org.junit.Test;
public class CopyJdkDemo4 {
@Test
public void test() {
File srcFile = new File("D:\\JAVASE\\day15\\jdk-8u191-windows-x64.exe");
File desFile = new File("D:\\JAVASE\\day15\\b.exe");
File desFile2 = new File("D:\\JAVASE\\day15\\c.exe");
//时间戳
long start = System.currentTimeMillis();
//使用
copyFile(srcFile,desFile);
//copyFile2(srcFile,desFile2);
//缓冲流copy
long end = System.currentTimeMillis();
System.out.println("copy jdk使用时间 :" + (end -start) + "毫秒");
}
private void copyFile2(File srcFile, File desFile) {
try(
InputStream is = new FileInputStream(srcFile);
//带缓冲的输入流
BufferedInputStream bis = new BufferedInputStream(is);
OutputStream os = new FileOutputStream(desFile);
//带缓冲的输出流
BufferedOutputStream bos = new BufferedOutputStream(os)
) {
int len = 0;
byte[] b = new byte[1024];
while((len = bis.read(b)) >0) {
bos.write(b, 0, len);
}
bis.close();
} catch (Exception e) {
e.printStackTrace();
}
}
private void copyFile(File srcFile, File desFile) {
try(
InputStream is = new FileInputStream(srcFile);
OutputStream os = new FileOutputStream(desFile)
) {
int len = 0;
byte[] b = new byte[1024];
while((len = is.read(b)) >0) {
os.write(b, 0, len);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
写入和读取对象
写入
读取