输入输出
一、Java IO
Java IO即Java 输入输出系统。流从概念上来说是一个连续的数据流。
按流向分为输入输出流,按维度分为字节流(8位字节)和字符流(16位字节),按角色分为节点流和处理流。
相对计算机程序而言,输入:读,输出:写。
节点流(低级流):向IO设备读写数据的流。
处理流(高级流、包装流):对已存在的流进行连接和封装后的流。是一种装饰器设计模式。只要流的构造器是流,则一定为处理流。关闭时只需关闭最上层的处理流,系统会自动关闭节点流。
例:
PrintStream ps=new PrintStream(new FileOutputStream(file));ps,println(str);
1、File操作
public class FileDemo {public static void main(String[] args) {//检查文件是否存在File file = new File( "d:/test.txt");boolean fileExists = file.exists();System. out.println( fileExists);//创建文件目录,若父目录不存在则返回falseFile file2 = new File( "d:/fatherDir/subDir");boolean dirCreated = file2.mkdir();System. out.println( dirCreated);//创建文件目录,若父目录不存则连同父目录一起创建File file3 = new File( "d:/fatherDir/subDir2");boolean dirCreated2 = file3.mkdirs();System. out.println( dirCreated2);File file4= new File( "d:/test.txt");//判断长度long length = file4.length();//重命名文件boolean isRenamed = file4.renameTo( new File("d:/test2.txt"));//删除文件boolean isDeleted = file4.delete();File file5= new File( "d:/fatherDir/subDir");//是否是目录boolean isDirectory = file5.isDirectory();//列出文件名String[] fileNames = file5.list();//列出目录File[] files = file4.listFiles();}
2、字节流
字节流对应的类是InputStream和OutputStream。最后应显式关闭IO资源或使用自动关闭资源的try语句。
写文件
public static void writeByteToFile() throws IOException{//创建File实例File file= new File( "d:/test.txt");//要写的内容String hello= new String( "hello word!");byte[] byteArray= hello.getBytes();//因为是用字节流来写媒介,所以对应的是OutputStream //又因为媒介对象是文件,所以用到子类是FileOutputStreamOutputStream os= new FileOutputStream( file);os.write(byteArray);//显式关闭os.close();}
读文件
public static void readByteFromFile() throws IOException{//创建File实例File file= new File( "d:/test.txt");//要存储的数组byte[] byteArray= new byte[( int) file.length()];//用自动关闭资源的try语句try(//因为是用字节流来读媒介,所以对应的是InputStream//又因为媒介对象是文件,所以用到子类是FileInputStreamInputStream is= new FileInputStream( file)){while((int size= is.read( byteArray))>0){System. out.println( "大小:"+size +";内容:" +new String(byteArray));}}catch(IOException ex){ex.printStackTrace();}
}
3、字符流
字符流对应的类应该是Reader和Writer。
写文件
public static void writeCharToFile() throws IOException{File file= new File( "d:/test.txt");String hello= new String( "hello word!");//因为是用字符流来读媒介,所以对应的是Writer,又因为媒介对象是文件,所以用到子类是FileWriterWriter os= new FileWriter( file);os.write( hello);os.close();}
读文件
public static void readCharFromFile() throws IOException{File file= new File( "d:/test.txt");//因为是用字符流来读媒介,所以对应的是Reader//又因为媒介对象是文件,所以用到子类是FileReaderReader reader= new FileReader( file);char [] byteArray= new char[( int) file.length()];int size= reader.read( byteArray);System. out.println( "大小:"+size +";内容:" +new String(byteArray));reader.close();}
4、转换流
可以将字节流转换成字符流。(没有相反的转换)
Reader reader= new InputStreamReader(is);
5、推回输入流
包括PushbackInputStream和PushbackReader。
可使用unread()方法将指定数组内容(字节/字符)推回到缓冲区中。
使用read方法读取的时候,先读取推回缓冲区内的内容,若容器没有满,则继续从推回输入流中读取。
PushbackReader pr=new PushbackReader(new FileReader(file),64);
pr.unread(array);
6、重定向标准输入/输出
System类里提供三个重定向标准输入/输出的方法:
- static void setErr(PrintStream err):重定向“标准”错误输出流
- static void setIn(InputStream in):重定向“标准”输入流
- static void setOut(PrintStream out):重定向“标准”输出流
7、随机读取File文件
RandomAccessFile提供了seek()
方法,用来定位将要读写文件的指针位置,我们也可以通过调用getFilePointer()
方法来获取当前指针的位置。
局限:只可以读写文件。不可向文件指定位置插入内容。
读:
public static void randomAccessFileRead() throws IOException {// 创建一个RandomAccessFile对象RandomAccessFile file = new RandomAccessFile( "d:/test.txt", "rw");// 通过seek方法来移动读写位置的指针file.seek(10);// 获取当前指针long pointerBegin = file.getFilePointer();// 从当前指针开始读byte[] contents = new byte[1024];file.read( contents);long pointerEnd = file.getFilePointer();System. out.println( "pointerBegin:" + pointerBegin + "\n" + "pointerEnd:" + pointerEnd + "\n" + new String(contents));file.close();}
写:
public static void randomAccessFileWrite() throws IOException {// 创建一个RandomAccessFile对象RandomAccessFile file = new RandomAccessFile( "d:/test.txt", "rw");// 通过seek方法来移动读写位置的指针file.seek(10);// 获取当前指针long pointerBegin = file.getFilePointer();// 从当前指针位置开始写file.write( "HELLO WORD".getBytes());long pointerEnd = file.getFilePointer();System. out.println( "pointerBegin:" + pointerBegin + "\n" + "pointerEnd:" + pointerEnd + "\n" );file.close();}
8、缓冲流
见谷歌书签
二、序列化
1、序列化含义
序列化:将一个JAVA对象写入IO流中。
反序列化:将流转换为对象,必须提供class文件,否则会引发ClassNotFound异常。
要使类是可序列化的,必须实现Serializable或Externalizable接口。可序列化对象的父类要么有无参构造器,要么也是可序列化的。
Serializable接口:表明类可序列化,实现它无须实现任何方法。
2、对象引用的序列化
JAVA提供了序列化算法,使得多次输出同一对象时,只有第一次才会将对象转换成字节序列输出,其他输出一个序列化编号。
问题:多次输出同一对象时,发生在中间的改变不会影响输出内容。
3、Java9增加的过滤功能
利用ObjectInputStream的setObjectInputFilter()方法获取过滤器,
使用过滤器的checkInput()方法有三种返回值:
- Status.REJECTED 反序列化停止
- Status.ALLOWED 执行反序列化
- Status.UNDECIDED 继续检查
4、版本
serialVersionUID:用于标识序列化版本,原则上序列化后的数据当中的serialVersionUID与当前类当中的serialVersionUID一致,那么该对象才能被反序列化成功。
这个serialVersionUID的详细的工作机制是:在序列化的时候系统将serialVersionUID写入到序列化的文件中去,当反序列化的时候系统会先去检测文件中的serialVersionUID是否跟当前的文件的serialVersionUID是否一致,如果一致则反序列化成功,否则就说明当前类跟序列化后的类发生了变化,比如是成员变量的数量或者是类型发生了变化,那么在反序列化时就会发生crash,并且回报出错误。
最好自己定义这个类变量,保证反序列化的成功进行。
三、NIO
NIO即新的输入输出,在标准java代码中提供了高速的面向块的IO操作。
1、流与块的比较
NIO和IO最大的区别是数据打包和传输方式。IO是以流的方式处理数据,而NIO是以块的方式处理数据。
面向流的IO一次一个字节的处理数据,一个输入流产生一个字节,一个输出流就消费一个字节。为流式数据创建过滤器就变得非常容易,链接几个过滤器,以便对数据进行处理非常方便而简单,但是面向流的IO通常处理的很慢。
面向块的IO系统以块的形式处理数据。每一个操作都在一步中产生或消费一个数据块。按块要比按流快的多。
2、Buffer
Buffer是一个对象,它包含一些要写入或读出的数据。在NIO中,数据是放入buffer对象的,而在IO中,数据是直接写入或者读到Stream对象的。应用程序不能直接对 Channel 进行读写操作,而必须通过 Buffer 来进行,即 Channel 是通过 Buffer 来读写数据的。
在NIO中,所有的数据都是用Buffer处理的,它是NIO读写数据的中转池。Buffer实质上是一个数组,通常是一个字节数据,但也可以是其他类型的数组。但一个缓冲区不仅仅是一个数组,重要的是它提供了对数据的结构化访问,而且还可以跟踪系统的读写进程。
使用 Buffer 读写数据一般遵循以下四个步骤:
- 写入数据到 Buffer;
- 调用 flip() 方法;
- 从 Buffer 中读取数据;
- 调用 clear() 方法或者 compact() 方法。
flip() 方法:一旦要读取数据,需要通过 flip() 方法将 Buffer 从写模式切换到读模式。在读模式下,可以读取之前写入到 Buffer 的所有数据。
clear() 或 compact() 方法:一旦读完了所有的数据,就需要清空缓冲,让它可以再次被写入。有两种方式能清空缓冲区:调用 clear() 或 compact() 方法。clear() 方法会初始化整个缓冲区(没有删除内容)。compact() 方法只会清除已经读过的数据。任何未读的数据都被移到缓冲区的起始处,新写入的数据将放到缓冲区未读数据的后面。
put()和get()方法:buffer放入或取出数据。
控制buffer状态的变量:
position:位置,跟踪已经写了多少数据或读了多少数据,它指向的是下一个字节来自哪个位置
limit:界限,代表还有多少数据可以取出或还有多少空间可以写入,它的值小于等于capacity。
capacity:容量,代表缓冲区的最大容量,一般新建缓冲区,limit的值和capacity的值相等。不可改变。
mark:可选标记。
3、Channel
Channel是一个对象,可以通过它读取和写入数据。可以把它看做IO中的流。但是它和流相比还有一些不同:
- Channel是双向的,既可以读又可以写,而流是单向的
- Channel可以进行异步的读写
- 程序不能直接访问其中的数据,对Channel的读写必须通过buffer对象
- 有映射数据到buffer的**map()**方法
在Java NIO中Channel主要有如下几种类型:
- FileChannel:从文件读取数据的
- DatagramChannel:读写UDP网络协议数据
- SocketChannel:读写TCP网络协议数据
- ServerSocketChannel:可以监听TCP连接
4、实例
public static void copyFileUseNIO(String src,String dst) throws IOException{//声明源文件和目标文件FileInputStream fi=new FileInputStream(new File(src));FileOutputStream fo=new FileOutputStream(new File(dst));//获得传输通道channelFileChannel inChannel=fi.getChannel();FileChannel outChannel=fo.getChannel();//获得容器bufferByteBuffer buffer=ByteBuffer.allocate(1024);//映射数据到buff,方法第一个参数代表映射模式//buffer=inChannel.map(FileChannel.MapMode.READ ONLY,0,length);while(true){//判断是否读完文件int eof =inChannel.read(buffer);//读完文件,此时 read() 方法会返回 -1 ,可以根据这个方法判断是否读完if(eof==-1){break; }//重设一下buffer的position=0,limit=position,准备读缓冲区,写文件buffer.flip();//开始写文件outChannel.write(buffer);//重置buffer,重设position=0,limit=capacity,准备写缓冲区,读文件,缓冲区内容不变buffer.clear();}inChannel.close();outChannel.close();fi.close();fo.close();
}
四、NIO.2
具体见《Java疯狂讲义》721页后……