输入、输出
File类是java.io包下代表与平台无关的文件和目录。File能新建、删除、重命名文件和目录。但不能访问文件内容本身,如果需要访问,则需要使用输入流、输出流。
访问文件和目录
File类可以创建File实例,既可以是决定路径,也可以是相对路径。默认情况下,系统总是依据用户的工作路径来解释相对路径,这个路径由系统属性user.dir指定,通常也就是运行Java虚拟机时所在的路径。
1,访问文件名相关的方法
- String getName():返回此File对象所表示的文件名或路径名(如果是路径,则返回最后一级子路径名)。
- String getPaht():返回此File对应的路径名.
- File getAbsoulteFile:返回此File对象的绝对路径的FIle.
- String getAbsoultePath:返回此File对象所对应的绝对路径名.
- String getParent():返回此对象所对应目录(最后一级子目录)的父目录名.
- boolean renameTo(File newName):重命名此File对象所对应的文件或目录,如果重命名成功,则返回true,否则返回false.
2,文件检测相关的方法
- boolean exists():判断File对象所对应的文件或目录是否存在.
- boolean canWrite():判断File对象所对应的文件和目录是否可写.
- boolean canRean():判断对象所对应的文件和目录是否可读.
- boolean isFile();
- boolean isDirectory();
- boolean isAbsolute();判断File对象所对应的文件或目录是否时决定路径.该方法消除了不同平台的差异,可以直接判断File对象是否为绝对路径.
3,获取常规文件信息
- long lastModified();
- long length():返回文件内容的长度.
4,文件操作相关的方法
- boolean createNewFile():当此File对象所对应的文件不存在时,该方法将新建一个该File对象所指定的新文件,如果创建成功则返回true;否则返回false;
- boolean delete():删除File对象所对应的文件或路径.
- static File createTempFile(String prefix,String suffix);在默认的临时文件目录中创建一个临时文件的空文件,使用给定的前缀,系统生成的随机数和给定后缀作为文件名.这是一个静态方法.prefix参数至少3个字节长.建议前缀使用一个短的,有意义的字符串.suffix参数可以为null,在这中情况下,将使用默认的后缀".tmp";
- static File createTempFile(String prefix,String suffix,File director);
- void deleteOnExit();注册一个删除钩子,只当当Java虚拟机退出时,删除FIle对象所对应的文件和目录.
5,目录操作相关的方法:
- boolean mkdir();
- Stirng[] list();列出File对象的所有子文件名和路径名,返回String数组.
- File[] listFiles():列出File对象的所有子文件和路径,返回File数组.
- static File[] listRoots():列出系统总所有的根路径.这是一个静态方法,可以直接通过File类来调用.
public static void main(String[] args) throws IOException {
File file = new File(".");
System.out.println(file.getName());//.
System.out.println(file.getParent());//null
System.out.println(file.getAbsoluteFile());//D:\StSWorkSpace\test\.
System.out.println(file.getAbsoluteFile().getParent()); //D:\StSWorkSpace\test
File temFile = File.createTempFile("aaa", ".txt", file);
temFile.deleteOnExit();
File newFile = new File(System.currentTimeMillis() + "");
System.out.println("newFile对象是否存在" + newFile.exists());//newFile对象是否存在false
newFile.createNewFile();
newFile.mkdir();
String[] list = file.list();
System.out.println("====================================");
for(String fileName : list) {
System.out.println(fileName);
}
File[] roots = File.listRoots();
System.out.println("=====================================");
for(File root : roots) {
System.out.println(root);
}
}
文件过滤器
在File类的list()方法中可以接受一个FilenameFilter参数,通过该参数可以只列出符合条件的文件.
FilenameFilter接口包含了一个accept(File dir,String nam); 是一个函数式接口.
理解Java的IO流
在Java中把不同的输入/输出源(键盘,文件,网络连接等)抽象为流(stream);stream是从起源(source)到接受(sink)的有序数据.
流的分类
1,输入流和输出流
按照流的流向来分,可以分为输入流和输出流:
- 输入流:只能从中读取数据,而不能向其写入数据.
- 输出流:只能向其写入数据,而不能从中读取数据.
所谓输入和输出,是相对于程序运行所在的内存的角度来考虑的.
Java的输入流主要由InputStream和Reader作为基类,而输出流则主要由OutPutStream和Writer作为基类.它们都是一些抽象基类,无法直接创建实例.
2,字节流和字符流
字节流和字符流的用法几乎完全一样,区别在于字节流和字符流所操作的数据单元不一样–字节流操作的数据单元是8位的字节,而字符流操作的数据单元是16位的字符.
字节流主要由InputStream和OutputStream作为基类,而字符流则主要由Reader和Writer作为基类.
3,节点流和处理流
按照流的角色来分,可以分为节点流和处理流
可以从/向一个特定的IO设备(如磁盘,网络)读/写数据的流,称为节点流,节点流也被称为低级流,程序直接连接到实际的数据源,和实际的输出和输出节点连接.
处理流则用于对一个已存在的流进行连接和封装,通过封装后的流来实现数据读写功能.处理流也被称为高级流.程序并不会直接连接到实际的数据源,没有和实际的输出/输出节点连接.使用处理流的一个明显的好处是,只要使用相同的处理流,程序就可以使用完全相同的输入/输入代码来访问不同的数据源,随着处理处理流所包装节点流的变化,程序实际所访问的数据源也相应地发生变化.
流的概念模型
Java把所有设备里的有序数据抽象程流模型.
java的IO流供涉及40多个类,这些类实际上非常规则,而且彼此之间存在非常紧密的联系.Java的IO流的40多个类都是从如下4个抽象基类派生的.
- InputStream/Reader:所有输入流的基类,前者是字节输入流,后者是字符输入流;
- OutputStream/Writer:所有输出流的基类,前者是字节输出流,后者是字符输出流.
Java的处理流模型体现了输入/输出流设计的灵活性.处理流的功能主要体现在以下两个方面.
- 性能的提高:主要以增加缓冲的方式来提高输入/输出的效率;
- 操作的便捷:提供便捷的方法一次输入/输出大批量的内容,而不是一个或多个水滴.
字节流和字符流
因此它们的操作方式几乎完全一样
InputStream和Reader
InputStream里的方法:
- int read();从输入流中读取单个字节,返回读取的字节数据.
- int read(byte[] b);从输入流中最多读取b.length个字节的数据,并将其存储在字节数组b中,返回实际读取的字节数.
- int read(byte[] b,int off,int len):从输入流中最多读取len个字节的数据,并将其存储在数组b中,放入数组中,从off位置开始,返回实际读取的字节数.
Reader
- int read();从输入流中读取单个字符,返回所读取的字符数据;
- int read(char[] cbuf);
- int read(char[] int off,int len);
两个基类的功能基本一样.
它们分别由一个用于读取文件的输入流:FileInputStream和FileReader.它们都是节点流.
public static void main(String[] args) throws IOException {
FileInputStream file = new FileInputStream("test.txt");
byte[] bbuf = new byte[1024];
int hasread = 0;
while((hasread = file.read(bbuf)) > 0) {
System.out.println(new String(bbuf, 0, hasread));
}
file.close();
}
字节流不能读取汉字;
public static void main(String[] args) {
try(
FileReader fr =new FileReader("test2.txt")
) {
char[] cbuf = new char[32];
int hasread = 0;
while((hasread = fr.read(cbuf)) > 0) {
System.out.println(new String(cbuf,0,hasread));
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
电脑上默认创建的文件不是UTF-8编码格式,需要先进行转化;
除此之外,Input和Reader还支持如下几个方法来移动记录指针.
- void mark(int readAheadLimit):在记录指针当前位置记录一个标记.
- boolan markSupported():判断此输入流是否支持mark()操作,即是否支持记录标记.
- void reset():将此流的记录指针重新定位到上次记录标记(mark)的位置.
- long skip(long n);记录指针向前移动n个字节/字符.
OutputStream和Writer
两个基类非常相似,都提供了如下方法:
- void write(int c);
- void write(byte[]/char[] buf);
- void write(byte[]/char[] buf,int off,int len);
- void write(String str);
- void write(String str,int off,int len);
输入/输出流体系
实际识别处理流非常简单,只要流的构造器参数不是一个物理节点,而是已经存在的流,那么这种流就是一个处理流;而所有节点流都是直接以物理IO节点作为构造器参数的.
使用处理流对于开发人员来说,操作更加简单,运行效率更高.
public static void main(String[] args) {
try(
FileOutputStream fos = new FileOutputStream("test.txt");
PrintStream ps = new PrintStream(fos)
) {
ps.println("普通字符串");
System.out.println("普通字符串");
ps.println(new InputStreamTest());
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
这段代码将在test.txt中进行输出,而不是在控制台上进行输出.
输入/输出流体系
Java把IO流按功能分成了许多类,而每个类中又分别提供了字节流和字符流.
图片
如果处理的是二进制内容,应该使用字节流;而处理文本文件,则考虑使用字符流.
创建StringWriter时,实际上以一个StringBuffer作为输出点;(因为String是不可变的字符串对象).
缓冲流增加了缓冲功能,增加缓冲功能可以提高输入,输出的效率,增加缓冲功能时,需要使用flush()才可以将缓冲区的内容写入实际的物理节点
转换流
两个转换流:
- InputStreamReader:将字节输入流转换为字符输入流
- OutputStreamWriter:将字节输出流转换成字符输出流
推回输入流
在输入/输出流中,有两个特殊的流与众不同,就是PushbackInputStream和PushbackReader,将指定的数据推回缓冲区里,从而允许,重复读取.
- void unread(byte[]/char[] buf);
- void unread(byte[]/char[] buf,int off,int len);
- void unread(int b);
而推回输入里的read()方法总是先从推回的缓冲区中读取数据.默认的推回缓冲区的长度是1,可以在构造时设定大小.如果推回的长度大于该值,就报错.
重定向标准输入/输出
Java中的标准输入和标准输入分别通过System.in和System.out来代表.在默认情况下,它们代表键盘和显示器.
在System类里提供了如下三个重定向标准输入/输出的方法:
- static void setErr(printStream err);重定向"标准"错误输出流
- static void setIn(InputStream in);
- static void setOut(PrintStream out);
java虚拟机读写其他进程的数据
使用Runtime对象的exec()方法可以运行平台上的其他程序,该方法可以产生一个Process对象,Process对象代表由java程序启动的子进程.Process类提供了如下三个方法,用于让程序和子程序进程进行通讯.
- InputStream getErrorStream:获取子进程的错误流;
- InputStream getInputStream:获取子进程的输入流;
- OutputStream getOutputStream();获取子进程的输出流.
RandomAccessFil
RandomAccessFile是Java输入/输出流体系中功能最丰富的文件内容访问类,它提供了多种方法来访问文件的内容,它既可以读取文件内容,也可以向文件传输数据.与普通的输入/输出流不同的是,RandomAccessFile支持随机访问的方式,程序可以直接跳转到文件的任意地方来读写数据.
RandomAccessFile可以向已存在的文件后追加内容.
它由一个局限,就是只能读写文件,不能读写其他的IO节点.
可以随意指定指针位置
方法:
- long getFilePointer();返回文件记录指针的当前位置.
- void seek(long pos):将文件记录指定定位到pos位置.
RandomAccessFile既可以读文件,也可以写文件.
除了包含了普通的读写方法,还有readXxx()和writeXxx()方法来完成输入/输入.
构造器,需要指定一个mode参数,指定RandomAccessFile的访问模式:
- “r” 只读模式
- “rw” 读写方式打开指定文件,如果不存在,则尝试创建文件
- “rws” 读写方式打开指定文件,相对于rw,还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备.
- “rwd” 相对于rw,还要求对文件内容的每个更新都同步写入到底层存储设备
对象序列化
对象序列化的目标是将对象保存到磁盘中,或允许在网络中直接传输对象.对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,通过网络将这种二进制流传输到另一个网络节点.其他程序一旦获取到二进制流,都可以i将这种二进制流恢复成原来地Java对象.
序列化的含义和意义
对象的序列化(Serialize)指将一个Java对象写入IO流中,于此对应的是,对象的反序列化(Deserialize)则从IO流中恢复该Java对象.为了让某个类可序列化,该类必须实现如下两个接口之一.
- Serializable
- Externalizable
Java的很多类已经实现了Serializable,该接口是一个标记接口,无须实现任何方法,只表示该类的实例是可序列化的.
使用对象流实现序列化
使用ObjectOutputStream的writeObject方法.
反序列化,ObjectInputStream 的readObject();
反序列化时,读取的仅仅是Java对象的数据,而不是Java类,因此采用反序列化恢复Java对象时,必须提供该Java对象所属类的class文件,否则将会引发ClassNotFoundException异常。反序列化机制无须通过构造器来初始化对象。
当一个可序列化类有多个类时(包括直接父类和间接父类),这些父类要么有无参构造的构造器,要么也是可序列化的,否则反序列化将跑出InvalidClassException异常。如果父类不是可序列化的,只是带有无参的构造器,则该父类中定义的成员变量值不会序列化进二进制流中。
对象引用的序列化
如果某个类的成员变量的类型不是基本类型或String类型,而是一个引用类型,那么这个引用类型必须是可序列化的,否则拥有该类型成员变量的类也是不可序列化的。
Java序列化机制采用了一种特殊的序列化算法,其算法内容如下。
- 所有保存到磁盘中的对象都有一个序列化编号
- 当程序试图序列化一个对象时,程序将先检查该对象是否已经被序列化过,只有该对象从未被序列化过,系统才会将该对象转化成字节序列并输出。
- 如果某个对象已经序列化过,程序将只是直接输出一个序列化编号,而不是再次重新重新序列化该对象。
由于存在这个机制,会有一个问题,当一个对象序列化后,在此序列化时,并没有进行序列化,而是输出序列化编号,即使是对象被修改也是这种情况,这是对象的修改无效。
自定义序列化
在一些特殊的场景下,如果一个类里包含的某些实例变量是敏感信息,例如银行账户信息等,这时不希望系统将实例变量值进行序列化;或者某个实例变量的类型是不可序列化,因此不希望对该实例变量进行递归序列化,以避免引发java.io.NotSerializableException异常。
通过在实例变量前面使用transient关键字修饰。可以指定Java序列化时无须理会该实例变量。
transient关键字只能用于修饰实例变量,不可修饰Java程序中的其它成分。这样反序列化恢复Java对象时无法取得该实例变量值。Java还提供了一种自定义序列化机制,通过这种自定义序列化机制可以让程序控制如何序列化各实例变量,甚至完全不序列化某些实例变量(与使用transient关键字相同);
在序列化和反序列化过程中需要特殊处理的类应该提供如下特殊签名的方法,这些特殊的方法用以实现自定义序列化。
- private void writeObject(java.io.ObjectOutputStream out) throws IOException;
- private void readObject(java.io.ObjectInputStream in) throws IOException,ClassNotFoundException;
- private void readObjectNoDate() throws ObjectStreamException;
readObject()方法负责写入特定类的实例状态,以便相应的readObject()方法可以恢复它。通过重写该方法,程序员可以完全获得对序列化机制的控制。默认情况下,该方法会调用out.defaultWriteObject来保存Java对象的各实例变量,从而可以实现序列化Java对象状态的目的。
readObject()方法负责从流中读取并恢复对象实例变量,通过重写该方法,可以完全获得对反序列化机制的控制。默认情况下,该方法会调用in.defaultReadObject来恢复Java对象的。
当序列化流不完整时,readObjectNoData()方法可以用来正确地初始化反序列化的对象。例如,接受方使用的反序列化类的版本不同于发出方,或者两个类不一样,这是就会调用readObjectNoData方法来初始化反序列化对象。
还有一个更彻底的自定义机制,它甚至可以在序列化对象时将该对象替换成其他对象。如果需要实现序列化默认对象时替换对象,则应该为序列化类提供如下特殊方法:
ANY-ACCESS-MODIFIER Object writeReplace()throws ObjectStreamException;
对象的序列化时,序列化的是对象的类名和实例变量;方法、类变量(即static修饰的成员变量)、transient实例变量都不会被序列化。
。。。
版本
Java序列化机制允许为序列化类提供一个private static final SerialVersionUID的值。
提供的好处就是,即便java类升级后,只有该版本号保持不变,序列化机制也能反序列化成功。
NIO
传统的流是阻塞的,并且是通过字节的移动来处理的(即使不直接处理字节流,但底层的实现还是依赖于字节处理),一次只能处理一个字节,因此面向流的输入输出系统效率通常不高。
从jdk1.4开始,Java提供了一系列改进的输入输出处理的新功能,这些功能被统称为新IO(NIO),这些类都放在java.nio包以及子包下,并且对原java.io包中的很多类都以NIO为基础进行了改写,新增了满足NIO的功能。
Java新IO概述
新IO采用内存映射文件的方式来处理输入输出,将文件或文件的一段区域映射到内存中,这样就可以行访问内存一样来访问文件了。
Java中与新IO相关的包如下:
- java.nio 主要包含各种与Buffer相关的类
- java.nio.channels 主要包含与Channel和Selector相关的类
- java.nio.charset 主要包含与字符集相关的类
- java.nio.channels.spi 主要包含与Channel相关的服务提供者编程接口。
- java.nio.charset.spi 包含与字符集相关的服务提供者编程接口。
Channel和Buffer是新IO中的两个核心对象,Channel是对传统的输入输出系统的模拟。它提供了一个map()方法,通过map()方法可以直接将一块数据映射到内存中。新IO是面向块的处理。
Buffer可以被理解成一个容器,它的本质是一个数组,发送到Channel中的所有对象都必须首先放到Buffer中,而从Channel中读取的数据也必须先放到Buffer中。
除了Channel和Buffer之外,新IO还提供了用于将Unicode字符串映射成字节序列以及映射操作的Charset类,也提供了用于支持非阻塞式输入输出的Selector类。
使用Buffer
从内部结构上看,Buffer就像一个数组,是一个抽象类,最常用的子类是ByteBuffer,他可以在底层字节数组上进行get,set操作。除了ByteBuffer,对应的于其他基本数据类型(boolan除外)都有相应的Buffer类:CharBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer。
除了ByteBuffer之外,都采用相同或相似的方法来管理数据,只是各自管理的数据类型不同而已。这些Buffer类都没有提供构造器,通过使用如下方法来得到一个Buffer对象。
- static XxxBuffer allocate(int capacity):创建一个容量为capacity的XxxBuffer对象。
实际上使用较多的是ByteBuffer和CharBuffer,其中ByteBuffer类还有一个子类,MapedByteBuffer,用于表示Channel将磁盘文件的部分或全部内容映射到内存中后得到的结果,通常MappedByteBuffer对象由Channel的map()
方法返回;
Buffer三概念:capacity,limit(limti及后边索引的数据不能被读写),position(下一个读出和写入的索引);
mark,Buffer允许直接将position定位到该mark处。
这些数据满足:0<=mark <= position <= capacity(mark一般位于已经读写的区域)
当Buffer装入数据时,positon为0,limit为capacity
当buffer装入数据结束后,调用Buffer的flip()方法,将limit设置为position所在的位置,并将position设为0
当Buffer读取完数据后调用clear方法,它仅仅是将position设置为0,将limit设置为capacity,这样为再次向Buffer中装入数据做好准备。
Buffer还包含如下的一些常用的方法:
- int capacity();(capacity不可设置,只能在定义时设置)
- boolean hasRemaining:判断当前位置position和界限之间limit之间是否有元素可供处理。
- int limit();
- Buffer limit(int newLt);设置limit
- Buffer mark(): 只能在0和positon之间做mark
- int position();
- Buffer position(int newPs);
- int remaining();返回当前位置和界限之处的元素个数。
- Buffer reset:将位置(positon)转到mark所在的位置。
- Buffer rewind();将位置positon设置成为0,取消设置的mark
Buffer的子类都提供了put()和get()用于放入和读取数据。支持对单个数据的访问,也支持对批量数据的访问(以数组为参数)。
put和get分为相对和绝对两种方式:
相对(Relative):从Buffer的当前position处开始读取或写入数据,然后将位置的值按处理元素的个数增加。
绝对(Absolute):直接根据索引向Buffer中读取或写入数据,不影响position
public static void main(String[] args) {
CharBuffer buff = CharBuffer.allocate(8);
System.out.println("capacity = " + buff.capacity());//8
System.out.println("limit = " + buff.limit());//8
System.out.println("position = " + buff.position());//0
buff.put('a');
buff.put('b');
buff.put('c');
System.out.println("加入三个元素。。。。。。。。。。。。。。。。。");
System.out.println("capacity = " + buff.capacity());//8
System.out.println("limit = " + buff.limit());//8
System.out.println("position = " + buff.position());//3
buff.flip();
System.out.println("flip后。。。。。。。。。。");
System.out.println("capacity = " + buff.capacity());//8
System.out.println("limit = " + buff.limit());//3
System.out.println("position = " + buff.position());//0
System.out.println("获取一个元素" + buff.get());
System.out.println("capacity = " + buff.capacity());//8
System.out.println("limit = " + buff.limit());//3
System.out.println("position = " + buff.position());//1
buff.clear();
System.out.println("clear........................");
System.out.println("capacity = " + buff.capacity());//8
System.out.println("limit = " + buff.limit());//8
System.out.println("position = " + buff.position());//0
}
使用Channel
Channel可以直接将指定文件的部分或全部直接映射成Buffer。
程序不能直接访问Channel中的数据,必须通过Buffer
Java为Channel接口提供了DatagramChannel(UDP网络通信),FilChannel、Pipe.SinkChannel、Pipe.SourceChannel(线程通信)、SelectableChanble、ServerSocketChannel、SocketChannel)(TCP网络通信)等实现类
Charset
Charset提供了一个avaliableCharsets()静态方法。
文件锁
Channel.tryLock();
Channel.Lock();
Channel.lock(long positon,long size,boolean shared);
Channel.tryLock(long position,long size,boolean shared);
Java7的NIO2
Java7新增的java.nio.file
java.nio.channels包下的以Asynchronous开头的Channel接口和类
Path,Paths和Files核心API
Paths包含了两个返回Path的静态方法
Paths和Files是用于操作Path和File的工具类
使用FileVisitor遍历文件和目录
Files类提供了如下两个方法来遍历文件和子类:
- walkFileTree(Path start,FileVisitor<? super Pah> visitor):遍历start路径下的所有文件和子目录
- walkFileTree(Path start,Set options,int maxDepath,FileVisitor<? super Path> visitor);
FileVisitor代表文件访问器,有四个方法,我们有时候无须全部实现,可以使用simpleFileVisitor(FileVisitor的抽象实现类)
四个方法:
都返回一个FileVisitorResult对象,它是一个枚举类
使用WatchService监控文件变化
NIO.2Path类提供了如下一个方法来监听文件系统的变化:
`register(WatchService watcher,WatchEvent.Kind<?> … events):用watcher监听Path代表的目录下的文件变化。events参数指定要监听哪些类型的时间。
WatcherService代表一个监听服务,三个方法来获取被监听目录的文件变化
- WatcherKey poll():获取下一个WatcherKey,如果诶呦就立即返回null;
- WatcherKey poll(long timeout,TimeUnit unit): 尝试timeout时间去获取下一个WatcherKey;
- WatcherKey take():获取下一个WatcherKey,如果没有WatchKey发生就一直等待。
如果程序需要一直监控,则应该选择使用take();
获取文件系统的WatchService对象;
WatchService watcherService = FileSystems.getDefault().newWatcherService
event是枚举类
通过take()等获取时间后,通过key.pollEvents()获取事件的集合;
访问文件属性
Java7的NIO.2在java.nio.file.attribute提供了大量的工具类,通过这些工具类,开发者可以非常简单地读取、修改文件属性。这些文件类主要分为如下两类。
XxxAttributeView:代表这种文件属性的“视图”;
XxxAttribute:代表文件属性的集合,程序一般通过XxxAttributeView对象来获取XxxAttributes。使用readAttributes()方法。
Files.getFileAttributeView(path,viewClass)