概述
流是有起点和终点的有序字节序列
流的分类:
输入流/输出流, 是当前程序为参照点, 程序从外面读数据这是输入流, 把程序的数据保存到外面是输出流
字节流/字符流, 如果是以字节为单位处理流中的数据就是字节流, 如果是以字符为单位处理流中的数据就是字符流
节点流/处理流, 如果直接从设备(数据源)上读写数据就是节点流, 处理流是对节点流的包装
Stream单词结尾就是字节流类, 如果是以Reader结尾就是字符输入流, 以Writer单词结尾就是字符输出流
FileInputStream/FileOutputStream
以字节为单位读写文件内容
FileInputStream
读取文件的步骤
- 在当前程序和指定文件之间建立流通道
- 读取文件内容
- 关闭流通道
两种读取方式:一次读取一个字节,一次读取一个字节数组
三种异常处理方式:抛异常,try catch finally,try catch
实例1:一次读取一个字节,循环读取
public class Test03 {
public static void main(String[] args) throws IOException {
//1)在当前程序与指定的文件之间建立流通道,
//读取d:/abc.txt文件的内容, 通过构造方法指定要访问的文件,如果文件不存在会抛出异常
FileInputStream fis = new FileInputStream("d:/abc.txt");
//文件内容: ABCabc
//2) 读取文件内容,
//read()方法从文件中读取一个字节, 并把读到的字节返回, 读到文件末尾返回-1
int cc = fis.read(); //65, A的码值
while( cc != -1 ){
//把读到的字节cc进行处理, 把cc转换为字符再打印, 因为当前文件中只有英文字符,一个字节就对应一个字符
System.out.print( (char)cc );
//继续读下个字节
cc = fis.read( );
}
//3)关闭流通道
fis.close();
}
实例2:一次读取一个字节数组,读取很多字节保存到字节数组中
public static void main(String[] args) throws IOException {
//1)在程序与读取的文件之间建立流通道
FileInputStream fis = new FileInputStream("d:/abc.txt");
//文件ABCabcABC
byte[] bytes = new byte[4];
//从流中读取很多字节, 保存到字节数组中, 返回读到的字节数,如果读到文件末尾,返回-1
int len = fis.read(bytes);
while( len != -1 ){
//从文件中读取了len个字节保存到了bytes数组中, 对len个字节进行处理
//把读到的len个字节转换为字符串 new String(byte[]bytes , 0 , len)
System.out.print( new String(bytes , 0 , len ));
//继续读
len = fis.read(bytes);
}
fis.close();
}
实例3:处理异常的方式
public static void main(String[] args) {
// m1(); //一次读取一个字节, 手动关闭流, 异常处理
m2(); //从文件中读取字节保存到字节数组中, 异常处理, 自动 关闭流
}
//从JDK7开始, 流可以自动关闭
private static void m2() {
try( //try资源块,自动释放
FileInputStream fis = new FileInputStream("d:/abc.txt");
) {
byte[] bytes = new byte[4];
int len = fis.read(bytes);
while( len != -1){
System.out.print( new String(bytes , 0 ,len));
len = fis.read(bytes);
}
} catch (Exception e) {
e.printStackTrace();
}
}
private static void m1() {
FileInputStream fis = null;
try {
fis = new FileInputStream("d:/abc.txt");
int cc = fis.read();
while( cc != -1 ){
System.out.print( (char)cc );
cc = fis.read();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close(); //关闭流,释放系统资源
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
FileOutputStream
以字节为单位把数据保存到文件步骤
- 建立流通道, 通过构造方法的参数指定要访问的文件
- 保存数据
- 关闭流
可以一次写一个字节,也可以一次写一个字节数组,也可以写字节数组的某部分
public static void main(String[] args) throws IOException {
//1)建立流通道, 通过构造方法的参数指定要访问的文件
//如果访问的文件不存在, 系统会创建一个新文件; v如果文件已存在, 会覆盖文件原来的内容
// FileOutputStream fos = new FileOutputStream("d:/def.txt");
//如果文件不存在就创建文件,如果文件已存在, 把内容保存到文件的后面, 即以追加的方式打开 文件
FileOutputStream fos = new FileOutputStream("d:/def.txt" , true);
//2) 保存数据
//一次写一个字节
fos.write( 65 ); //把65对应的字符A保存到文件中
fos.write( 66 ); //把66对应的字符A保存到文件中
fos.write( 67 ); //把67对应的字符A保存到文件中
//换行, 在Windows操作系统中,换行需要使用\r\n两个字符
fos.write('\r'); //回车, 13
fos.write('\n'); //换行, 10
//一次写一个字节数组
byte[] bytes = "helloworld".getBytes();
fos.write(bytes); //把bytes数组中所有的字节保存到文件中
//换行
fos.write(13);
fos.write(10);
//把字节数组的部分字节保存到文件中
fos.write(bytes, 0, 2);
//3) 关闭流
fos.close();
}
文件的复制
在进行文件复制时,一般都会选择字节数组进行复制,并且字节数组的长度是1024的偶数,因为如果复制的文件较大,以一个字节的方式复制较慢
FileReader/FileWriter
FileReader/FileWriter在读写方式上和FileInputStream/FileOutputStream是一样的套路
FileReader
以字符为单位读写文件内容,只能读写纯文本文件, 并且要求文本文件的编码格式与当前环境编码格式兼容,如果读写的文件都是英文,则不管编码格式是否一致,都可以读取,因为英文字符不管是GBK还是UTF8都是一个字节,
如果文件是GBK格式, 文件中有中文, 如果和当前编码格式不一致,读取时会出现乱码 ,因为汉字在GBK中占两个字节,在UTF-8中占三个字节,一般使用FileReader读取与当前环境编码一致的文件
一次读取一个字符,一次读取一个字符数组
FileWriter
保存数据时,只能把数据保存到和当前编码格式一致的文件中
和FileOutputStream一致:如果文件不存在, 会创建一个新的文件, 如果文件存在会覆盖原来的内容,文件不存在就创建, 文件已存在也可以用追加的方式打开
可以一次写一个字符,也可以一次写一个字符串,也可以一次写一个字符数组,也可以把字符串的一部分保存到文件中
InputStreamReader/OutputStreamWriter
FileReader/FileWriter只能读写与当前环境编码兼容的文本文件
如果文本文件与当前环境编码不兼容, 使用InputStreamReader/OutputStreamWriter转换流读写
InputStreamReader把字节流以指定的编码转换为字符流
OutputStreamWriter可以把字符转换为指定格式的字节流
转换流采用了适配器设计模式
public static void main(String[] args) throws IOException {
// m1(); //使用InputStreamReader读取文件内容
m2(); //使用OutputStreamWriter保存数据
}
//当操作的文件编码与当前环境编码不兼容, 使用OutputStreamWriter把字符以指定的编码转换为字节
private static void m2() throws IOException {
//把字符保存到d:/def.txt文件, 该文件是GBK编码, 当前环境是UTF8编码, 把字符转换为GBK格式再保存
OutputStream out = new FileOutputStream("d:/def.txt", true );
OutputStreamWriter osw = new OutputStreamWriter(out, "GBK");
osw.write("\r\n");
osw.write("当前的内容是使用转换流保存到文件中的, 工作区编码是UTF8, 该文件使用GBK编码 ");
osw.close();
}
//当文本文件的编码与当前环境编码不兼容时, 使用InputStreamReader类读取
private static void m1() throws IOException {
//读取d:/def.txt文件, 该文件使用GBK编码,当前环境使用UTF8编码
//在当前程序与指定的文件之间建立字节流通道
InputStream in = new FileInputStream("d:/def.txt");
//使用GBK编码,把in字节流中的数据转换为字符流
InputStreamReader isr = new InputStreamReader(in, "GBK");
int cc = isr.read();
while( cc != -1){
System.out.print((char)cc);
cc = isr.read();
}
isr.close();
}
BufferedReader/BufferedWriter
public static void main(String[] args) throws IOException {
// m1(); //使用BufferedReader读取文本文件内容
// m2(); //使用BufferedWriter保存文本到文件
m3(); //从键盘上输入文本,把这些文本保存到文件中
}
private static void m3() throws IOException {
BufferedWriter bw = new BufferedWriter(new FileWriter("d:/xyz.txt"));
//使用BufferedReader对键盘输入流缓冲
// System.in是标准的输入设备,即键盘
//因为System.in是InputStream字节流,应该将字节流转换成字符流,然后作为BufferedReader缓冲对象
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line = br.readLine();
while( line.length() > 0 ){
bw.write(line);
bw.newLine();
line = br.readLine();
}
br.close();
bw.close();
}
private static void m2() throws IOException {
Writer out = new FileWriter("d:/abc.txt", true );
BufferedWriter bw = new BufferedWriter(out);
bw.write("我送你99朵玫瑰花儿");
// bw.flush(); //清空缓冲区,把数据保存到文件中
bw.close();
}
private static void m1() throws IOException {
Reader in = new FileReader("d:/test08.java");
BufferedReader br = new BufferedReader(in);
//从缓冲字符流中读取一行,读到文件末尾返回null
String line = br.readLine();
while( line != null ){
System.out.println( line );
line = br.readLine();
}
br.close(); //把包装流关闭后, 被包装的流也会关闭
}
ObjectInputStream/ObjectOutputStream
对象序列化:
把对象转换为01二进制序列就是对象序列化
对象反序列化
把一组01二进制序列转换为对象
注意:
对象序列化/反序列化前提是对象的类要实现Serializable接口,该接口是一个标志性接口,没有任何方法
ObjectOutputStream类 可以把对象序列化,把序列化后二进制保存到文件中
ObjectInputStream类可以从文件读取01序列,把 这组01序列转换为对象(反序列化)
实例:对象序列化
public static void main(String[] args) throws IOException {
Person p1 = new Person("yongge", 36);
//对象序列化, 把p1对象保存到d:/obj.txt文件中
OutputStream out = new FileOutputStream("d:/obj.txt");
ObjectOutputStream oos = new ObjectOutputStream(out);
oos.writeObject(p1);
oos.close();
}
实例:对象反序列化
public static void main(String[] args) throws IOException, ClassNotFoundException {
InputStream in = new FileInputStream("d:/obj.txt");
ObjectInputStream ois = new ObjectInputStream(in);
//从文件中读取一个对象, obj是Object类型的,引用的是Person对象
Object obj = ois.readObject();
System.out.println( obj );
ois.close();
注意的问题 :
场景描述:
把对象序列化到文件后, 又在Person类中添加了一个字段, 再进行反序列化时,产生以下异常:
java.io.InvalidClassException:
com.bjpowernode.chapter06.objectinputoutput.Person; local class incompatible:
stream classdesc serialVersionUID = 6372825075440148724,
local class serialVersionUID = 5707827319367398195
分析:
Person类实现了Serializable接口后,系统会给类自动添加一个serialVersionUID序列化版本号字段
当在Person类添加/删除了一个字段,重新编译,serialVersionUID字段会生成一个新的值
解决方法:
保证 对象序列化时与反序列化时serialVersionUID字段的值要相同
可以手动的添加一个serialVersionUID字段
一般情况下, 类实现了Serializable接口后, 手动的添加一个序列化版本号字段:
private static final long serialVersionUID = 2332956456465L;
PrintStream/PrintWriter
打印字节流/打印字符流
字节打印流实例:
public static void main(String[] args) throws IOException {
//1)
OutputStream out = new FileOutputStream("d:/log.txt", true);
PrintStream pStream = new PrintStream(out);
pStream.print("hello");
pStream.println(" world");
//2)System类的out成员就是PrintStream类型的打印流
//System.out默认系统的标准输出设备是显示器
System.out.println("默认在显示器上打印信息");
//可以修改System.out的打印方向
System.setOut(pStream);
System.out.println("这一行信息就不是在屏幕上打印, 而是打印到pStream流中,即log.txt文件");
//3)经常把异常信息保存到日志文件中
try {
FileInputStream fis = new FileInputStream("f:/asdfsaf.tt");
} catch (Exception e) {
// 在开发时, 一般调用e.printStackTrace()把异常信息打印到屏幕上方便程序员调试
//在部署后, 会把异常信息打印到日志文件中
e.printStackTrace(pStream);
}
pStream.close();
}
字符打印流实例:
public static void main(String[] args) throws IOException {
PrintWriter pw = new PrintWriter(new FileWriter("d:/def.txt"));
pw.print("这是字符打印流");
try {
FileInputStream fis = new FileInputStream("f:/sadf.dsf");
} catch (Exception e) {
e.printStackTrace(pw);
}
pw.close();
}
File类
读取文件内容使用IO流, 操作文件/文件夹使用File类, 如创建/遍历/删除文件夹, 查看文件的相关属性等操作
构造方法:
public static void main(String[] args) throws IOException {
File f1 = new File("d:/java");
f1.mkdir(); //创建文件夹
File f2 = new File("d:/java/sub1");
f2.mkdir();
File f3 = new File("d:/java", "sub2");
f3.createNewFile(); //创建文件
File f4 = new File(f2, "sub3");
f4.createNewFile();
}
查看文件相关属性:
public static void main(String[] args) throws IOException {
// File f1 = new File("d:/hehe.avi");
File f1 = new File("hehe.avi"); //相对路径,在项目目录下的文件
/*
如果在创建文件时构造方法中的内容是相对路径,返回的绝对路径是当前项目的路径 + 相对路径,会去当前项目的路径下判断是否存在,
*/
f1.createNewFile();
System.out.println( f1.getAbsolutePath() ); //返回绝对路径(从根目录开始的路径 )
System.out.println( f1.getPath() ); //返回路径 ,构造方法中写的什么就会返回什么
System.out.println( f1.getParent() ); //返回上一级文件夹,如果不存在则返回null
System.out.println( f1.getName() ); //对象名
System.out.println( f1.length()); //文件大小
System.out.println( f1.exists() ); //是否存在
System.out.println( f1.isFile() ); //是否为文件
System.out.println( f1.isAbsolute() ); //是否绝对路径
System.out.println( f1.lastModified() ); //最后一次修改的时间
对文件夹的操作:
public static void main(String[] args) {
listSub3("e:/Java");
}
//显示指定文件 夹的内容
public static void listSub1(String dirname) {
File dir = new File(dirname);
String[] subs = dir.list();
for (String string : subs) {
System.out.println( string );
}
}
//显示绝对路径
private static void listSub2(String dirname) {
File dir = new File(dirname);
File[] listFiles = dir.listFiles();
for (File file : listFiles) {
System.out.println(file.getAbsolutePath());
}
}
//显示绝对路径 , 包括子文件夹的内容
private static void listSub3(String dirname) {
File dir = new File(dirname);
File[] listFiles = dir.listFiles();
for (File file : listFiles) {
System.out.println(file.getAbsolutePath());
//如果file对象是文件夹, 显示该子文件夹的内容
if (file.isDirectory()) {
listSub3( file.getAbsolutePath() ) ; //递归调用
}
}
}