IO编程
IO 基础
简介
IO:Input/Output
IO流是一种流式数据输入、输出模型:
1)二进制数据以byte为最小单位在InputStream/OutputStream中单向流动
2)字符数据以char为最小单位在Reader/Writer中单向流动
3)JDK的java.io包提供了同步IO功能
4)JDK的java.nio包提供了异步IO功能
Java的IO流的接口
字节流接口:InputStream、OutputStream
字符流接口:Reader、Writer
File对象
java.io.File 表示文件系统的一个文件或者目录
isFile():判断是否是文件
isDirectory():判断是否是目录
创建File对象本身不涉及IO操作
getPath():获取路径
getAbsolutePath():获取绝对路径
getCanonicalPath():获取规范路径
文件操作
canRead():是否允许读取该文件
canWrite():是否允许写入该文件
canExecute():是否允许运行该文件
length():获取文件大小
createNewFile():创建一个新文件
static createTempFile():创建一个临时文件
delete():删除该文件
deleteOnExit():在JVM退出时删除文件
目录操作
String[] list():列出目录下的文件和子目录名
File[] listFiles():列出目录下的文件和子目录名
File[] listFiles(FileFilter filter)
File[] listFiles(FilenameFilter filter)
mkdir():创建该目录
mkdirs():创建该目录,并在必要时将不存在的父目录也创建出来
delete():删除该目录
练习
编写程序,1)列出当前目录下的所有子目录和文件,并按层次打印;2)如果不指定参数,则使用当前目录,如果指定参数,则使用指定目录
public static void printFileName(File file){
if(file.isDirectory() && 0 != file.listFiles().length){
File[] files = file.listFiles();
files = sortFile(files);
for(File f : files){
StringBuilder sb = new StringBuilder();
if(f.isFile()){
sb.append(getTab(number));
sb.append(f.getName());
}else{
sb.append(getTab(number));
sb.append(f.getName());
sb.append("\\");
}
System.out.println(sb);
if(f.isDirectory()){
number++;
printFileName(f);
number--;
}
}
}
}
//得到子目录前需要加多少tab
private static String getTab(int number){
StringBuilder sb = new StringBuilder();
for(int i = 0; i < number; i++){
sb.append("\t");
}
return sb.toString();
}
//对子目录进行排序,文件夹在前,文件在后
private static File[] sortFile(File[] files) {
ArrayList<File> list = new ArrayList<File>();
for(File f : files){
if(f.isDirectory()){
list.add(f);
}
}
for(File f : files){
if(f.isFile()){
list.add(f);
}
}
return list.toArray(new File[files.length]);
}
Input 和 Output
InputStream
InputStream是所有输入流的超类
1)int read():读取一个字节,并返回字节(0~255),已读到末尾返回-1
2)int read(byte[]):读取若干字节并填充到byte[]数组
3)int read(byte[] b, int off, int len)指定byte[]数组的偏移量和最大填充数
4)void close():关闭输入流
read()方法是阻塞的
使用try可以保证InputStream正确关闭
public static void readFile(){
InputStream input = null;
try {
input = new FileInputStream("src/1.txt");
int n;
while((n = input.read()) != -1){
System.out.println(n);
}
} catch(IOException e){
e.printStackTrace();
}finally {
if(input != null){
try {
input.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
// JDK7 新特性
public static void readFile() throws FileNotFoundException, IOException{
try(InputStream input = new FileInputStream("src/1.txt")){
int n;
while((n = input.read()) != -1){
System.out.println(n);
}
} // 在此自动关闭InputStream(推荐写法)
}
// 利用缓存区一次读取多个字节
public static void readFile() throws FileNotFoundException, IOException{
try(InputStream input = new FileInputStream("src/1.txt")){
byte[] buffer = new byte[1024];
int n;
while((n = input.read(buffer)) != -1){
System.out.println("read " + n + " bytes.");
}
}
}
常用的InputStream:
1)FileInputStream:可以从文件获取输入流
2)ByteArrayInputStream:可以在内存中模拟一个InputStream
byte[] data = {72, 101, 108, 111, -28, -67, -96, -27, -91, -67};
try(InputStream input = new ByteArrayInputStream(data)){// 可以用在测试中
int n;
while((n = input.read()) != -1){
System.out.println(n)
}
}
OutputStream
outputStream是所有输出流的超类
1)void write(int b):写入一个字节
2)void write(byte[]):写入byte[]数组的所有字节
3)void write(vyte[] b, int off, int len):写入byte[]数组指定范围的字节
4)void close():关闭输入流
5)void flush():将缓存区内容输出
write()方法也是阻塞的
使用try可以保证OutputStream正确关闭
常用OutputStream
1)FileOutputStream:可以输出到文件
2)ByteArrayOutputStream:可以在内存中模拟一个OutputStream
try(ByteArrayOutputStream output = new ByteArrayOutputStream()){
output.write("Hello ".getBytes("UTF-8"));
output.write("world!".getBytes("UTF-8"));
byte[] data = output.toByteArray();
}
练习
编写程序,接收两个命令行参数,分别表示源文件和目标文件,用InputStream、OutputStream把源文件复制到目标文件。复制后,请检查源文件和目标文件是否相同(文件长度相同,内容相同),分别用文本文件、图片文件和zip文件测试
public class Main {
public static void main(String[] args) throws FileNotFoundException, IOException {
if (args.length < 2) {
System.out.println("usage: java CopyFile sourcefile targetfile");
System.exit(0);
}
byte[] bytes = new byte[1024];
try(InputStream input = new FileInputStream(args[0]);
OutputStream output = new FileOutputStream(args[1])){
int n;
while((n = input.read(bytes)) != -1){
output.write(bytes, 0, n);
}
}
}
}
Filter模式
Filter模式是为了解决子类数量爆炸的问题
直接提供数据的InputStream:
1)FileInputStream
2)ByteArrayInputStream
3)ServletInputStream
提供附加功能的InputStream从FilterInputStream派生
1)BufferedInputStream:缓存功能
2)DigestInputStream:计算签名功能
3)CipherInputStream:加密/解密功能
4)GZIPInputStream:压缩功能
InputStream input = new GZIPInputStram(
new BufferedInputStream(
new FileInputStream("test.gz");
)
)
Filter模式又称Decorator模式,通过少量的类是实现了各种功能的组合
FilterInputStream 和 FilterOutputStream类似
// 实现一个字节数计数的InputStream
public class CounInputStream extends FilterInputStream{
int count = 0;
public CounInputStream(InputStream in) {
super(in);
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
int n = super.read(b, off, len);
count++;
return n;
}
}
操作Zip
ZipInputStream可以读取Zip流
JarInputStream提供了额外读取jar包内容的功能
ZipOutputStream可以写入Zip流
配合FileInputStream和FileOutputStream就可以读写Zip文件
// ZipInputStream的基本使用
try(ZipInputStream zip = new ZipInputStream("test.zip")){
ZipEntry entry = null;
while((entry = zip.getNextEntry()) != null){
String name = entry.getName();
if(!entry.isDirectory()){
int n;
while((n = zip.read()) != -1){
// ...
}
}
}
}
// ZipOutputStream的基本使用
try(ZipOutputStream zip = new ZipOutputStream("test.zip")){
File[] files = ...;
for(File file : files){
zip.putNextEntry(new ZipEntry(file.getName));
zip.write(getFileDataAsBytes(file));
zip.closeEntry();
}
}
classpath资源
classpath中也可以包含任意类型的文件
从classpath读取文件可以避免不同环境下文件路径不一致的问题
读取classpath资源:
try(InputStream input = getClass().getResourceAsStream("/default.properties")){
if(input != null){
// Read from classpath
}
}
序列化
序列化是指把一个Java对象变成二进制内容(byte[]),序列化后可以把byte[]保存到文件中,也可以通过网络传输
Java对象实现序列化必须实现Serializable接口
Serializable接口没有定义任何方法,空接口被称为“标记接口(Marker Interface)”
反序列化是指把一个二进制内容(bytep[])变成Java对象
使用ObjectOutputStream和ObjectInputStream实现序列化和反序列化
try(ObjectOutputStream output = new ObjectOutputStream(...)){
output.writeObject(new Person("aa"));
output.writeObject(new Person("bb"));
}
try(ObjectInputStream input = new ObjectInputStream(...)){
Object obj = input.readObject();
Person per = (Person)input.readObject();
}
readObject()可能抛出异常:
1)ClassNotFoundException:没有找到对应的Class
2)InvalidClassException:Class不匹配
反序列化的重要特点:反序列化由JVM直接构造出Java对象,不调用构造方法
可设置serialVersionUID作为版本号(非必须)
Reader 和 Writer
Reader
Reader以字符为最小单位实现了字符输入
1)int read():读取下一个字符,并返回字符(0~65535),读到末尾返回-1
2)int read(char[]):读取若干字符并填充到char[]数组
3)int read(char[] c, int off, int len):指定char[]数组的偏移量和最大填充数
4)void close():关闭Reader
常用的Reader类
1)FileReader:从文件读取
2)CharArrayReader:从char[]数组读取,可以再内存中模拟一个Reader
Reader是基于InputStream构造的,任何InputStream都可指定编码并通过InputStreamReader转换为Reader:
InputStream input = new FileInputStream(filename)
Reader reader = new InputStreamReader(input, "UTF-8")
Writer
Writer以字节为最小单位实现了字符流输出
1)write(int c):写入下一个字符
2)write(cahr[]):写入char[]数组的所有字符
常用Write类:
1)FileWriter:写入文件
2)CharArrayWriter:写入cahr[]数组
Writer是基于OutputStream构造的,任何Output都可指定编码并通过OutputStreamWriter转换为Writer:
Writer writer = new OutputStreamWriter(output, "UTF-8")
练习
编写一个程序,接收两个命令行参数,分别表示源文件和目标文件,然后用Reader、Writer把GBK编码的源文件转换为UTF-8编码的目标文件
public class Main {
public static void main(String[] args) throws FileNotFoundException,
IOException {
if (args.length < 2) {
System.out.println("usage: java CopyFile sourcefile targetfile");
System.exit(0);
}
char[] chars = new char[1024];
try (Reader reader = new InputStreamReader(
new FileInputStream(args[0]), "GBK");
Writer writer = new OutputStreamWriter(new FileOutputStream(
args[1]), "UTF-8")) {
int ch;
while ((ch = reader.read(chars)) != -1) {
writer.write(chars, 0, ch);
}
}
}
}