Java基础之IO流

时间:2022-10-29 21:36:24

I/O流

  • 字符流: Reader、Writer(文字、字符串等)
  • 字节流:InputStream、OutputStream(歌曲、图片等)

file.write("xxx")只是将内容存在了缓冲区,flush()后可立即写入。close()关闭文件,隐式执行了flush。执行了close()后不可再write和flush。

FileWriter fw = new FileWriter("abc.txt",true);后面的参数为true表示可以再在原文件的基础上续写而不覆盖原文件。

打开/新建文件时必须处理IOException,可以在方法头上家throws IOException,也可以使用try catch处理,就是比较麻烦。

FileWriter

public static void main(String[] args) {
FileWriter fw = null; // 先初始化为空,不然会提醒没有处理异常
try {
fw = new FileWriter("abc.txt");
fw.write("123");
fw.write("456");
} catch (IOException e) {
e.printStackTrace();
// finally文件关闭里面也要try、catch
// 最好进行一次文件是否为空的判断
} finally {
try {
if (fw != null)
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

FileReader

  • read()每次读一个字符,若到结尾返回-1。
int ch;
while ((ch=fr.read()) != -1) {
System.out.print((char)ch); // ASCII码到字符的强转
}
  • 一次读一个数组,顺便演示了续写,和一次写入一个数组
public static void main(String[] args) throws IOException {
char[] buff = new char[1024];
int len;
FileReader fr = new FileReader("abc.txt");
FileWriter fw = new FileWriter("abc.txt", true); // 参数true,可以续写而不覆盖
while ((len=fr.read(buff)) != -1) {
System.out.println(new String(buff, 0 ,len));
fw.write(buff, 0 ,len);
}
fw.close(); // 关流顺序:先开的后关,后开的先关,可以只关处理流(如BufferedReader、BufferedWriter)就不用关节点流了(如FileReader、FileWriter)
fr.close();
}

BufferedReader&BufferedWriter

像FileWriter和FileReader这类被称为节点流。而BufferedWriter和BufferedReader被称为处理流。它们对节点流进行了装饰,使得其功能更加灵活。

缓冲读写,提高效率。Filewriter每次write都是直接操作硬盘,FileReader同理。而BufferedWriter的write是先写入自己的缓冲区,待缓冲区满了之后再刷新到硬盘。

BufferedReader有一个readLine(),BufferedWriter有一个newLine根据系统自动选择换行,用法如下。

public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new FileReader("abc.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("def.txt"));
String line; // readLine返回的是String
while ((line=br.readLine()) !=null) {
System.out.println(line);
bw.write(line);
bw.newLine();
}
bw.close();
br.close();
}

以上体现了装饰模式,与继承相比更加灵活,装饰者与被装饰者的关系不是父子关系,而更像是同事,或者继承自同一个父类。

下面是一个装饰的例子。

public class NewPerson {
private Person p;
// 传入的是Person
public NewPerson(Person p) {
this.p = p;
}
public void eat() {
System.out.println("I am New Person");
p.eat(); // 注意继承的话这里是super.eat()
}
public static void main(String[] args) {
NewPerson b = new NewPerson(new Person("Bob")); // 装饰
b.eat();
}
}

class Person {
private String name;

Person(String name) {
this.name = name;
}

public void eat() {
System.out.println("I am eating...");
}

public String getName() {
return name;
}

}

下面再用继承的思想,和上面比较就能看出不同。

public class NewPerson extends Person {
private int age;
// 这里传入的参数和Person类似,而不是直接传Person
public NewPerson(String name, int age) {
super(name); // 在构造函数中super必须放在第一行
this.age = age;
}
// 继承过来的方法需要覆盖,而装饰不存在继承关系
@Override
public void eat() {
System.out.println("I am NewPerson");
super.eat();
System.out.println("I am "+age+"years old.");
}

@Override
public String getName() {
return super.getName();
}
public int getAge() {return age;}

public static void main(String[] args) {
NewPerson a = new NewPerson("Bob", 34);
a.eat();
}
}

class Person {
private String name;

Person(String name) {
this.name = name;
}

public void eat() {
System.out.println("I am eating...");
}

public String getName() {
return name;
}

}

以上方法针对的是字符输入输出,若要针对字节则用FileOutpurStream(写)、FileInputStream(读)、BufferedInputStream(带缓冲的读)、BufferedOutputStream(带缓冲的写)。使用方法大同小异。

InputStream

InputStream input = System.in从键盘读取,System.in只有一个,不用关闭,一旦关闭不可再打开。

char和int之间可以直接比较,自动提升类型。

public static void main(String[] args) throws IOException {
int ch = 'a';
char abc = 97;
System.out.println(ch); // 打印97
System.out.println(abc); // 打印a
}

InputStreamReader&OutputStreamWriter

以上两个是字节 <--> 字符的桥梁,举个例子。

InputStreamReader isr = new InputStreamReader(System.in);将传入的System.in进行修饰,本来从硬盘读取到的是字节,现在装饰后变成字符,而且再经BufferedReader装饰后,可以读取一整行。如下可实现一次从键盘输入中读取一行。

BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

同理OutputStreamWriter是将字符转为字节,方便打印到控制台(控制台接收的是字节)。

// 从键盘读并打印到控制台
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
String line = br.readLine();
bw.write(line);
bw.flush(); // 因为只是写入了缓冲区,必须刷新才能在控制台看到,系统的标准输出,不用关闭
}

FileReader的父类就是InputStreamReader,所以从硬盘读到的字节已经被转为字符。FileWriter的父类就是OutputStreamWriter,所以写入的是字符,被转成字节存到硬盘。

OutputStreamWriter osw = new OutputStream(new FileOutputStream("aa.txt"));
FileWriter fw = new FileWriter("aa.txt");
// 以上两句等价的

什么时候用OutputStreamWriter

以指定的码表读写,不能用FileReader和FileWriter,这两个默认用的UTF-8。要是指定了其他码表,就要用OutPutStreamWriter和InputStreamReader了。什么时候用?

  • 需要从字符转为字节(或反之)时
  • 需要指定码表时

小结

流那么多,该怎么快速选择我们需要用到的,有几个方法可以参考。

  1. 明确源和目的
    • 源:InputStream和Reader
    • 目的:OutputStream和Writer
  2. 是否是纯文本
    • 源(是):Reader (否):InputStream
    • 目的(是):Writer (否):OutputStream
  3. 明确设备
    • (源)硬盘 --> File
    • (源)控制台 --> System.in
    • (源)内存 --> 数组
    • (源)网络 --> Socket流
    • (目的)硬盘 --> File
    • (目的)控制台 --> System.out
    • (目的)内存 --> 数组
    • (目的)网络 --> Socket流
  4. 是否需要高效
    • 是:加上装饰,Buffered缓冲

File

该类时文件和目录的抽象表示。

其构造函数可以传两个字符串,一个是父路径,一个是子路径。也可第一个传File,第二个传字符串。

File f = new File("C:\\");
File file1 = new File("c:\\", "Windows"); // 该对象是个目录
File file2 = new File(f, "aa.txt"); // 该对象是个文件

一些常用的方法:

  • delete() 删除该File
  • exists()该File是否已经存在
  • createNewFile()该File不存在时,创建之
  • getAbsoluteFile()返回File,绝对路径
  • getAbsolutePath()返回String,绝对路径
  • getName()返回 路径末尾的目录或者文件
  • mkdir()只能创建单一的目录如F:\\ABC
  • mkdirs()可创建多层目录如F:\\ABC\\DEF
  • list()返回String[],该目录下所有子目录和文件的列表
  • listFiles()返回File[],该目录下所有子目录和文件的列表
  • isFile()、isDir()、isHidden()是否是文件、目录、是否被隐藏

FilenameFilter文件过滤,测试指定文件是否应该包含在某一文件列表中。需要覆盖accept方法。

通常写法如下,该例子是测试文件是否以某个特定的关键词结尾。

public class FilterByKey implements FilenameFilter {
private String key;

FilterByKey(String key) {
this.key = key;
}

@Override
public boolean accept(File dir, String name) {
return name.endsWith(key);
}
}

Map-Hashtable-Properties和I/O相关联,不支持泛型,通常存储以key-value对应的配置文件。

  • getProperty(String key) 根据键获取值。
  • setProperty**(String key, String value) 相当于put(key, value)
  • list(System.out)可以将属性列表输出。
  • store()可以存储到文件

PrintWriter&PrintStream

字符/字节打印流,print()方法和write()方法有区别,就是print方法可以输出原始数据。

PrintWriter pw = new PrintWriter(System.out);
pw.println(98); // 打印98,保持了原始数据
pw.write(98); // 打印b,ASCII码表
pw.flush();

操作对象流ObjectOutputStream和ObjectInputStream可以将对象存入硬盘,或者从硬盘读取对象,可以时间对象的永久化。

new ObjecOutputStream(new FileOutputStream),所写入的类必须实现Serialiable接口。


by @sunhiayu

2017.1.10