1.什么是IO流
在工作中,经常会操作磁盘上的资源,这个过程中实现了数据的输入和输出操作,磁盘上的文件和内存之间进行交互,数据的交互需要有一个媒介或者管道,把这个媒介或者管道就称为IO流,也被称为输入输出流【I:Input O:Output】
输入:从外部存储设备到内存叫输入|读取
输出:从内存到外部存储设备叫输出|写入
2.流的作用和原理
流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总成或抽象。即数据在两设备键的传输称为流,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。
3.IO流的种类
按照流的流向分:
输入流:表示将数据读取到Java程序(内存)中使用的流。
输出流:表示从Java程序(内存)向外输出的流。
根据处理数据单位分:
字节流:一次传输一个字节数据,将数据以字节的形式传输。
字符流:一次性传输一个字符(UTF-8编码1、2或3个字节)数据,将数据以字符的形式传输。
按照功能分:
节点流:可以从或向特点的地方(节点)读写字节数据。
处理流:是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写。
先来一张结构图:
4字节输入流
InputStream类的常用方法
InputStream是所有字节输出流的父类,是抽象类,要用FileInputStream实例化对象,
文件输入流FileInputStream
public class Demo1 {
public static void main(String[] args) throws Exception {
//1.创建流
FileInputStream fis = new FileInputStream("d:gp4.txt");
//2.读取文件
//2.1一次读取一个字节
/*int data = -1;
while((data = fis.read())!=-1){
System.out.print((char) data);
}*/
//2.2一次读取多个
byte[] buf = new byte[1024*4];//4k
/*int len = fis.read(buf);//len是读取数据的长度
int len2 = fis.read(buf);
int len3 = fis.read(buf);
System.out.println("第一次读取的:" + len);
System.out.println("第一次读取的:" + len2);
System.out.println("第一次读取的:" + len3);*/
int len = -1;
while ((len = fis.read(buf))!=-1){
for (int i = 0; i < len; i++) {
System.out.print((char) buf[i]);
}
}
System.out.println();
//3.关闭
fis.close();
//
}
}
注意:创建被操作文件:此文件必须存在,否则读取时,排除文件找不到异常。
当读到最后一个文件的时候,控制台输出-1
5 字节输出流
OutputStream是所有字节输出流的父类,是抽象类,要用FileOutputStream实例化对象,OutputStream常用方法:
public class Demo2 {
public static void main(String[] args) throws IOException {
//1.创建输出流
FileOutputStream fos = new FileOutputStream("d:\\info.txt",true);
//2.写入
//fos.write(91);
String content = "helloword\r\n";
fos.write(content.getBytes());//将字符串的值转为byte类型的;
fos.flush();
//关闭
fos.close();
System.out.println("写入完毕");
}
}
注意:当向文件中写入数据时,若文件不存在,程序会自动创建。
字节输入流可以不刷新直接关闭,字节输出流关闭前要刷新,把内存中的数据传入硬盘。
只能写入byte类型
6 字符输出流
Reader:是所有字符输入流的父类,为一个抽象类,不能实例化对象,使用它的子类FileReader类
Reader—>InputStreamReader—>FileReader
public class Demo3 {
public static void main(String[] args) throws Exception {
//1.创建流
FileReader reader = new FileReader("d:\\info.txt");
//2.读取
//2.1一次读取一个字符
/* int data = reader.read();*/
int data = -1;
while ((data = reader.read()) !=-1){
System.out.print((char)data);
}
//3.关闭
reader.close();
}
}
FileReader创建的时候需要传入一个File对象。
FileReader的reader可以一次读取一个char数组。
当读到最后一个字符后再读一次返回-1;
7 字符输出流
Writer :是所有字符输出流的父类,为一个抽象类,不能实例化,使用它的子类FIleWriter
public class Demo5 {
public static void main(String[] args) throws Exception {
//1.创建字符输出流
FileWriter fileWriter = new FileWriter("d:\\info.txt");
//2.写入
for (int i = 0; i < 10; i++) {
fileWriter.write("好好学习,天天向上\r\n");
fileWriter.flush();
}
//3.关闭
fileWriter.close();
System.out.println("执行完毕");
}
}
注意:当文件不存在的时候,系统会自动创建一个文件。
写入的时候可以直接写入字符串
记得关闭前要刷新。
使用字节复制字符串
public class CopyDemo1 {
public static void main(String[] args) throws Exception {
//1.创建字节输入,输出流
FileInputStream fis = new FileInputStream("copy.png");
FileOutputStream fos = new FileOutputStream("copy2.png");
//2.读取和写入
byte[] buf = new byte[1024*4];
int len = -1;
while ((len = fis.read(buf)) !=-1){
fos.write(buf,0,len);
}
fis.close();
fos.close();
}
}
使用字符流复制数组
public class Demo4 {
public static void main(String[] args) throws Exception {
FileReader fr = new FileReader("12.txt");
FileWriter fw = new FileWriter("121.txt");
char[] c = new char[1024*4];
int l = -1;
while ((l = fr.read(c))!=-1){
fw.write(c);
fw.flush();
}
fr.close();
fw.close();
}
}
如果复制的文件是文本文件,用字节流和字符流都可以。
如果复制的文件是图片、音乐、电影,用字符流辅助会出现乱码,要用字节流复制。
补充:utf-8的BOM头
练习:递归显示所有文件夹中的所有的文件,包括子文件夹中的内容和递归删除文件夹。
public class Demo1 {
public static void main(String[] args) {
File dir=new File("d:\\");
// listDir(dir,0);
deleteDir(dir);
}
// 1 递归显示文件夹中的所有的文件,包括子文件夹中内容
public static void listDir(File dir,int level) {
System.out.println(getSeparator(level)+dir);
level++;
File[] listFiles = dir.listFiles();
if(listFiles!=null&&listFiles.length>0) {
for (File file : listFiles) {
if(file.isDirectory()) {//如果是文件夹,递归调用
listDir(file,level);
}else {
System.out.println(getSeparator(level)+file);
}
}
}
}
public static String getSeparator(int level) {
StringBuilder sb=new StringBuilder();
for(int i=0;i<level;i++) {
sb.append(" ");
}
return sb.toString();
}
//2 递归删除文件夹
public static void deleteDir(File dir) {
//1先删除里面的内容
File[] listFiles = dir.listFiles();
if(listFiles!=null&&listFiles.length>0) {
for (File file : listFiles) {
if(file.isDirectory()) {
deleteDir(file);
}else {
//删除文件
System.out.println(file+"-------"+file.delete());
}
}
}
//删除文件夹
System.out.println(dir+"-------"+dir.delete());
}
}
8 转换流
InputStreamReader
作用:
a.实现字节流到字符流的转换
b.解决中文乱码的问题
中文编码
gb2312 (采用两个字节保存字符汉字,英文数字一个字节)
GBK (采用两个字节保存字符汉字,英文数字一个字节)
GB18030 (英文数字都是一个字节,中文是两个或四个字节)
Unicode字符集(包含每个国家的所有字符)国际通用
unicode编码 使用两个字节—65536个字符,浪费空间
为了节省空间使用转码形式
utf-8 使用 1 、2、3个字节 (EF BB BF 记事本添加的BOM(Byte Order Mark)头,编码的标记)
utf-16 使用两个字节—65536个字符 (FF FE 小端(尾) FE FF 大端(尾))
utf-32 使用4个字节
台湾 big5
ANSI:在简体中文Windows操作系统中, ANSI 编码代表 GBK 编码
只有转换流才能指定读取和写入的字符集
InputStreamReader:字节字符转换输入流,将字节输入流转换为字符输入流
代码实现:
public class InputStreamReaderDemo {
public static void main(String[] args) throws IOException {
//1.实例化File的对象
//File file = new File("file/input1.txt");
//2.实例化转换输入流的对象
//注意:当一个流的存在的意义是为了实例化另外一个流,则这个流不需要手动进行关闭
//InputStream input = new FileInputStream(file);
//InputStreamReader reader = new InputStreamReader(input);
//使用默认的字符集【GBK】进行实例化转换流
//InputStreamReader reader = new InputStreamReader(new FileInputStream(new File("file/input1.txt")));
//使用指定字符集进行实例化转换流
//字符集一般使用字符串直接传参,不区分大小写,但是,如果字符集书写有误的话,则会抛出java.io.UnsupportedEncodingException
InputStreamReader reader = new InputStreamReader(new FileInputStream(new File("file/input1.txt")),"UTF-8");
//3.读取
char[] arr = new char[16];
int len = 0;
while((len = reader.read(arr)) != -1) {
String string = new String(arr, 0, len);
System.out.println(string);
}
reader.close();
}
}
OutputStreamWriter
OutputStreamWriter:字符转换输出流,将内存中的字符转成字节保存到硬盘中。
public class OutputStreamWriterDemo {
public static void main(String[] args) throws IOException {
//需求:将一段文本以utf-8的格式写入到文件中【注,文件格式为默认格式】
//1.实例化FIle对象
//注意:对于所有的输出流而言,文件可以不存在,在进行写入的过程中可以自动进行创建
//但是,对于所有的输入流而言,文件必须先存在,然后才能操作,否则,会抛出FileNotFounedException
File file = new File("file/output1.txt");
//2.实例化转换输出流
//如果不想覆盖源文件中的内容时,则在传参的时候,设置一个参数为true
OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(file,true), "utf-8");
//3.写入
writer.write("家客户放假啊刚回家");
//4.刷新
writer.flush();
//5.关闭
writer.close();
}
}
9 缓冲流
作用:主要是为了增强基础流的功能而存在的,提高了流的工作效率【读写效率】
缓冲区可以重复使用。
默认缓冲区大小8192,8k
使用newLine可以实现跨平台换行
注意:如果使用记事本创建的文件,文件是utf-8或者unicode编码,文件的前面有一个BOM(Byte Order Mark)头,BOM作用指定文件使用的编码类型。GBK编码没有添加bom头。
utf-8:EF BB BF
unicode 小端: FF FE 66 00
unicode 大端 :FE FF 00 66
BufferedInputStream类
public class BufferedInputStreamDemo {
public static void main(String[] args) throws IOException {
//实例化一个File对象
File file = new File("file/test22.txt");
//实例化一个缓冲字节输入流的对象
BufferedInputStream input = new BufferedInputStream(new FileInputStream(file));
/*
//读取
byte[] arr = new byte[1024];
int len = 0;
while((len = input.read(arr)) != -1) {
String string = new String(arr, 0, len);
}
*/
byte[] arr = new byte[4];
int len = input.read(arr);
String string = new String(arr, 0, len);
System.out.println(string);
input.mark(66);
len = input.read(arr);
string = new String(arr, 0, len);
System.out.println(string);
// 实现了效果:覆水可收
input.reset();
len = input.read(arr);
string = new String(arr, 0, len);
System.out.println(string);
input.close();
}
}
BufferedOutputStream类
public class BufferedOutputStreamDemo {
public static void main(String[] args) throws IOException {
//实例化FIle对象
File file = new File("test33.txt");
//实例化换种字节输出流
BufferedOutputStream output = new BufferedOutputStream(new FileOutputStream(file));
//写
output.write("你好的halle".getBytes());
//刷新
output.flush();
//关闭
output.close();
}
}
BufferedReader类
public class BufferedReaderDemo {
public static void main(String[] args) throws IOException {
//实例化FIle对象
File file = new File("test33.txt");
//实例化缓冲字符流的对象
BufferedReader reader = new BufferedReader(new FileReader(file));
//方式一:read循环读取
/*
//读取
char[] arr = new char[8];
int len = 0;
while((len = reader.read(arr)) != -1) {
String string = new String(arr, 0, len);
}
*/
//方式二:readLine循环读取
/*
String result1 = reader.readLine();
System.out.println(result1);
String result2 = reader.readLine();
System.out.println(result2);
*/
String result = "";
while((result = reader.readLine()) != null) {
System.out.println("第一行:" + result);
}
reader.close();
}
}
BufferedWriter类
public class BufferedWriterDemo {
public static void main(String[] args) throws IOException {
// 实例化FIle对象
File file = new File("test33.txt");
//实例化缓冲字符输出流
BufferedWriter writer = new BufferedWriter(new FileWriter(file,true));
// 写
writer.write("今天天气还可以");
// 作用:主要就是为了换行
writer.newLine();
// 刷新
writer.flush();
//关闭
writer.close();
}
}
10 内存流
案例:完成一个字母大小写转换的程序
public class TextDemo02 {
public static void main(String[] args) throws IOException {
//定义一个字符串,全部由大写字母组成
String string = "HELLOWORLD";
//内存输入流
//向内存中输出内容,注意:跟文件读取不一样,不设置文件路径
ByteArrayInputStream bis = new ByteArrayInputStream(string.getBytes());
//内存输出流
//准备从内存中读取内容,注意:跟文件读取不一样,不设置文件路径
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int temp = 0;
//read()方法每次只读取一个字符
while((temp = bis.read()) != -1) {
//将读取的数字转为字符
char c = (char)temp;
//将字符变为大写
bos.write(Character.toLowerCase(c));
}
//循环结束之后,所有的数据都在ByteArrayOutputStream中
//取出内容,将缓冲区内容转换为字符串
String newString = bos.toString();
//关闭流
bis.close();
bos.close();
System.out.println(newString);
}
}
注意:内存操作流的操作对象,一定是以内存为主准,不要以硬盘为准。
11 标准输入输出流
Java的标准输入/输出分别通过System.in和System.out实现,默认情况下分别代表是键盘和显示器
PrintStream类:PrintStream为其他输出流添加了功能,使它们能够方便地打印各种数据值表示形式。
PrintWriter类:向文本输出流打印对象的格式化表示形式。此类实现在 PrintStream中的所有 print方法。它不包含用于写入原始字节的方法。
public static void main(String[] args)throws Exception {
//1创建PrintStream
//PrintStream ps=new PrintStream("d:\\print.txt");
PrintWriter pw=new PrintWriter("d:\\print.txt");
//2打印
pw.println(true);
pw.println(3.14);
pw.println(100);
pw.println("我爱北京");
//3刷新
pw.flush();
//4关闭
pw.close();
}
public class PrintStreamDemo {
public static void main(String[] args) throws FileNotFoundException {
//System.out.println("hello world");
//创建打印流的对象
//注意:默认打印到控制台,但是,如果采用setOut方法进行重定向之后,将输出到指定的文件中
PrintStream print = new PrintStream(new FileOutputStream(new File("test33.txt")));
/*
* static void setErr(PrintStream err)
重新分配“标准”错误输出流。
static void setIn(InputStream in)
重新分配“标准”输入流。
static void setOut(PrintStream out)
重新分配“标准”输出流。
* */
//将标准输出重定向到print的输出流
System.setOut(print);
System.out.println("hello world");
}
}
public class InputStreamDemo {
public static void main(String[] args) throws FileNotFoundException {
FileInputStream inputStream = new FileInputStream(new File("test33.txt"));
//setIn
System.setIn(inputStream);
//System.out.println("请输入内容:");
//默认情况下是从控制台进行获取内容
//但是如果使用setIn方法设置了重定向之后,将从指定文件中获取内容
Scanner sc = new Scanner(System.in);
String string = sc.next();
System.out.println(string);
}
}
12 对象流
流中流动的数据是对象
将一个对象写入到本地文件中,被称为对象的序列化
将一个本地文件中的对象读取出来,被称为对象的反序列化
使用对象流
ObjectInputStream: 对象输出流
ObjectOutputStream:对象输入流
注意:
序列化对象的类型必须实现Serializable接口。否则不能序列化。
系统根据系统的序列化ID判断是否是同一个类
如果向将多个对象序列化到本地,可以借助于集合,【思路:将多个对象添加到集合中,将集合的对象写入到本地文件中,再次读出来,获取到的仍然是集合对象,遍历集合】。
对象中那些字段可以不序列化:
1 transient 修饰的字段
2 静态的字段
在要序列化类中添加字段,保证序列化和反序列化是同一个类
可以定义对象的序列值,这样对象如果发生改变,还能输出该对象,不过新的值是默认值
private static final long serialVersionUID = 100L;
public class ObjectStreamDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
//objectOutputStreamUsage();
objectInputStreamUsage();
}
// 写:将对象进行序列化
public static void objectOutputStreamUsage() {
//1.实例化一个Person的对象
Person person = new Person("张三", 10, 'B');
//2.实例化一个对象输出流的对象
ObjectOutputStream output = null;
try {
output = new ObjectOutputStream(new FileOutputStream(new File("file/person.txt")));
//3.将对象写入到流中
output.writeObject(person);
//4.刷新
output.flush();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
finally {
try {
output.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
// 读:反序列化
public static void objectInputStreamUsage() {
//1.实例化对象输入流的对象
try {
ObjectInputStream input = new ObjectInputStream(new FileInputStream(new File("file/person.txt")));
//2.读取
Object object = input.readObject();
//3.对象的向下转型
if(object instanceof Person) {
Person p = (Person)object;
System.out.println(p);
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
注意:在使用对象流的时候,用于初始化对象流的参数只能是字节流(将对象转换为二进制的形式,然后再把二进制写入文件)
13 RandomAccessFile类
RandomAccessFile是用来访问那些保存数据记录的文件的,你就可以用seek( )方法来访问记录,并进行读写了。这些记录的大小不必相同;但是其大小和位置必须是可知的。但是该类仅限于操作文件。
seek设置读取指针的偏移量,每次都是从0开始
skipBytes(10)跳过10个字节。从当前位置开始跳。
public class TextDemo01 {
public static void main(String[] args) throws Exception {
RandomAccessFile file = new RandomAccessFile("file.txt", "rw");
// 以下向file文件中写数据
file.writeInt(20);// 占4个字节
file.writeDouble(8.236598);// 占8个字节
//这个长度写在当前文件指针的前两个字节处,可用readShort()读取
file.writeUTF("这是一个UTF字符串");
file.writeBoolean(true);// 占1个字节
file.writeShort(395);// 占2个字节
file.writeLong(2325451l);// 占8个字节
file.writeUTF("又是一个UTF字符串");
file.writeFloat(35.5f);// 占4个字节
file.writeChar('a');// 占2个字节
//把文件指针位置设置到文件起始处
file.seek(0);
// 以下从file文件中读数据,要注意文件指针的位置
System.out.println("——————从file文件指定位置读数据——————");
System.out.println(file.readInt());
System.out.println(file.readDouble());
System.out.println(file.readUTF());
//将文件指针跳过3个字节,本例中即跳过了一个boolean值和short值。
file.skipBytes(3);
System.out.println(file.readLong());
//跳过文件中“又是一个UTF字符串”所占字节
//注意readShort()方法会移动文件指针,所以不用写2。
file.skipBytes(file.readShort());
System.out.println(file.readFloat());
// 以下演示文件复制操作
System.out.println("——————文件复制(从file到fileCopy)——————");
file.seek(0);
RandomAccessFile fileCopy = new RandomAccessFile("fileCopy.txt", "rw");
int len = (int) file.length();// 取得文件长度(字节数)
byte[] b = new byte[len];
//全部读取
file.readFully(b);
fileCopy.write(b);
System.out.println("复制完成!");
}
}
14 Properties类
是Map接口的一个实现类,并且是Hashtable的子类
Properties集合中元素也是以键值对的形式存在的
Properties特点:
1 存储属性名和属性值
2 属性名和属性值都是字符串
3 和流有关系
4 没有泛型
public class PropertiesDemo {
public static void main(String[] args) {
//1.实例化一个Properties的对象
Properties pro = new Properties();
System.out.println(pro);
//2.把文件userlist.properties中的键值对同步到集合中
//实质:读取
/**
* void load(InputStream inStream)
从输入流中读取属性列表(键和元素对)。
*/
try {
pro.load(new BufferedInputStream(new FileInputStream(new File("file/userlist.properties"))));
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(pro);
//3.向集合中添加一对键值对
/*
* Object setProperty(String key, String value)
调用 Hashtable 的方法 put。
* */
pro.setProperty("address", "china");
System.out.println(pro);
try {
//4.store
//实质:写入
//comments:工作日志
pro.store(new BufferedOutputStream(new FileOutputStream(new File("file/userlist.properties"))), "add a pair of key and value");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
总结
1 转换流
InputStreamReader 字节通向字符的桥梁—》指定编码
中文编码 gb2313–>gbk—>gb18030 1 2个字节 4个字节
国际 unicode编码表 包括了世界所有的国家的语言
utf-8 编码 1 2 3 字节
utf-16 编码 unicode 2个字节
utf-32 编码 4个字节
繁体big5
OutputStreamWriter 字符流通向字节流的桥梁---》指定编码
2 缓冲流:增强基础流的功能,缓冲区提高读写效率
BufferedInputStream
BufferedOutputStream
BufferedReader---> readLine();读取一行
BufferedWriter---> newLine();//写入行终止符 \r\n \n
3 内存流 : 内存操作的数据就在内存
ByteArrayInputStream
ByteArrayOutputStream
4 对象流:
序列化: 把内存中的对象转换成二进制数据的过程。
反序列化: 把二进制数据转成对象的过程。
ObjectInputStream -->readObject();
ObjectOutputStream–>writeObject();
Serializable:可序列化 标记接口
不能序列化成员
transient 修饰的成员
静态成员
版本号:保证序列化和反序列化的类是同一个类。
serialVersionUID
5 标准输入输出
System.out PrintStream 写到控制台
System.in InputStream 从控制台读取
重定向标准输出流
System.setOut(new PrintStream("d:\\print.txt"));
System.setIn(new FiletInputStream("d:\\aaa.txt"));
6 RandomAccessFile 随机访问文件,既可以读取,也可以写入
writeInt(); //写整数
writeUTF();//写字符串
writeDouble();//写浮点数
writeBoolean();//写入boolean
readInt();
readUTF();
readDobule();
readBollean();
seek();//从头开始设置偏移量
skipBytes(20);//从当前位置,向后调20个字节
7 Properties集合
1 存储属性名,属性值
2 存储String类型
3 和流有关系
4 没有泛型
setProperty(key,value);
getProperty(key);
stringPropertyNames();//获取的属性名的集合set
//和有关的方法
list(System.out);
load(); //从硬盘加载属性文件