Java语言学习之文件IO

java.io包下主要包括输入,输出两种IO流,每种输入,输出流又可分为字节流和字符流,其中字节流以字节为单位来处理输入,输出操作,
而字符流以字符来处理输入,输出操作。

一.File类
1.访问文件和目录
一旦创建了File对象后,就可以调用File对象的方法来访问,File类提供了很多方法来操作文件和目录。
1>访问文件名相关的方法;
2> 文件检测相关的方法;
3>获取常规文件信息;
4> 文件操作相关的方法;
5> 目录操作相关的方法。

import java.io.*;

public class FileTest
{
	public static void main(String[] args)
		throws IOException
	{
		// 以当前路径来创建一个File对象
		File file = new File(".");
		// 直接获取文件名,输出一点
		System.out.println(file.getName());
		// 获取相对路径的父路径可能出错,下面代码输出null
		System.out.println(file.getParent());
		// 获取绝对路径
		System.out.println(file.getAbsoluteFile());
		// 获取上一级路径
		System.out.println(file.getAbsoluteFile().getParent());
		// 在当前路径下创建一个临时文件
		File tmpFile = File.createTempFile("aaa", ".txt", file);
		// 指定当JVM退出时删除该文件
		tmpFile.deleteOnExit();
		// 以系统当前时间作为新文件名来创建新文件
		File newFile = new File(System.currentTimeMillis() + "");
		System.out.println("newFile对象是否存在:" + newFile.exists());
		// 以指定newFile对象来创建一个文件
		newFile.createNewFile();
		// 以newFile对象来创建一个目录,因为newFile已经存在,
		// 所以下面方法返回false,即无法创建该目录
		newFile.mkdir();
		// 使用list()方法来列出当前路径下的所有文件和路径
		String[] fileList = file.list();
		System.out.println("====当前路径下所有文件和路径如下====");
		for (String fileName : fileList)
		{
			System.out.println(fileName);
		}
		// listRoots()静态方法列出所有的磁盘根路径。
		File[] roots = File.listRoots();
		System.out.println("====系统所有根路径如下====");
		for (File root : roots)
		{
			System.out.println(root);
		}
	}
}

2.文件过滤器

二.理解Java的IO流

1.流的分类
1> 输入流和输出流
Java的输入流主要由InputStream和Reader作为基类,而输出流则主要由OutputStream和Writer作为基类。它们都是一些抽象基类,无法直接创建实例。

2> 字节流和字符流

字节流主要由InputStream和OutputStream作为基类,而字符流则主要由Reader和Writer作为基类。

3> 节点流和处理流

2.流的概念模型
Java的IO流的40多个类都是从如下4个抽象基类派生的。
InputStream/Reader:所有输入流的基类,前者是字节输入流,后者是字符输入流;
OutputStream/Writer:所有输出流的基类,前者是字节输出流,后者是字符输出流。

三.字节流和字符流

1.InputStream和Reader

import java.io.*;

public class FileInputStreamTest
{
	public static void main(String[] args) throws IOException
	{
		// 创建字节输入流
		FileInputStream fis = new FileInputStream(
			"FileInputStreamTest.java");
		// 创建一个长度为1024的“竹筒”
		byte[] bbuf = new byte[1024];
		// 用于保存实际读取的字节数
		int hasRead = 0;
		// 使用循环来重复“取水”过程
		while ((hasRead = fis.read(bbuf)) > 0 )
		{
			// 取出“竹筒”中水滴(字节),将字节数组转换成字符串输入!
			System.out.print(new String(bbuf , 0 , hasRead ));
		}
		// 关闭文件输入流,放在finally块里更安全
		fis.close();
	}
}

java7 改写了所有的IO资源类,它们都实现了AutoCloseable接口,因此可以通过自动关闭资源的try语句类关闭这些IO流。

import java.io.*;

public class FileReaderTest
{
	public static void main(String[] args) throws IOException
	{
		try(
			// 创建字符输入流
			FileReader fr = new FileReader("FileReaderTest.java"))
		{
			// 创建一个长度为32的“竹筒”
			char[] cbuf = new char[32];
			// 用于保存实际读取的字符数
			int hasRead = 0;
			// 使用循环来重复“取水”过程
			while ((hasRead = fr.read(cbuf)) > 0 )
			{
				// 取出“竹筒”中水滴(字符),将字符数组转换成字符串输入!
				System.out.print(new String(cbuf , 0 , hasRead));
			}
		}
		catch (IOException ex)
		{
			ex.printStackTrace();
		}
	}
}

