【字节流、字符类、IO异常处理、属性集】
IO概述
什么是IO
我们把数据的传输,看作是一种数据的流动,按照流动的方向,以内存为基准,分为输入Input
和输出Output
,即流向内存为输入流,流出内存为输出流。
Java中I/O操作主要是指使用java.io
包下内容,进行输入、输出的操作。输入也叫做读取数据,输出也叫做写入数据。
IO的分类
根据数据的流向分为:输入流和输出流。
- 输入流:把数据从其他设备上读取到内存中的流;
- 输出流:把数据从内存上写入到其他设备上的流。
根据数据的类型分为:字节流和字符流。
- 字节流:以字节为单位,读写数据的流;
- 字符流:以字符为单位,读写数据的流。
顶级父类们
输入流 | 输出流 | |
---|---|---|
字节流 | 字节输入流 InputStream | 字节输出流 OutputStream |
字符流 | 字符输入流 Reader | 字符输出流 Writer |
字节流
一切皆为字节
一切文件数据(文本、图片、视频等)在存储时,都是以二进制数字的形式保存的一个一个的字节。传输时一样如此。
所以,字节流可以传输任意文件数据,在操作流的时候,我们要时刻明确,无论使用什么样的流对象,底层传输的始终为二进制数据 。
字节输出流【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) | 将指定的字节写入字节流 |
FileOutputStream
java.io.FileOutputStream extends OutputStream
,也叫文件字节输出流。
作用:把内存中的数据写入到硬盘的文件中。
构造方法
构造方法 | 含义 |
---|---|
FileOutputStream(String name) | 创建一个向具有指定name的文件中写入数据的文件输出流 |
FileOutputStream(File file) | 创建一个向指定File对象来表示的文件中写入数据的文件输出流 |
FileOutputStream(String name,boolean append) | 创建一个向具有指定name的文件中追加写入数据的输出文件流 |
FileOutputStream(File file,boolean append) | 创建一个向指定File对象来表示的文件中追加写入数据的文件输出流 |
写出字节数据
字节输出流的使用步骤【重点】:
- 创建一个FileOutputStream对象,构造方法中传递写入数据的目的地;
- 调用FileOutputStream对象中的方法write,把数据写入到文件中;
- 释放资源(流使用会占用一定的内存,使用完毕要把内存清空,提高程序的效率)。
write(int b)
方法,每次可以写出一个字节数据,代码使用演示:
public class Demo01OutputStream {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("C:\\Users\\Ann\\Desktop\\a.txt");
fos.write(97);
fos.close();
}
}
注意:
任意的文本编辑器(记事本、Notepad++等),在打开文件的时候,都会查询编码表,把字节转换为字符表示:
0~127:会查询ASCII表(97–>a)
其他值:查询系统默认码表(中文系统会查询GBK码表)
一次写出多个字节的方法:write(byte[] b)
、write(byte[] b,int off,int len)
,代码使用演示:
import java.io.FileOutputStream;
import java.io.IOException;
public class Demo01OutputStream {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("C:\\Users\\Ann\\Desktop\\a.txt");
fos.write(97);//a
//写入数字100
byte[] intBytes = {49, 48, 48};
fos.write(intBytes);//100
//写入字符串”你好”
String s = "你好";
byte[] bytes = s.getBytes();
fos.write(bytes);//你好
// 链式编程
fos.write(",世界".getBytes());//,世界
byte[] intBytes2 = {65,66,67,68,69};
fos.write(intBytes2,1,2);//写入BC
//释放内存
fos.close();
}
}
注意:
如果写的第一个字节是正数(0-127),那么显示的时候会查询ASCII表
如果写的第一个字节是负数,那么第一个字节和第二个字节会组成一个中文显示,查询系统默认码表(GBK)
数据的追加写和换行写
追加写/续写,使用两个参数的构造方法:FileOutputStream(String name,boolean append)
、FileOutputStream(File file,boolean append)
参数:
String name
\File file
:写入数据的目的地boolean append
:追加写开关- true:创建对象不会覆盖源文件,继续在文件的末尾追加写数据;
- false:创建一个新文件,覆盖源文件。
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
public class Demo01OutputStream {
public static void main(String[] args) throws IOException {
File f1 = new File("C:\\Users\\Ann\\Desktop\\a.txt");
//使用两个参数的构造方法,append参数传入true,在源文件后追加写
FileOutputStream fos = new FileOutputStream(f1, true);
//追加写入字符串:"追加的数据"
fos.write("追加的数据".getBytes());
//换行,追加写入字符串:"追加追加的数据"
fos.write("\r\n换行追加的数据".getBytes());
//释放内存
fos.close();
}
}
不同系统写换行的符号:
- windows:\r\n
- linux:/n
- mac:/r
字节输入流【InputStream】
java.io.InputStream
抽象类是表示字节输入流的所有类的超类。可以读取字节信息到内存中,它定义了字节输入流的基本共性功能方法:
方法 | 含义 |
---|---|
public void close() | 关闭此输入流并释放与此流相关联的任何系统资源 |
public abstract int read() | 从输入流读取数据的下一个字节 |
public int read(byte[] b) | 从输入流中读取一些字节数,并将它们存储到字节数组b中 |
FileInputStream
java.io.FileInputStream extends InputStream
:也叫字节输入流。
作用:把硬盘文件中的数据,读取到内存中使用。
构造方法
构造方法 | 含义 |
---|---|
FileInputStream(String name) | 通过打开一个到实际文件的链接来创建一个FileInputStream,该文件通过文件系统中的路径名name指定 |
FileInputStream(File file) | 通过打开一个到实际文件的连接来创建一个FileInputStream,该文件通过文件系统中的File对象file指定 |
读取字节数据
字节输入流的使用步骤【重点】:
- 创建FileInputStream对象,构造方法中指定要读取的数据源;
- 使用FileInputStream对象中的方法read,读取文件;
- 释放资源。
public abstract int read()
,从输入流读取数据的下一个字节,代码使用演示:
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class Demo01InputStream {
public static void main(String[] args) throws IOException {
//创建FileInputStream对象,构造方法中指定要读取的数据源
File f1 = new File("C:\\Users\\Ann\\Desktop\\a.txt");
FileInputStream fis = new FileInputStream(f1);
//使用FileInputStream对象中的方法read,读取文件;
//int read()读取文件中的一个字节并返回,读取到文件的结尾返回-1
int len = fis.read();
System.out.println(len);
len = fis.read();
System.out.println(len);
/*
返现以上读取文件是一个重复的过程,所以可以使用循环优化,
不知道文件中有多少字节,使用while循环
while循环结束调教:读取到-1的时候结束
*/
while (len != -1) {
len = fis.read();
if (len != -1) {
System.out.print((char) len);
}
}
//释放资源
fis.close();
}
}
public int read(byte[] b)
,从输入流中读取一些字节数,并将它们存储到字节数组b中。
明确两件事情:
- 方法的参数byte[]的作用?
- 起到缓冲的作用,存储每次读取到的多个字节
- 数组的长度一般定义为1024(1KB)或者1024的整数倍
- 方法的返回值int是什么?
- 每次读取的有效字节个数
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class Demo01InputStream {
public static void main(String[] args) throws IOException {
//创建FileInputStream对象,构造方法中指定要读取的数据源
File f1 = new File("C:\\Users\\Ann\\Desktop\\a.txt");
FileInputStream fis = new FileInputStream(f1);
//使用FileInputStream对象中的方法read读取文件
//int read(byte[] b) 从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中
byte[] bytes = new byte[1024];
/*
发现以上读取是一个重复的过程,可以使用循环优化
不知道文件中有多少字节,所以使用while循环
while循环结束的条件,方法的返回值len为-1
*/
int len = 0;
while ((len = fis.read(bytes)) != -1) {
//使用String类的构造方法String(byte[] bytes,int off,int len)
//将byte数组中的数据转换为字符串,off为0,len为read方法的返回值len,可消除空格
System.out.print(new String(bytes,0,len));
}
//释放内存
fis.close();
}
}
练习:文件复制
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Scanner;
public class FileCopy {
public static void main(String[] args) throws IOException {
//获取要复制的文件名
Scanner input = new Scanner(System.in);
System.out.println("请输入要复制的文件的路径:");
//C:\Users\Ann\Desktop\Details about ling's wedding photos layouts.docx
String s = input.nextLine();
FileInputStream fis = new FileInputStream(s);
byte[] bytes = new byte[1024];
FileOutputStream fos = new FileOutputStream("C:\\Users\\Ann\\Desktop\\a.docx", true);
int len = 0;
while ((len = fis.read(bytes)) != -1) {
fos.write(bytes, 0, len);
}
//释放内存(先关写的,再关读的)
fos.close();
fis.close();
}
}
字符流
当使用字节流读取文本文件时,可能会有一个小问题:遇到中文字符时,可能不会显示完整的字符,就是因为一个中文字符可能占有多个字节数据。所以Java提供了一些字符流类,以字符为单位读写数据,专门用于处理文本文件。
字符输入流【Reader】
java.io.Reader
:字符输入流,是字符输入流的最顶层的父类,定义了一些共性的方法,是一个抽象类。
其共性的成员方法有:
成员方法 | 含义 |
---|---|
int read() | 读取单个字符并返回 |
int read(char[] cbuf) | 一次读取多个字符,将字符读入数组 |
void close() | 关闭该流并释放与之关联的所有资源 |
FileReader
java.io.FileReader extends InputStreamReader extends Reader
:也叫文件字符输入流。
作用:把硬盘文件中的数据以字符的方式读取到内存中。
构造方法
构造方法 | 含义 |
---|---|
FileReader(String fileName) | 创建一个FileReader对象,将该对象指向要读取的路径对应的文件 |
FileReader(File file) | 创建一个FileReader对象,将该对象指向要读取的文件 |
读取字符数据
字符输入流的使用步骤【重点】:
- 创建FileReader对象,构造方法中绑定要读取的数据源;
- 使用FileReader对象中的方法read读取文件;
- 释放资源。
int read()
,从输入流读取数据的下一个字符,代码使用演示:
import java.io.FileReader;
import java.io.IOException;
public class Demo02FileReader {
public static void main(String[] args) throws IOException {
FileReader fs = new FileReader("C:\\Users\\Ann\\Desktop\\a.txt");
//int read() 读取单个字符并返回
int len = 0;
while((len = fs.read())!= -1){
System.out.print((char)len);
}
fs.close();
}
}
int read(char[] cbuf)
:一次读取多个字符,将字符读入数组,代码演示如下:
import java.io.FileReader;
import java.io.IOException;
public class Demo02FileReader {
public static void main(String[] args) throws IOException {
FileReader fs = new FileReader("C:\\Users\\Ann\\Desktop\\a.txt");
//int read(char[] cbuf)一次读取多个字符,将字符读入数组,返回获取到的有效字符个数
char[] chars = new char[1024];
int len = 0;
while((len = fs.read(chars))!=-1){
System.out.println(new String(chars,0,len));
}
fs.close();
}
}
字符输出流【Writer】
java.io.Writer
:字符输出流,是字符输出流的最顶层的父类,定义了一些共性的方法,是一个抽象类。
其共性的成员方法有:
方法 | 含义 |
---|---|
void write(int c) | 写入单个字符 |
void write(char[] cnuf) | 写入字符数组 |
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() | 关闭此流,但要先刷新它 |
FileWriter
java.io.FileWriter extends OutputStreamWriter extends Writer
:也叫文件字符输出流。
作用:把内存中的字符数据写入到文件中。
构造方法
构造方法 | 含义 |
---|---|
FileWriter(File file) | 根据给定的File对象构造一个FileWriter对象 |
FileWriter(String fileName) | 根据给定的文件名构造一个FileWriter对象 |
FileWriter(File file,boolean append) | 创建一个向指定File对象来表示的文件中追加写入数据的文件输出流 |
FileWriter(String fileName,boolean append) | 创建一个向具有指定name的文件中追加写入数据的输出文件流 |
基本写出数据
字符输出流的使用步骤【重点】:
- 创建一个FileWriter对象,构造方法中绑定要写入数据的目的地
- 使用FileWriter对象的方法write,把数据写入到内存缓冲区中(字符转换为字节的过程)
- 使用FileWriter中的方法flush,把内存缓冲区中的数据,刷新到文件中;
- 释放资源(会先把内存缓冲区中的数据刷新到文件中)。
void write(int c)
,写入单个字符,代码演示如下:
import java.io.FileWriter;
import java.io.IOException;
public class Demo02FileWriter {
public static void main(String[] args) throws IOException {
FileWriter fw = new FileWriter("C:/Users/Ann/Desktop/a.txt",true);
fw.write('看');
fw.write('什');
fw.write('么');
fw.flush();
fw.close();
}
}
关闭和刷新
因为内置缓冲区的原因,如果不关闭输出流,无法写出字符到文件中。但是关闭的流对象,是无法继续写出数据的。如果我们迹象写出数据,又想通知系统使用流,就需要使用flush方法了。
flush
:刷新缓冲区,流对象可以继续使用;close
:先刷新缓冲区,然后通知系统释放资源,流对象不可以再被使用。
写出其他数据
对于字符输出流写数据的其他方法:write(char[] cbuf)
、write(char[] cbuf,int off,int len)
、write(String str)
、write(String str,int off,int len)
,代码演示如下:
import java.io.FileWriter;
import java.io.IOException;
public class Demo02FileWriter {
public static void main(String[] args) throws IOException {
FileWriter fw = new FileWriter("C:/Users/Ann/Desktop/a.txt");
char[] chars = "黑马程序员".toCharArray();
fw.write(chars);
fw.write(chars,2,2);
fw.flush();
fw.write("换行\r\n换行");
fw.write("黑马程序员",2,2);
fw.close();
}
}
字符输出流的续写和换行
同上字节输出流的追加和换行。
import java.io.FileWriter;
import java.io.IOException;
public class Demo02FileWriter {
public static void main(String[] args) throws IOException {
FileWriter fw = new FileWriter("C:/Users/Ann/Desktop/a.txt",true);
char[] chars = "黑马程序员".toCharArray();
fw.write(chars);
fw.write(chars,2,2);
fw.flush();
fw.write("换行\r\n换行");
fw.write("黑马程序员",2,2);
fw.close();
}
}
IO异常的处理
JDK7前处理
在JDK1.7之前,可以使用try…catch…finally处理流中的异常。
格式:
try{
可能会产生异常的代码
}catch(异常类 变量名){
异常的处理逻辑
}finally{
一定会执行的代码(如,资源释放)
}
import java.io.FileWriter;
import java.io.IOException;
public class Demo03IOException {
public static void main(String[] args) {
FileWriter fw = null;
try {
fw = new FileWriter("a.txt");
fw.write("字符串");
} catch (IOException e) {
System.out.println(e);
} finally {
if (fw != null) {
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
###JDK7和JDK9处理(了解)
JDK7中的新特性:
在try的后面可以增加一个(),在括号中可以定义对象;那么这个流对象的作用域就在try中有效;try中的代码执行完毕,就会自动把对象释放,不用写finally。
格式:
try(定义流对象1;定义流对象2…){
可能会产生异常的代码
}catch(异常类 变量名){
异常的处理逻辑
}
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
public class Demo03IOException {
public static void main(String[] args) {
try(FileWriter fw = new FileWriter("a.txt");
FileInputStream fis = new FileInputStream("a.txt")) {
fw.write("字符串");
fw.flush();
int len = 0;
while((len = fis.read())!= -1){
System.out.println(len);
}
} catch (IOException e) {
System.out.println(e);
}
}
}
JDK9中的新特性:
在try的前面可以定义流对象,在try后面的()中可以引入流对象的名称(变量名),在try代码执行完毕后,流对象也可以释放掉,不用写finally。
格式:
A a = new A();
B b = new B();
try(a;b){
可能会产生异常的代码
}catch(异常类 变量名){
异常的处理逻辑
}
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
public class Demo03IOException {
public static void main(String[] args) throws IOException {
FileWriter fw = new FileWriter("a.txt");
FileInputStream fis = new FileInputStream("a.txt");
try(fw;fis) {
fw.write("字符串");
fw.flush();
int len = 0;
while((len = fis.read())!= -1){
System.out.println(len);
}
} catch (IOException e) {
System.out.println(e);
}
}
}
属性类
java.util.Properties
继承于Hashtable
,用来表示一个持久的属性集。它使用键值结构存储数据,每个键及其对应值都是一个字符串。该类也被许多Java类使用,比如获取系统属性时,System.getProperties
方法就是返回一个Properties
对象。
Properties
集合是唯一一个和IO流相结合的集合。
- 可以使用
Properties
集合中的方法store,把集合中的临时数据,持久化写入到硬盘中存储; - 可以使用
Properties
集合中的方法load,把硬盘中的保存的文件(键值对),读取到集合中。
Properties集合的基本使用
方法 | 含义 |
---|---|
Object setProperty(String key, String value) | 调用 Hashtable 方法 put |
String getProperty(String key) | 通过key 找到value 值,此方法相当于Map集合中的get(key) 方法 |
Set<String> stringPropertyNames() | 返回此属性列表中的键集合,其中该键及其对应值时字符串,此方法相当于Map集合中的keyset 方法 |
import java.util.Iterator;
import java.util.Properties;
import java.util.Set;
public class Demo04Properties {
public static void main(String[] args) {
show01();
}
public static void show01() {
//创建Properties集合对象
Properties prop = new Properties();
//使用setProperty方法往集合中添加数据
prop.setProperty("赵丽颖", "168");
prop.setProperty("迪丽热巴", "165");
prop.setProperty("古力娜扎", "163");
//使用stringPropertyNames把集合中的键取出,存储到一个Set集合中
Set<String> strings = prop.stringPropertyNames();
//遍历Set集合,取出Properties集合的每一个键
for (String string : strings) {
String property = prop.getProperty(string);
System.out.println(string+"-->"+property);
}
System.out.println("-------------");
Iterator<String> iterator = strings.iterator();
while (iterator.hasNext()) {
String key = iterator.next();
String value = prop.getProperty(key);
System.out.println(key + "-->" + value);
}
}
}
store方法
可以使用Properties集合中的方法store,把集合中的临时数据,持久化地写入到硬盘中存储。
方法 | 参数含义 |
---|---|
void store(OutputStream out, String comments) | OutputStream out :字节输出流,不能写入中文String comments :注释,用来解释说明保存的文件是做什么的,不能使用中文,会产生乱码,默认是Unicode编码,一般使用“”(空字符串) |
void store(Writer writer, String comments) | Writer writer :字符输出流,可以写入中文String comments :注释,用来解释说明保存的文件是做什么的,不能使用中文,会产生乱码,默认是Unicode编码,一般使用“”(空字符串) |
使用步骤:
- 创建Properties集合对象,添加数据;
- 创建字节输出流\字符输出流,构造方法中绑定要输出的目的地;
- 使用Properties集合中的方法store,把集合中的临时数据,持久化的写入到硬盘中;
- 释放资源。
import java.io.FileWriter;
import java.io.IOException;
import java.util.Properties;
public class Demo04Properties {
public static void main(String[] args) throws IOException {
show02();
}
public static void show02() throws IOException {
//创建Properties集合对象
Properties prop = new Properties();
//使用setProperty方法往集合中添加数据
prop.setProperty("赵丽颖", "168");
prop.setProperty("迪丽热巴", "165");
prop.setProperty("古力娜扎", "163");
//创建字节输出流\字符输出流,构造方法中绑定要输出的目的地
FileWriter fw = new FileWriter("C:/Users/Ann/Desktop/c.txt");
//使用Properties集合中的方法store,把集合中的临时数据,持久化的写入到硬盘中
prop.store(fw,"");
//释放资源
fw.close();
}
}
load方法【重点】
可以使用Properties
集合中的方法load,把硬盘中的保存的文件(键值对),读取到集合中。
方法 | 参数含义 |
---|---|
void load(InputStream inStream) | InputStream inStream :字节输入流,不能读取含有中文的键值对 |
void load(Reader reader) | Reader reader :字符输入流,能读取含有中文的键值对 |
使用步骤:
- 创建Properties集合对象;
- 使用Properties集合对象中的方法load读取保存的键值对文件;
- 遍历Properties集合。
注意:
- 存储键值对的文件中,键与值默认的连接符号可以使用=,空格(或其他符号);
- 存储键值对的文件中,可以使用#进行注释,被注释的键值对不会再被读取;
- 存储键值对的文件中,键与值默认都是字符串,不用再加引号。
import java.io.FileReader;
import java.io.IOException;
import java.util.Properties;
import java.util.Set;
public class Demo04Properties {
public static void main(String[] args) throws IOException {
show03();
}
public static void show03() throws IOException {
//创建Properties集合对象
Properties prop = new Properties();
//使用Properties集合对象中的方法load读取保存的键值对文件;
prop.load(new FileReader("C:/Users/Ann/Desktop/c.txt"));
//遍历集合
Set<String> keys = prop.stringPropertyNames();
for (String key : keys) {
String value = prop.getProperty(key);
System.out.println(key+"-->"+value);
}
}
}