欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java:IO

程序员文章站 2024-03-04 21:58:00
...

 

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();
		}
	}
}

程序运行结果:Java:IO

改路径是否是文件:true
该文件大小:0
该文件上次修改时间:Fri Aug 10 16:32:31 CST 2018

 

可以看到,运行之后,在定义路径创建了一个文件“123.txt”。这里稍微解释一下:File.separator,separator是File类的一个静态属性,用它来得到当前环境下路径的分隔符。

二、字节流与字符流

File类不支持对文件的处理,如果要处理文件内容,必须通过流的操作来完成。“流”分为输入流和输出流,在java.io包中,流分为字节流与字符流。

下面用两张图来形象的说说字节流与字符流:

Java:IO

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();
	}
}

程序运行结果:

Java:IO

***注意:在进行文件输出的时候,所有的文件将自动帮助用户创建,不再需要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();
	}
}

程序运行结果:

Java:IO

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]

完!