2.OutputStream和Writer

import java.io.*;

public class FileOutputStreamTest
{
	public static void main(String[] args)
	{
		try(
			// 创建字节输入流
			FileInputStream fis = new FileInputStream(
				"FileOutputStreamTest.java");
			// 创建字节输出流
			FileOutputStream fos = new FileOutputStream("newFile.txt"))
		{
			byte[] bbuf = new byte[32];
			int hasRead = 0;
			// 循环从输入流中取出数据
			while ((hasRead = fis.read(bbuf)) > 0 )
			{
				// 每读取一次,即写入文件输出流,读了多少,就写多少。
				fos.write(bbuf , 0 , hasRead);
			}
		}
		catch (IOException ioe)
		{
			ioe.printStackTrace();
		}
	}
}

如果希望直接输出字符串内容,则使用Writer会有更好的效果。

import java.io.*;

public class FileWriterTest
{
	public static void main(String[] args)
	{
		try(
			FileWriter fw = new FileWriter("poem.txt"))
		{
			fw.write("锦瑟 - 李商隐\r\n");
			fw.write("锦瑟无端五十弦,一弦一柱思华年。\r\n");
			fw.write("庄生晓梦迷蝴蝶,望帝春心托杜鹃。\r\n");
			fw.write("沧海月明珠有泪,蓝田日暖玉生烟。\r\n");
			fw.write("此情可待成追忆,只是当时已惘然。\r\n");
		}
		catch (IOException ioe)
		{
			ioe.printStackTrace();
		}
	}
}

四.输入输出流体系
1.处理流的用法
下面程序使用PrintStream处理流来包装OutputStream,使用处理流后的输出流在输出时将更加方便。

public class PrintStreamTest
{
	public static void main(String[] args)
	{
		try(
			FileOutputStream fos = new FileOutputStream("test.txt");
			PrintStream ps = new PrintStream(fos))
		{
			// 使用PrintStream执行输出
			ps.println("普通字符串");
			// 直接使用PrintStream输出对象
			ps.println(new PrintStreamTest());
		}
		catch (IOException ioe)
		{
			ioe.printStackTrace();
		}
	}
}

上面程序中的两行代码是先定义了一个节点输出流FileOutputStream,然后程序使用PrintStream包装了该节点输出流,最后使用PrintStream输出字符串,输出对象。

2.输入输出流体系
在这里插入图片描述
如果进行输入输出的内容是文本内容,则应该考虑使用字符流,如果进行输入输出的内容是二进制内容,则应该考虑使用字节流。
下面示范了使用字符串作为物理节点的字符输入输出流的用法。

public class StringNodeTest
{
	public static void main(String[] args)
	{
		String src = "从明天起,做一个幸福的人\n"
			+ "喂马,劈柴,周游世界\n"
			+ "从明天起,关心粮食和蔬菜\n"
			+ "我有一所房子,面朝大海,春暖花开\n"
			+ "从明天起,和每一个亲人通信\n"
			+ "告诉他们我的幸福\n";
		char[] buffer = new char[32];
		int hasRead = 0;
		try(
			StringReader sr = new StringReader(src))
		{
			// 采用循环读取的访问读取字符串
			while((hasRead = sr.read(buffer)) > 0)
			{
				System.out.print(new String(buffer ,0 , hasRead));
			}
		}
		catch (IOException ioe)
		{
			ioe.printStackTrace();
		}
		try(
			// 创建StringWriter时,实际上以一个StringBuffer作为输出节点
			// 下面指定的20就是StringBuffer的初始长度
			StringWriter sw = new StringWriter())
		{
			// 调用StringWriter的方法执行输出
			sw.write("有一个美丽的新世界,\n");
			sw.write("她在远方等我,\n");
			sw.write("哪里有天真的孩子,\n");
			sw.write("还有姑娘的酒窝\n");
			System.out.println("----下面是sw的字符串节点里的内容----");
			// 使用toString()方法返回StringWriter的字符串节点的内容
			System.out.println(sw.toString());
		}
		catch (IOException ex)
		{
			ex.printStackTrace();
		}
	}
}

3.转换流
输入输出流体系中还提供了两个转换流,用于实现将字节流转换成字符流,其中InputStreamReader将字节输入流转换成字符输入流,OutputStreamWriter将字节输出流转换成字符输出流。

