Java:IO
IO里面主要学习五个类(File、OutputStream、InputStream、Reader、Writer)和一个接口(Serializable)。
一、File类
File类是唯一一个与文件本身操作(创建、删除、取得信息等)有关的程序类。
1、File类的基本操作:
①:实例化File类对象(两种构造方法):
a:public File(String pathname):根据绝对路径来产生File类对象。
b:public File(String parent, String child):相对路径进行创建。
这里解释一下,什么是相对路径。比如说:*广场在中国北京市的北京市东城区东长安街,而我在*的五星红旗的旗台下。那么我告诉别人我的位置就有两种方式:绝对路径:中国北京市的北京市东城区东长安街*广场旗台下;相对路径:我先告诉别人*广场的位置,然后再告诉别人我自己相对于*广场的位置。
②:创建新文件:public boolean createNewFile() throws IOException
③:判断文件是否存在:public boolean exists()
④:删除文件:public boolean delete()
2、File类的目录操作:
①:取得当前路径的上一级目录:public String getParent()
②:取得当前路径的上一级目录的file类对象:public File getParentFile()
③:创建目录(无论有多少级父目录,都会创建):public boolean mkdirs()
3、取得文件信息:
①:判断当前路径是否是文件:public boolean isFile()
②:判断当前路径是否是目录:public boolean isDirectory()
③:取得当前文件大小(单位字节):public long length()
④:取得当前文件最后修改日期:public long lastModified()
下来,我们对上面提到的所有方法综合起来进行应用,看代码:
public class Test{
public static void main(String[] args) throws Exception {
File file = new File("C:"+File.separator+"Users"+File.separator+"Administrator"+File.separator+
"Desktop"+File.separator+"java"+File.separator+"test"+File.separator+"testIO"
+File.separator+"123.txt");
if(!file.exists()) {
file.getParentFile().mkdirs();
file.createNewFile();
System.out.println("改路径是否是文件:"+file.isFile());
System.out.println("该文件大小:"+file.length());
System.out.println("该文件上次修改时间:"+new Date(file.lastModified()));
}else {
file.delete();
}
}
}
程序运行结果:
改路径是否是文件:true
该文件大小:0
该文件上次修改时间:Fri Aug 10 16:32:31 CST 2018
可以看到,运行之后,在定义路径创建了一个文件“123.txt”。这里稍微解释一下:File.separator,separator是File类的一个静态属性,用它来得到当前环境下路径的分隔符。
二、字节流与字符流
File类不支持对文件的处理,如果要处理文件内容,必须通过流的操作来完成。“流”分为输入流和输出流,在java.io包中,流分为字节流与字符流。
下面用两张图来形象的说说字节流与字符流:
字节流与字符流操作的本质区别只有一个:字节流是原生的操作,而字符流是需要经过处理的操作。
不管是字节流还是字符流,其基本的操作流程几乎是一样(都需要下面四个步骤):
a:根据文件路径创建File类对象。
b:根据字节流或字符流的子类实例化父类对象(因为字节流和字符流输入输出流都是抽象类,所以要用其子类实力化父类对象,向上转型来完成)。
c:进行数据的读取或写入操作。
d:关闭流。
下来,我们依次来看看字节流与字符流。
1、字节输出流
这里用到的类是OutputStream类:public abstract class OutputStream implements Closeable, Flushable
OutputStream类实现了Closeable和Flushable两个接口的两个方法:
①:public void close() throws IOException:关闭流的方法。
②:void flush() throws IOException:刷新缓冲区的方法。
在OutputStream类中还定义了其它的方法:
①:public abstract void write(int b) throws IOException:输入单个字节
②:public void write(byte b[]) throws IOException:将给定的字节数组的内容全部输出
③:public void write(byte b[], int off, int len) throws IOException:将所给字节数组的内容部分输出
因为OutputStream类是一个抽象类,所以需要用它的子类(FileOutputStream)为其实例化,我们只需要关心子类的构造方法,通过FileOutputStream类来进行文件的操作:
①:public FileOutputStream(File file) throws FileNotFoundException
②:public FileOutputStream(File file, boolean append)
这两个构造方法有什么区别呢?第一个构造方法在对应路径的文件中进行输出的时候,默认覆盖掉文件内的所有内容;而第二个方法除了接收路径参数外,还有第二个属性,它是boolean类型的:传递“true”在文件原有内容上进行追加输出,而传递“false”时,跟第一种构造方法相同,对文件原有内容进行覆盖。
下面,我们用字节输出流来对目标文件进行输出,看下面代码:
public class Test{
public static void main(String[] args) throws Exception {
File file = new File("C:"+File.separator+"Users"+File.separator+"Administrator"+File.separator+
"Desktop"+File.separator+"java"+File.separator+"test"+File.separator+"testIO"
+File.separator+"123.txt");
OutputStream out = new FileOutputStream(file);
String str = "我爱你中国!";
out.write(str.getBytes());
out.close();
}
}
程序运行结果:
***注意:在进行文件输出的时候,所有的文件将自动帮助用户创建,不再需要createNewFile()进行手工创建。
2、字节输入流
这里用到的类是InputStream类:public abstract class InputStream implements Closeable
与字节输出流相同,InputStream也是一个抽象类,所以,想要进行实例化,只能通过子类(FileInputStream)进行实例化。
InputStream类中提供了read()方法,下面对read()方法的返回值进行说明:
①:返回字节数组的大小
②:返回读取数据的大小
③:返回-1,表示读取完成
下面,我们就对刚刚写到“123.txt”文件中的内容进行读取,看下面代码:
public class Test{
public static void main(String[] args) throws Exception {
File file = new File("C:"+File.separator+"Users"+File.separator+"Administrator"+File.separator+
"Desktop"+File.separator+"java"+File.separator+"test"+File.separator+"testIO"
+File.separator+"123.txt");
InputStream in = new FileInputStream(file);
byte[] data = new byte[1024];
int len = in.read(data);
String str = new String(data, 0, len);
System.out.println(str);
in.close();
}
}
程序运行结果:
我爱你中国!
3、字符输出流
这里用到的是Writer类,Writer类的结构和方法的使用与OutputStream非常类似,只是Writer类提供了直接写入String的方法而已。它也需要子类(FileWriter)为其实例化。
刚才说到,Writer类提供了直接写入String的方法:
public void write(String str) throws IOException
下面使用Writer实现对文件输出:
public class Test{
public static void main(String[] args) throws Exception {
File file = new File("C:"+File.separator+"Users"+File.separator+"Administrator"+File.separator+
"Desktop"+File.separator+"java"+File.separator+"test"+File.separator+"testIO"
+File.separator+"123.txt");
Writer out = new FileWriter(file,true);
out.write("我爱你IT!");
out.close();
}
}
程序运行结果:
4、字符输入流
这里用到的是Reader类,Reader类的结构和方法的使用与InputStream非常类似,它也需要子类(FileReader)为其实例化。
直接看下面的代码:
public class Test{
public static void main(String[] args) throws Exception {
File file = new File("C:"+File.separator+"Users"+File.separator+"Administrator"+File.separator+
"Desktop"+File.separator+"java"+File.separator+"test"+File.separator+"testIO"
+File.separator+"123.txt");
Reader in = new FileReader(file);
char[] arr = new char[1024];
int len = in.read(arr);
String str = new String(arr, 0, len);
System.out.println(str);
in.close();
}
}
程序运行结果:
我爱你中国!
我爱你IT!
5、总结:
字节流与字符流的代码形式上差别不大,但是要是用的话首选字节流操作。因为所有的字符流操作,无论是读入还是输出,数据都先保存在缓存当中。如果字符流不关闭,数据就有可能保存在缓存中,没有输出到目标源。这种情况下,必须强制刷新,才能够得到完整数据。
字节流可以处理一切的文件,而字符流处理文本文件。
三、内存操作流
前面的操作都是针对于文件进行IO处理。除了文件之外,IO操作也可以发生在内存之中,这种流称为内存操作流。文件流的操作里面一定会产生一个文件数据(不管最后这个文件是否被保存)。
如果现在需要进行IO处理,但是又不希望产生文件。这种情况下就可以使用内存作为操作终端。
内存操作流也分为两类:
字节内存流:ByteArrayInputStream ByteArrayOutputStream
字符内存流:CharArrayReader CharArrayWriter
下面我们先用内存流来实现一下大小写转换的的操作:
public class Test{
public static void main(String[] args) throws Exception {
String msg = "hello world";
InputStream input = new ByteArrayInputStream(msg.getBytes());
OutputStream output = new ByteArrayOutputStream();
int temp = 0;
while((temp = input.read()) != -1) {
output.write(Character.toUpperCase(temp));
}
System.out.println(output);
input.close();
output.close();
}
}
这个时候发生了IO操作,但是没有文件产生。
内存操作流的核心就是:将所有OutputStream输出的内容全部保存在了程序里面。
四、打印流:
打印流解决的就是OutputStream设计的缺陷,属于OutputStream功能的增强版。如果操作的不是二进制数据,只是想通过程序向终端目标输出信息的话,OutputStream不是很方便,它的缺点有两个:
①:所有信息必须转换成字节数组。
②:如果要输出的是int、double等类型就不方便了。
1、自己设计一个打印流:
class Print{
private OutputStream output;
public Print(OutputStream output) {
this.output = output;
}
public void print(String str) {
try {
this.output.write(str.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
public void println(String str) {
this.print(str + "\r\n");
}
public void print(int data) {
this.print(String.valueOf(data));
}
public void println(int data) {
this.println(String.valueOf(data));
}
public void print(Double data) {
this.print(String.valueOf(data));
}
public void println(Double data) {
this.println(String.valueOf(data));
}
}
public class Test{
public static void main(String[] args) throws Exception {
Print print = new Print(new FileOutputStream(new File("C:"+File.separator+"Users"+File.separator+"Administrator"+File.separator+
"Desktop"+File.separator+"java"+File.separator+"test"+File.separator+"testIO"
+File.separator+"123.txt")));
print.print("姓名:");
print.println("张三");
print.print("年龄:");
print.println(18);
print.print("职业:");
print.println("程序员");
print.print("工资:");
print.println(3000.25);
}
}
其实打印流的本质就是对OutputStream的功能进行类封装。Java有自己的打印流处理类。
2、系统常见的打印流
PrintStream:字节打印流。
PrintWriter:字符打印流。
打印流的具体使用与上面我们自己实现的打印流用法相同,在这里不再说明。
五、System类对IO的支持
在System类中定义了三个常量:
①:public final static InputStream in:标准输入(显示器)
②:public final static PrintStream out:标准输出(键盘)
③:public final static PrintStream err:错误输出
1、系统输出
由于System.out是PrintStream的实例化对象,PrintStream又是OutputStream的子类,所以可以用System.out直接为OutputStream实例化,这时,OutputStream输出的位置将变为标准输出(显示器)。看下面代码:
public class Test{
public static void main(String[] args) throws IOException {
OutputStream outputStream = System.out;
outputStream.write("hello world".getBytes());
}
}
2、系统输入
System.in对应类型是InputStream,而这种输入流指的是由用户通过键盘进行输入。java本身并没有直接的用户输入处理,如果要实现这种操作,必须使用IO来进行完成。
那么,到底如何使用IO进行完成呢?看下面代码:
public class Test{
public static void main(String[] args) throws IOException {
InputStream in = System.in;
byte[] data = new byte[1024];
System.out.println("请输入信息...");
int temp = in.read(data);
System.out.println(new String(data,0,temp));
}
}
程序运行结果:
请输入信息...
123 //输入数据
123 //系统输出数据
六、两种输入流
1、BufferedReader类:缓冲的输入流,而且是一个字符流的操作对象。在此类中有如下方法(读取一行数据):
public String readLine() throws IOException:这个方法可以读取一行数据,以回车为换行符。看下面代码:
public class Test{
public static void main(String[] args) throws IOException {
BufferedReader buf = new BufferedReader(new InputStreamReader(System.in));
System.out.println("请输入信息:");
String str = buf.readLine();
System.out.println(str);
}
}
程序输出结果:
请输入信息:
hello world //键盘输入信息
hello world //标准输出信息
2、Scanner类
Scanner类可以说是对BufferedReader类进行优化的类,它是一个专门进行输入流处理的程序类,同时也可以结合正则表达式进行各项处理,这个类中有以下几个方法:
①:判断是否有指定类型的数据:public boolean hasNextXxx()
②:取得指定类型的数据:public 数据类型 nextXxx()
③:定义分隔符:public Scanner useDelimiter(Pattern pattern)
④:构造方法:private Scanner(InputStream source)
使用Scanner实现数据输入,看下面代码:
public class Test{
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入数据:");
if(scanner.hasNext()) {
System.out.println(scanner.next());
}
}
}
程序运行结果:
请输入数据:
123abc
123abc
3、总结
从代码简洁性来说(除了二进制文件的拷贝处理外),打印流使用PrintStream,信息输入流用Scanner。
七、序列化
对象序列化是指:将内存中保存的对象变为二进制数据流的形式进行传输,或者是将其保存在文本当中。所有需要被序列化的类必须实现Serializable接口,这个接口只是一个标识,内部没有定义任何方法。JVM识别到you'有此标识时,自动序列化。
1、实现对象序列化:
class Person implements Serializable{
private String name;
private int age;
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
public class Test{
public static final File FILE = new File("C:"+File.separator+"Users"+File.separator+"Administrator"+File.separator+
"Desktop"+File.separator+"java"+File.separator+"test"+File.separator+"testIO"
+File.separator+"123.txt");
public static void main(String[] args) throws Exception {
ser(new Person("张三", 18));
}
public static void ser(Object obj) throws FileNotFoundException, IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(FILE));
oos.writeObject(obj);
oos.close();
}
}
程序运行结果:
sr 鏃ュ父缁冧範.Person9縉?弎} I ageL namet Ljava/lang/String;xp t 寮犱笁
2、实现对象反序列化
在上面代码的Test类加下面方法实现反序列化:
public static void dser() throws Exception{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(FILE));
System.out.println(ois.readObject());
ois.close();
}
程序运行结果:
Person [name=张三, age=18]
3、transient关键字
使用Serializable接口序列化时,会将类中所有属性进行序列化保存,如果希望希望某些属性不被保存,就可以加上transient关键字。
对上面代码的“age”属性加上transient关键字后,重新进行序列化和反序列化:
class Person implements Serializable{
private String name;
private transient int age;
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
public class Test{
public static final File FILE = new File("C:"+File.separator+"Users"+File.separator+"Administrator"+File.separator+
"Desktop"+File.separator+"java"+File.separator+"test"+File.separator+"testIO"
+File.separator+"123.txt");
public static void main(String[] args) throws Exception {
dser();
}
public static void ser(Object obj) throws FileNotFoundException, IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(FILE));
oos.writeObject(obj);
oos.close();
}
public static void dser() throws Exception{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(FILE));
System.out.println(ois.readObject());
ois.close();
}
}
程序输出结果:
Person [name=张三, age=0]
完!