1.IO——字节流
把数据的传输,可以看作是一种数据的流动,以内存为基准,分为输入流和输出流。输入流,即从其他设备(如硬盘)上读取到内存中的流。输出流,即从数据从内存写出到其他设备(如硬盘)上的流。根据数据类型,又可以分为字节流和字符流。
tips:1个非ASCII码字符(>127)=2个字节;1个字节=8个二进制位
IO流的四个顶级父类
1.1 字节输出流
/*
java.io.OutputStream 字节输出流
此抽象类是表示输出字节流的所有类的超类。
定义了一些子类共性的成员方法:
- public void close():关闭输出流并释放与此流相关的任何系统资源。
- pubic void flush():刷新此输出流并强制任何缓冲的输出字节被写出。
- public void write(byte[] b):将b.length字节从指定的字节数组写入此输出流。
- public void write(byte[] b, int off, int len):从指定的字节数组写入len字节,从偏移量off开始输出到此输出流。
- public abstaract void write(int b):将指定的字节流输出流。
java.io.FileoutputStream extends OutputStream
FileOutputStream:文件字节输出流
作用:把内存中的数据写入到硬盘的文件中
构造方法:
FileOutputStream(String name)创建一个向具有指定名称的文件中写入数据的输出文件流。
FileOutputStream(File file) 创建一个向指定File对象表示的文件中,写入数据的文件输出流。
参数:写入数据的目的地
String name:目的地是一个文件的路径;
File file:目的地是一个文件
构造方法的作用:
1.创建一个FileOutputStream对象
2.会根据构造方法中传递的文件/文件路径,创建一个空的文件;
3.会把FileOutputStream对象指向创建好的文件
*/
写如数据的原理(内存–>硬盘):java程序–>JVM(java虚拟机)–>OS–>OS调用写数据的方法–>把数据写入到文件中
字节输出流的使用步骤(重点):
1)创建一个FileOutputStream对象,构造方法中传递写入数据的目的地;
2)调用FileOutputStream对象中的方法write,把数据写入到文件中;
3)释放资源(流使用会占用一定的内存,使用完毕把内存清空,提升程序效率)
public class Demo {
public static void main(String[] args) {
FileOutputStream fos = new FileOutputStream("C:\\a.txt");
fos.write(97);
//写入时,会把10进制转化为二进制,再写入硬盘
fos.close();
}
}
/*
注意,任意的文本编辑器,打开文件时,都会查询编码表,把字节转换为字符表示,0-127,会查询ASCII表;其他值,则查询系统默认码表(中文系统中式GBK)
所以上面写入后,打开文件看到的是a字符。
*/
下面是两个一次写多个字节的方法:
- public void write(byte[] b): 将b.length字节从指定的字节数组写入此输出流
注意,如果写的第一个字节是正数(0-127),那么显示时会查询ASCII表;若是负数,则第一个字节和第二个会组成一个中文显示,查询系统默认码表(GBK或UTF-8);
GBK中一个中文是两个字节;UTF-8中一个中文是三个字节。
- public void write(byte[] b, int off, int len):从指定的字节数组写入len字节,从偏移量off开始,输出到此输出流。即把字节数组的一部分写入到文件中,off是数组开始的索引,len是写几个字节。
public class Demo {
public static void main(String[] args) {
FileOutputStream fos = new FileOutputStream(new File("C:\\a.txt"));
fos.write(49);
fos.write(48);
fos.write(48);//在文件中显示100
byte[] bytes = {-65,-66,-67,68,69}; //烤央E
fos.write(bytes);
/*
写入字符的方法:可以用String类中的方法把字符串,转化为字节数组;byte[] getBytes(),把字符串转换为字节数组。
*/
byte[] bytes2 = "你好".getBytes();
fos.write(bytes);
fos.close();
}
}
如果我们想要往一个文件追加写一些数据,需要用以下两个函数。
/*
追加写/续写:使用两个参数的构造方法
FileOutputStream(String name, boolean append) 创建一个向具有指定name的文件中写入数据的输出文件流。
FileOutputStream(File file, boolean append) 创建一个指定File对象表示的文件中写入数据的文件输出流。
参数:
String name, File file:写入数据的目的地
boolean append:追加写开关
true:创建对象不会覆盖源文件,继续在文件的末尾追加写数据
false:创建一个新文件覆盖源文件
*/
public class Demo {
public static void main(String[] args) throws IOException{
FileOutputStream fos = new FileOutputStream("C:\\a.txt",true);
for(int i=0;i<10;i++){
fos.write("你好".getBytes());
fos.write("\r\n".getBytes());
/*
换行符号:
windows: \r\n
linux: /n
mac: /r
*/
}
}
}
1.2 字节输入流
java.io.InputStream,是字节输入流。此抽象类是表示字节输入流的所有类的超类。它定义的子类共性方法主要有下面三个:
int read() 从输入流中读取数据的下一个字节
int read(byte[] b) 从输入流中读取一定数量的字节,并将其存储在缓冲区数组b中
void close() 关闭此输入流并释放资源
InputStream的其中一个实现类是FileInputStream
java.io.FileInputStream extends InputStream
FileInputStream:文件字节输入流
作用:把硬盘文件中的数据,读取到内存中使用
构造方法:
FileInputStream(String name)
FileInputStream(File file)
参数:读取文件的数据源
String name:文件的路径
File file:文件
构造方法的作用:
1.会创建一个FileInputStream对象;
2.会把FileInputStream对象指定的构造方法中尧都区的文件
读取数据原理:java程序–>JVM–>OS–>OS读取数据的方法–>读取文件
字节输入流的使用步骤(重点):
1)创建FileInputStream对象,构造方法中绑定要读取的数据源;
2)使用FileInputStream对象中的read方法,读取文件
3)释放资源;
FileInputStream fis = new FileInputStream("C:\\a.txt");
int len = fis.read();
System.out.println(len);//读取的是内容的二进制数据转成十进制的数据
//若读取完毕,即读到结束标记,则返回的是-1。
int len = 0 ;//记录读取到的字节
while(len = fis.read())!=-1) {// 布尔表达式
System.out.print((char)len);//将读到的数据(二进制转为十进制)再转成char
}
字节流中一次读取多个字节的方法
int read(byte[] b) 从输入流中读取一定数量的字节,并将其存储再缓冲区b中。
FileInputStream fis = new FileInputStream("C:\\a.txt");
byte[] bytes = new byte[2];
int len = fis.read(bytes); //起到缓冲作用,存储读取到的多个字节。
System.out.println(len);//每次读取的有效字节个数,若已经读到最后,没有读到新的数据,就会返回-1
System.out.printlnt(new String(bytes));//若读到最后,那么读到的字节将不会变化了。每次新读取的字节,会依次从下标0开始覆盖bytes中的对应值。
若要读取文件中的所有数据,分批次读取,代码如下:
byte[] bytes = new byte[1024];
int len = 0;
while((len = fis.read(bytes))!=-1) {
//String(byte[] bytes, int offset, int length) 把字节数组的一部分转换为字符串,offset为数组的开始索引。
System.out.println(new String(bytes, 0 ,len));
}
fis.close();
1.3字节流输入输出综合案例——文件的复制
文件复制的步骤:
1)创建一个字节输入流,构造方法中绑定要读取的数据流;
2)创建一个字节输出流,构造方法中绑定要写入的目的地
3)使用字节输入流对象中的read读取文件;
4)使用字节输出流的方法write,把读取到的字节写入到目的地的文件中;
5)释放资源;
FileInputStream fis = new FileInputStream("C:\\a.jpg);
FileOutputStream fos = new FileOutputStream("D:\\a.jpg);
byte[] bytes = new byte[1024];
int len = 0;
while((len = fis.read(bytes))!=-1) {
fos.write(bytes,0,len);//这里也可以用fis.read()和fos.write(len),但是由于每个字节都要循环一次,循环次数过多导致程序效率低下。
}
fos.close();//肯定是先读完,再写完,所以先关闭写的fos
fis.close();
2.IO——字符流
当使用字节流读取文本文件时,遇到中文字符时,可能不会显示完整的字符,因为一个中文字符可能占用多个字节存储。字符流就是以字符为单位读写的数据,专门用于处理文本文件。一个中文文字,GBK编码占用两个字节,UTF-8编码占用3个字节。
字符流一次读取一个字符,这个字符包括英文中文和普通符号。
2.1字符输入流Reader
java.io.Reader字符输入流,时字符输入流的顶层父类,定义了一些共性的成员方法,是一个抽象类。
共性的成员方法有三个:
int read() 读取单个字符并返回;
int read(char[] cbuf) 一次读取多个字符,并将字符读入数组;
void close() 关闭该流并释放与之关联的所有资源;
java.io.FileReader extends InputStreamReader extends Reader
FileReader:文件字符输入流
作用:把硬盘文件中的数据以字符的方式读取到内存中
构造方法:
FileReader(String fileName)
FileReader(File file)
这两个构造方法的作用:1)创建一个FileReader对象;2)会把FileReader对象指向要读取的文件
字符输入流使用的步骤:
1)创建FileReader对象,构造方法中绑定要读取的数据源
2)使用FileReader对象中的方法read读取文件
3)释放资源;
FileReader fr = new FileReader("C:\\a.txt");
int len = 0;
while(len = fr.read()) != -1) {
System.out.println((char)len);
}
/*多个字符读取*/
char[] cs = new char[1024];
int len = 0;
while(len = fr.read(cs))!=-1) {
System.out.println(new String(cs,0,len));
}
fr.close();
2.2 字符输出流Write
java.io.Writer:字符输出流,是所有字符输出流的最顶层的父类,是一个抽象类。
共性成员方法:
- void write(int c) 写入单个字符
- void write(char[] cbuf) 写入字符数组
- abstact void write(char[] cbuf, int off, int len) 写入字符数组的某一部分,off数组的开始索引,
- void write(String str)写入字符串
- void write(String str,int off, int len)写入字符串的某一部分,off字符串的开始索引
- void flush()刷新该流的缓冲
- void close() 关闭此流,但要先刷新它
java.io.FileWriter extends OutputStreamWriter extends Writer
FileWriter 文件字符输出流
作用:把内存中字符数据写入到文件中
构造方法:
FileWriter(File file)
FileWriter(String fileName)
作用:
1.创建一个FileWriter对象;
2.会根据构造方法中传递的文件/文件的路径,创建文件
3.会把FileWriter对象指向创建好的文件。
字符输出流的使用步骤:
1)创建FileWriter对象,构造方法中绑定要写入数据的目的地;
2)使用FileWriter中的方法write,把数据写入到内存缓冲区中(字符转换为字节的过程)
3)使用FileWriter中的方法flush,把内存缓冲区的数据,刷新到文件中;
4)释放资源(会把内存缓冲区的数据刷新到文件中)
FileWriter fw = new FileWriter("C:\\a.txt");
fw.write(97);
fw.flush();//这时才会把数据刷新到文件中去。
flush和close方法的区别:
flush:刷新缓冲区,流对象可以继续使用;
close:先刷新缓冲区,然后通知系统释放资源,流对象不可继续使用
下面是字符输出流写数据的其他方法
FileWriter fw = new FileWriter("C:\\a.txt");
char[] cs = {'a','b','c'};
fw.write(cs)
fw.write(cs,1,3);
fw.write("string字符串");
fw.write("ssffaa",2,3);
下面是字符输出流的续写和换行
FileWriter(String fileName, boolean append)
FileWriter(File file, boolean append)
参数:
append 续写开关,false的话则会创建新的文件覆盖源文件
换行的话 window下是”\r\n"
2.3 IO中的异常处理
在jdk7之前使用try catch finally处理流中异常,比较繁琐和麻烦。
//为了提高fw的作用域,让finally可以使用,因此在trycatch外创建FileWriter,并赋值为空
FileWriter fw = null;
try{
//可能出现异常的代码
fw = new FileWriter("C:\\a.txt",true);
for(int i=0;i<2;i++) {
fw.write("HellowWorld+"\r\n");
}
} catch(IOException e) {
//异常的处理逻辑
System.out.println(e);
}finally {
//一定会执行的代码
//若创建对象失败了,fw的默认值似乎null,因此调用clos方法就会抛出空指针异常,因此需要判断
if(fw!=null) {
try {
fw.close()
} catch(IOException e) {
e.printStackTrace();
}
}
}
在JDK7后,在try的后边可以增加一个(),在括号中可以创建流对象,那么这个流对象的作用域就在try中有效了;同时,在try中代码执行完毕后,会自动把流对象释放,就不用写finally了。格式如下:
try(定义流对象;定义流对象){
可能会产生异常的代码
} catch(异常类变量 变量名) {
异常的处理逻辑
}
因此上述代码可以简化为
而在JDK9新特性,try的前边可以定义流对象,在try后的()中可以直接引入流对象的名称,在try代码执行完毕后,流对象也可以直接释放掉,不用写finally。
public statci void main(String[] args) throws FileNotFoundException {
FileInputStream fis = new FileInputStream("c:\\a.txt");
FileOutputStream fos = new FileOutputStream("d:\\a.txt");
try(fis;fos) {
int len = 0;
while((len = fis.read())!=-1) {
fos.write(len);
}
} catch (IOException e) {
System.out.println(e);
}
}
3.属性集
Properties类表示了一个持久的数据集,Properties可以保存在流中或从流中加载,属性列表中每个键以及其对饮都是一个字符串。
/*
java.util.Properties 集合 extends Hashtable<k,v> implements Map<k,v>
Properties类表示了一个持久的属性集,Properties可保存在流中,或从流中加载。
Properties集合是一个唯一和IO流相结合的集合
可以使用Properties集合中的方法store,把集合中的临时数据,持久化写入硬盘中存储
可以使用Properties集合中的方法load,把硬盘中保存的文件(键值对),读取到集合中使用。
属性列表中每个键以及对应值都是一个字符串。
Properties集合是一个双列集合,key和value默认都是字符串。
*/
/*
Properties集合存储数据,遍历取出Properties集合中的数据
Properties常用方法:
Object setProperty(string key, String value)调用Hashtable的方法put
String getProperty(String key)通过key找到value值,此方法相当于Map集合中的get(key)方法
Set<String> stringPropertyNames()返回此属性列表中的键集,其中该键以及对应值是字符串,相当于Map集合中keySet方法。
*/
private static void show01() {
Properties prop = new Properties();
Prop.setProperty("lucy","174");
Prop.setProperty("Nancy","165");
Set<String> set = Prop.StringPropertyNames();
for(String key:set) {
String value = Prop.getProperty(key);
}
}
/*
可以使用Properties集合中的方法store,把集合中的临时数据,持久化写入硬盘中存储
void store(OutputStream out, String comments)
void store(Writer writer, String comments)
参数:
OutputStream out:字节流输出,不能写入中文;
Writer writer:字符输出流,可以写中文
String comments:注释,用来解释说明保存的文件是做什么的,不能用中文,因为默认Unicode编码,一般使用空字符串
使用步骤:
1.创建Properties集合对象添加数据;
2.创建字节输出流/字符输出流对象,构造方法中绑定要输出的目的地
3.使用Properties集合中的方法store,把集合中临时数据,持久化写入硬盘存储
4.释放资源
*/
Properties prop = new Properties();
Prop.setProperty("lucy","174");
Prop.setProperty("Nancy","165");
FileWriter fw = new FileWriter("C:\\a.txt");
prop.store(fw,"save data");//如果这里fw用匿名类来写,就不需要close,因为匿名类用完自动结束和释放
fw.close();
/*
可以使用Properties集合中的方法load,把硬盘中保存的文件(键值对),读取到集合中使用。
void load(InputStream inStream)//字节输入流,不能读取中文
void load(Reader reader)//字节输入流,可以读取中文
使用步骤:
1.创建Properties集合对象;
2.使用Properties集合对象中的方法Load读取保存键值对文件
3.遍历Properties集合
注意:
1.存储键值对的文件值,键域值默认的连接符号可以使用=,空格(其他符号)
2.存储键值对的文件中,可以使用#进行注释,被注释的不会被读取;
3.存储键值对的文件中,值和键都是字符串,不用再加引号
*/
Properties prop = new Properties();
prop.load(new FileReader("C:\\a.txt"); #为了中英文都能读,一般都用字符流
Set<String> set = prop.stringPropertyName();
for (String key : set) {
String value = prop.getProperty(key);
System.out.println(key+"="+value);
}
3. 缓冲流
缓冲流能够更加高效的读写,缓冲流的基本原理,就是在创建流对象时,会创建一个内置的默认大小的缓冲区数组,通过缓冲区读写,减少IO次数,从而提高读写效率。
3.1字节缓冲流
java.io.BufferedOutputStream extends OutputStream
//字节缓冲输出流
使用方法:
1.创建FileOutputStream对象,构造方法中绑定要输出的目的地;
2.创建BufferedOutputStream对象,构造方法中传递FileOutputStream对象,提高FileOutputStream对象效率;
3.使用BufferedOutputStream对象中的方法write,把数据写入到缓冲区
4.使用BufferedOutputStream对象中的方法flush,把内部缓冲区中的数据,刷新到文件中;
5.释放资源(会调用flush方法先刷新数据)
FileOutputStream fos = new FileOutputStream("D:\\a.txt");
BufferedOutputStream bos = new BufferedOutputStream(fos);
bos.write("把数据写入到内部缓冲区中".getBytes());
bos.flush()
bos.close()
java.io.BufferedInputStream extends InputStream
字节缓冲输入流
构造方法
BufferedInputStream(InputStream in)
使用步骤:
1.创建FileInputStream对象,构造方法中绑定要读取的数据源;
2.创建BufferedInputStream对象,构造方法中传递FileInputStream对象,提高FileInputStream对象的读取效率
3.使用BufferedInputStream对象中的方法read,读取文件
4.释放资源
FileInputStream fis = new FileInputStream("D:\\a.txt");
BufferedInputStream bis = new BufferedInputStream(fis);
/*
int len = 0;
while(len = bis.read())!=-1) {
System.out.println(len);
}
*/
byte[] bytes = new byte[1024];
int len = 0;
while(len = bis.read(bytes))!=-1) {
System.out.println(new String(bytes,0,len));
}
bis.close();//同时也会把fis关闭
经过测试,缓冲流的效率要比普通流高很多,不论普通流是否有用缓冲区bytes进行一次读写多个。
3.2 字符缓冲流
java.io.BufferedWriter extends Writer
字符缓冲输出流
BufferedWriter(Writer out) 创建一个默认大小输出缓冲区的缓冲字符输出流。
特有成员方法
void newLine() 根据不同操作系统写一个行分隔符
使用步骤:
1.创建字符缓冲输出流对象,构造方法中传递字符输出流;
2.调用字符缓冲输出流中的方法write,把数据写入到内存缓冲区中
3.调用字符缓冲输出流中的方法flush,把内存缓冲区中数据刷新到文件中;
4.释放资源
java.io.BufferedReader extends Reader
字符缓冲输入流
构造方法:
BufferedReader(Reader in)
特有成员方法:
String readLine() 读取一行文本
行的终止符号:通过下列字符之一,即可认为某行终止:换行('\n')、回车('\r')或者回车后直接跟换行(\r\n)
返回值:包含该行内容的字符串,不包含任何终止符,如果已到达流末尾,返回null。
使用步骤:
1.创建字符缓冲输入流对象,构造方法中传递字符输出流。
2.使用字符缓冲输入流对象的方法read/readline读取文本;
3.释放资源
3.3 综合实例——对文本的内容进行排序
步骤:
1.创建一个HashMap集合对象,key:存储每行文本序号;value:存储每行的文本
2.创建字符缓冲输入流对象,构造方法中绑定字符输如流;
3.创建字符缓冲输出流对象,构造方法中绑定字符输出流;
4.使用字符缓冲输入流中的方法readline,逐行读取文本;
5.对读取到的文本进行切割,获取行中的序号和文本内容;
6.把切割好的序号和文本的内容存储到HashMap集合中(key序号是有序的,会自动排序1.2.3.4);
7.遍历hashMap集合,获取每一个键值对
8.把每一个键值对,拼接为一个文本行;
9.把拼接好的文本,使用字符流缓冲输出流中的方法write,写入到文件中
10.释放资源;
//读到一行文本后,文本切割
String[] arr = line.split("\\.");
map.put(arr[0],arr[1]);
//遍历HashMap集合,获取键值对
for(String key : map.keySet()) {
String value = map.get(key);
line = key + "." +value;
bw.write(line);
bw.newLine();//换行
}