public class KeyinTest
{
	public static void main(String[] args)
	{
		try(
			// 将Sytem.in对象转换成Reader对象
			InputStreamReader reader = new InputStreamReader(System.in);
			// 将普通Reader包装成BufferedReader
			BufferedReader br = new BufferedReader(reader))
		{
			String line = null;
			// 采用循环方式来一行一行的读取
			while ((line = br.readLine()) != null)
			{
				// 如果读取的字符串为"exit",程序退出
				if (line.equals("exit"))
				{
					System.exit(1);
				}
				// 打印读取的内容
				System.out.println("输入内容为:" + line);
			}
		}
		catch (IOException ioe)
		{
			ioe.printStackTrace();
		}
	}
}

4.推回输入流
PushbackInputStream和PushbackReader。
下面程序试图找出程序中的“new PushbackReader”字符串,当找到该字符串后,程序只是打印出目标字符串之前的内容

public class PushbackTest
{
	public static void main(String[] args)
	{
		try(
			// 创建一个PushbackReader对象,指定推回缓冲区的长度为64
			PushbackReader pr = new PushbackReader(new FileReader(
				"PushbackTest.java") , 64))
		{
			char[] buf = new char[32];
			// 用以保存上次读取的字符串内容
			String lastContent = "";
			int hasRead = 0;
			// 循环读取文件内容
			while ((hasRead = pr.read(buf)) > 0)
			{
				// 将读取的内容转换成字符串
				String content = new String(buf , 0 , hasRead);
				int targetIndex = 0;
				// 将上次读取的字符串和本次读取的字符串拼起来,
				// 查看是否包含目标字符串, 如果包含目标字符串
				if ((targetIndex = (lastContent + content)
					.indexOf("new PushbackReader")) > 0)
				{
					// 将本次内容和上次内容一起推回缓冲区
					pr.unread((lastContent + content).toCharArray());
					// 重新定义一个长度为targetIndex的char数组
					if(targetIndex > 32)
					{
						buf = new char[targetIndex];
					}
					// 再次读取指定长度的内容(就是目标字符串之前的内容)
					pr.read(buf , 0 , targetIndex);
					// 打印读取的内容
					System.out.print(new String(buf , 0 ,targetIndex));
					System.exit(0);
				}
				else
				{
					// 打印上次读取的内容
					System.out.print(lastContent);
					// 将本次内容设为上次读取的内容
					lastContent = content;
				}
			}
		}
		catch (IOException ioe)
		{
			ioe.printStackTrace();
		}
	}
}

五.重定向标准输入输出
在System类里提供了如下三个重定向标准输入输出的方法。

static void setErr(PrintStream err):重定向标准错误输出
static void setIn(PrintStream in):重定向标准输入流
static void setOut(PrintStream out):重定向标准输出流。
public class RedirectOut
{
	public static void main(String[] args)
	{
		try(
			// 一次性创建PrintStream输出流
			PrintStream ps = new PrintStream(new FileOutputStream("out.txt")))
		{
			// 将标准输出重定向到ps输出流
			System.setOut(ps);
			// 向标准输出输出一个字符串
			System.out.println("普通字符串");
			// 向标准输出输出一个对象
			System.out.println(new RedirectOut());
		}
		catch (IOException ex)
		{
			ex.printStackTrace();
		}
	}
}

六.Java虚拟机读写其他进程的数据
使用Runtime对象的exec方法可以运行平台上的其他程序,该方法产生一个process对象,process对象代表由该Java程序启动的子进程。Process类提供了如下三个方法,用于让程序和其子进程进行通信。
InputStream getErrorStream() 获取子进程的错误流;
InputStream getInputStream() 获取子进程的输入流;
OutputStream getOutputStream() 获取子进程的输出流。

public class ReadFromProcess
{
	public static void main(String[] args)
		throws IOException
	{
		// 运行javac命令,返回运行该命令的子进程
		Process p = Runtime.getRuntime().exec("ls");
		try(
			// 以p进程的错误流创建BufferedReader对象
			// 这个错误流对本程序是输入流,对p进程则是输出流
			BufferedReader br = new BufferedReader(new
				InputStreamReader(p.getErrorStream())))
		{
			String buff = null;
			// 采取循环方式来读取p进程的错误输出
			while((buff = br.readLine()) != null)
			{
				System.out.println(buff);
			}
		}
	}
}

