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了。什么时候用?
- 需要从字符转为字节(或反之)时
- 需要指定码表时
小结
流那么多,该怎么快速选择我们需要用到的,有几个方法可以参考。
- 明确源和目的
- 源:InputStream和Reader
- 目的:OutputStream和Writer
- 是否是纯文本
- 源(是):Reader (否):InputStream
- 目的(是):Writer (否):OutputStream
- 明确设备
- (源)硬盘 --> File
- (源)控制台 --> System.in
- (源)内存 --> 数组
- (源)网络 --> Socket流
- (目的)硬盘 --> File
- (目的)控制台 --> System.out
- (目的)内存 --> 数组
- (目的)网络 --> Socket流
- 是否需要高效
- 是:加上装饰,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