结果:

Exception in thread "main" java.io.IOException: Cannot run program "pwd": CreateProcess error=2, 系统找不到指定的文件。
	at java.base/java.lang.ProcessBuilder.start(ProcessBuilder.java:1128)
	at java.base/java.lang.ProcessBuilder.start(ProcessBuilder.java:1071)
	at java.base/java.lang.Runtime.exec(Runtime.java:589)
	at java.base/java.lang.Runtime.exec(Runtime.java:413)
	at java.base/java.lang.Runtime.exec(Runtime.java:310)
	at ReadFromProcess.main(ReadFromProcess.java:11)
Caused by: java.io.IOException: CreateProcess error=2, 系统找不到指定的文件。
	at java.base/java.lang.ProcessImpl.create(Native Method)
	at java.base/java.lang.ProcessImpl.<init>(ProcessImpl.java:483)
	at java.base/java.lang.ProcessImpl.start(ProcessImpl.java:158)
	at java.base/java.lang.ProcessBuilder.start(ProcessBuilder.java:1107)
	... 5 more

七.RandomAccessFile
RandomAccessFile是Java输入输出流体系中功能最丰富的文件内容访问类,它提供了众多的方法来访问问价内容,既可以读取文件内容,也可以向文件输出数据,与普通的输入输出流不同的是,RandomAccessFile支持随机访问的方式,程序可以直接跳转到文件的任意地方来读写数据。
所以多线程断点的网络下载工具,就可以通过RandomAccessFile类来实现的。

八.对象序列化
对象序列化的目标是将对象保存到磁盘中,或允许在网络中直接传输对象。对象序列化机制允许吧内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,通过网络将这种二进制流传输到另一个网络节点。其他程序一旦获得了这种二进制流,都可以将这种二进制流恢复成原来的Java对象。

1.序列化的含义和意义

对象的序列化(Serialize)指将一个Java对象写入IO流中,与此对应的是,对象的反序列化(Deserialize)则指从IO流中恢复该Java对象。
如果需要让某个对象支持序列化机制,则必须让它的类是可序列化的(serializable),为了让某个类是可序列化的,该类必须实现如下两个接口之一:
Serializable
Externalizable

Java的很多类已经实现了Serializable,该接口是一个标记接口,实现该接口无须实现任何方法,它只是表明该类的实例是可序列化的。

2.使用对象流实现序列化
一旦某个类实现了Serializable接口,该类的对象就是可序列化的,具体如下两个步骤:
1> 创建一个ObjectOutputStream,这个输出流是一个处理流,所以必须建立在其他节点流的基础之上
//创建个ObjectOutputStream输出流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputSteam(“object.txt”));

2>调用ObjectOutputStream对象的writeObject()方法输出可序列化对象,
//将一个Person对象输出到输出流中
oos.writeObject(per);

//标识该类的对象是可序列化的
public class Person
	implements java.io.Serializable
{
	private String name;
	private int age;
	// 注意此处没有提供无参数的构造器!
	public Person(String name , int age)
	{
		System.out.println("有参数的构造器");
		this.name = name;
		this.age = age;
	}
	// 省略name与age的setter和getter方法

	// name的setter和getter方法
	public void setName(String name)
	{
		this.name = name;
	}
	public String getName()
	{
		return this.name;
	}

	// age的setter和getter方法
	public void setAge(int age)
	{
		this.age = age;
	}
	public int getAge()
	{
		return this.age;
	}
}
public class WriteObject
{
	public static void main(String[] args)
	{
		try(
			// 创建一个ObjectOutputStream输出流
			ObjectOutputStream oos = new ObjectOutputStream(
				new FileOutputStream("object.txt")))
		{
			Person per = new Person("孙悟空", 500);
			// 将per对象写入输出流
			oos.writeObject(per);
		}
		catch (IOException ex)
		{
			ex.printStackTrace();
		}
	}
}

运行上面程序,将会看到生成一个object.txt文件,该文件的内容就是Person对象。

如果希望从二进制中恢复Java对象,则需要使用反序列化的步骤如下:
1> 创建一个ObjectInputStream输入流,这个输入流是一个处理流,所以必须建立在其他节点流的基础之上
//创建一个ObjectInputStream输入流
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(“object.txt”));
2> 调用ObjectInputStream对象的readObject方法读取流中的对象,该方法返回一个Object类型的Java对象,如果程序知道该Java对象的类型,则可以将对象强制类型转换成其真实的类型
//从输入流中读取一个Java对象,并将其强制类型转换为Person类
Person p = (Person) ois.readObject();

public class ReadObject
{
	public static void main(String[] args)
	{
		try(
			// 创建一个ObjectInputStream输入流
			ObjectInputStream ois = new ObjectInputStream(
				new FileInputStream("object.txt")))
		{
			//将obj中的二进制流恢复java对象,则需要使用反序列化
			// 从输入流中读取一个Java对象,并将其强制类型转换为Person类
			//反序列化机制无须通过构造器来初始化java对象
			Person p = (Person)ois.readObject();
			System.out.println("名字为:" + p.getName()
				+ "\n年龄为:" + p.getAge());
		}
		catch (Exception ex)
		{
			ex.printStackTrace();
		}
	}
}

反序列化读取的仅仅是Java对象的数据,而不是Java类,因此采用反序列化恢复Java对象时,必须提供该Java对象所属类的class文件,否则将会引发ClassNotFoundException异常。

Person类只有一个有参数的构造器,没有无参数的构造器,而且该构造器内有一个普通的打印语句,当反序列化读取Java对象时,并没有看到程序调用该构造器,这表明反序列机制无须通过构造器来初始化Java对象。

3.对象引用的序列化
如果某个类的成员变量的类型不是基本类型或String类型,而是另一个引用类型,那么这个引用类必须是可序列化的,否则拥有该类型成员变量的类也是不可序列化的。

public class Teacher
	implements java.io.Serializable
{
	private String name;
	private Person student;
	public Teacher(String name , Person student)
	{
		this.name = name;
		this.student = student;
	}
	// 此处省略了name和student的setter和getter方法

	// name的setter和getter方法
	public void setName(String name)
	{
		this.name = name;
	}
	public String getName()
	{
		return this.name;
	}

	// student的setter和getter方法
	public void setStudent(Person student)
	{
		this.student = student;
	}
	public Person getStudent()
	{
		return this.student;
	}
}

当程序序列化一个Teacher对象时,如果该Teacher对象持有一个Person对象的引用,为了在反序列化时可以正常恢复该Teacher对象,程序会顺带将该Person对象也进行序列化,所以Person类也必须是可序列化的,否则Teacher类将不可序列化。

九.NIO
前面的方法中InputStream的read方法一般如果数据源中没有数据,则会阻塞该线程。
JDK1.4提供了改进输入输出的新功能,这些类都放在java.nio包以及子包下。

java.nio包: 主要包含各种与Buffer相关的类;
java.nio.channel包:主要包含与Channel和Selector相关的类;
java.nio.charset包:主要包含与字符集相关的类;
java.nio.channels.spi包:主要包含与Channle相关的服务提供者编程接口;
java.nio.charset.spi包:包含与字符集相关的服务提供者编程接口。

1.Buffer
2.Channel
3.字符集和Charset

十.文件锁
从JDK1.4的NIO开始,Java开始提供文件锁的支持。
在NIO中,Java提供了FileLock来支持文件锁定功能,在FileChannel中提供的lock()/tryLock()方法可以获得文件锁FileLock对象,从而锁定文件。

文件锁可以有效阻止多个进程并发修改同一个文件。

lock()和tryLock()方法区别:
当lock()试图锁定某个文件时,如果无法得到文件锁,程序将一直阻塞;而tryLock()是尝试锁定文件,它将直接返回而不是阻塞,如果获得了文件锁,该方法则返回该文件锁,否则返回null。
在这里插入图片描述
处理完文件后通过FileLock的release方法释放文件锁,下面是实例:

import java.io.*;
import java.nio.*;
import java.nio.channels.*;

public class FileLockTest
{
	public static void main(String[] args)
		throws Exception
	{

		try(
			// 使用FileOutputStream获取FileChannel
			FileChannel channel = new FileOutputStream("a.txt")
				.getChannel())
		{
			// 使用非阻塞式方式对指定文件加锁
			FileLock lock = channel.tryLock();
			// 程序暂停10s
			Thread.sleep(10000);
			// 释放锁
			lock.release();
		}
	}
}

文件锁虽然可以用于控制并发访问,但对于高并发访问的情形,还是推荐使用数据库来保存程序信息,而不是使用文件。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值