• Think in Java ---Chapter 18 I/O系统


    • 对于一个编程语言的IO系统来说,要能够对各种I/O源端和接收端(输入的两端),进行多种不同方式的通信,例如顺序访问,随机存取,缓冲,字符或是二进制,按照行还是字符还是块等。Java类库使用大量类和规整的继承结构来完成这个目的。并且也在不断的更新,例如JDK1.4中添加了nio类来改进性能以及功能。学习Java的IO就是学习类库
    • File类 ---- 处理文件目录的属性
      是IO读写的工具类,表示一个文件或者文件夹的路径,带有对给定路径(绝对路径或是相对路径)操作,指定路径的相关属性(可读可写可执行)的操作,列出当前目录下的文件,文件或文件夹属性的获取,创建临时文件等。常用的方法为:
    		File f= new File("..\..\");
    		
    		System.out.println( "exists: " + f.exists());
    		System.out.println( "isDirectory:  " + f .isDirectory()   );
    		System.out.println( "isFile: " + f .isFile()   );
    		System.out.println( "canExecute: " + f .canExecute()   );
    		System.out.println( "length: " + f .length()   );
    		System.out.println( "lastModified: " + f .lastModified()   );
    		System.out.println( "getFreeSpace: " + f .getFreeSpace() );
    		System.out.println( "getTotalSpace: " + f .getTotalSpace() );
    		
    		System.out.println( "list: " +  Arrays.asList( f .list() )  );
    		System.out.println( "listRoots: " + Arrays.asList(File .listRoots() )  );
    		
    		System.out.println( "getName: " + f .getName()   );
    		System.out.println( "getParent: " + f .getParent()  );
    		System.out.println( "getAbsolutePath: " + f.getAbsolutePath());
    		System.out.println( "getPath: " + f .getPath()   );
    		System.out.println( "getCanonicalPath: " + f .getCanonicalPath()   );
    
    		f = new File(f .getCanonicalPath()+ "\a\b");
    		System.out.println( "mkdirs: " + f .mkdirs()  ); 
    		System.out.println( "mkdir: " + f .mkdir()  ); 
    		System.out.println("delete: " + f .delete()   );
    
    
    
    Output:
    exists: true
    isDirectory:  true
    isFile: false
    canExecute: true
    length: 8192
    lastModified: 1491530973798
    getFreeSpace: 169798930432
    getTotalSpace: 214749409280
    list: [1.bmp, 1.jpg, 11.jpg, 360安全浏览器.lnk, a, Adobe Photoshop CS3.lnk, android-studio-bundle_2.2.0.0.exe, backup,  ChromeStandalone_57.0.2987.133_Setup.exe, desktop.ini, Eclipse Java Neon.lnk, eclipse-inst-win32.exe, Flash.lnk, HADOOP3(jb51.net).rar, hostednetwork.BAT, link.jpg, Microsoft Excel 2010.lnk, Microsoft PowerPoint 2010.lnk, Microsoft Word 2010.lnk, pycrypto-2.6.1, pycrypto-2.6.1.tar.gz, python-2.7.13.msi]
    listRoots: [C:\, D:\, E:]
    getName: ..
    getParent: ..
    getAbsolutePath: C:UserslenovoDesktopworkspaceThinkInJava....
    getPath: ....
    getCanonicalPath: C:UserslenovoDesktop
    mkdirs: true
    mkdir: false
    delete: true
    
    

    File对象可代表一个文件路径或者一个文件夹路径。
    程序的初始路径为 C:UserslenovoDesktopworkspaceThinkInJava 。然后使用了一个相对路径 ..\..\ 创建File对象。
    其中要注意 getName , getParent ,getAbsolutePath, getPath, getCanonicalPath的区别。
    getName 简单的返回File对象的尾部后缀。
    getParent 简单的返回File对象的倒数第二个后缀。
    getAbsolutePath 返回 当前初始路径 + File的路径。即使路径是错误的。
    getPath 返回Path代表的路径。
    getCanonicalPath 如果路径正确,对路径进行求值,包括将相对路径展开,然后根据特定的操作系统路径规则进行修饰,例如windows系统的大写逻辑分区字母,Unix系统的符号链接等。因此是需要异常捕捉的。

    mkdir 创建目标目录,中间路径必须存在。
    mkdirs 创建目标目录,若中间路径没有创建,则自动创建,类似Linux的 mkdir -m 命令。

    list 返回File目录下的所有文件或文件夹的字符串形式。
    listFile 同上,返回的是File对象数组。

    renameTo 接受一个File对象,将this对象的路径改为传入的File指定路径。

    • File目录的排序和过滤

    过滤是通过将一个实现了 FilenameFilter 接口的对象传给 File.list() 方法完成的,需要实现 accept 方法,list方法会使用回调方式将该文件所在目录的File对象和文件名传入调用 FilenameFilter对象的accept方法,这是一个 策略模式 的应用。 而排序可以使用Arrays.sort对list返回的字符串列表进行排序。

    class DirFilter implements FilenameFilter{
    	private Pattern filstr;
    	public DirFilter(String filstr) {
    		this.filstr = Pattern.compile(filstr);
    	}
    	@Override
    	public boolean accept(File arg0, String arg1) {
    		if(filstr.matcher(arg1).matches())return true;
    		return false;
    	}
    }
    public class 啊 {
    
    
    //      也可以使用匿名内部类来写过滤方法
    
    //	public static FilenameFilter filter(final String regex){
    //		return new FilenameFilter(){
    //			private Pattern filstr= Pattern.compile(regex);
    //			@Override
    //			public boolean accept(File dir, String name) {
    //				if(filstr.matcher(name).matches())return true;
    //				return false;
    //			}
    //			
    //		};
    //	}
    	public static void main(String[] args) throws IOException {
    		啊 a = new 啊();
    		
    		File f= new File("..\..\");
    		String[] li = f .list(new DirFilter(".*"));
    		Arrays.sort(  li , String.CASE_INSENSITIVE_ORDER );
    		for( String s : li){
    			System.out.println( s );
    		}
    
    	}
    }
    
    

    也可以使用匿名内部类来写过滤方法,这样就可以使 FilenameFilter 和 所在类 紧密的捆绑在一起。当然,也可以更进一步使用下面的方式。

    		String[] li = f .list(new FilenameFilter() {
    			@Override
    			public boolean accept(File dir, String name) {
    			 Pattern filstr= Pattern.compile(".*?(\.txt|\.zip)");
    				if(filstr.matcher(name).matches())return true;
    					return false;
    			}
    		});
    
    

    另外,使用File.listFiles 进行递归可以完成整个目录树的遍历。
    注意: 把统计逻辑放在FilenameFilter.accept方法里是很常见的用法, 例如统计总文件大小,统计各种类型的文件的数量等, 但是 任何传给匿名方法的参数需要是final类型的, 而方法内修改外部变量只能是静态变量才行。 ,这是匿名类可以访问外部变量的方法。main 方法的args参数是final型的。

    • 如果类只使用一次,就创建匿名内部类。
    • 流 的概念屏蔽了实际IO设备中处理数据的细节。有关IO的类层次结构可以再JDK文档中看到。类可以分为字节流处理和字符流处理两种类型。也可以按照读和写两种类型分类,Inputstream和Reader作为父类提供了read等共同的方法,OouputStream和Writer作为父类型提供了write等共有的方法。也可以分为实体流类和处理类,实体流处理真实的读写,而处理类可以叠加到实体流类上增强功能。这是 装饰器设计模式 的应用。

    • InputStream是字节流输入基类,其子类包含文件,管道,字节,对象,字符串等各种数据源,还有作为装饰类使用的FilterInputStream类,是各种例如缓存(BufferedInputStream装饰器类内部每次读取一块,然后返回读取的部分,这样就不用每次都启动IO。代理模式。BufferedoutputStream类似),数据读取(DataInputStream扩展了一系列的writeByte,writeFloat等方法,自带转型。DataOutputStream类似。),回退特性和PrintStream(FilterOutputStream的子类,格式化特性。重载了print和println方法,可打印出各种数据类型。但是这个类不支持国际化,平台相关的换行动作)装饰的父类。

    ByteArrayInputStream 将内存缓冲区作为inputstream
    StringBufferInputStream 将字符串String作为inputstream
    FileImputStreamPipedInputStream 将多线程中的管道作为inputstream
    SequenceInputStream 将两个或多个inputstream转换成一个inputstream

    以上的各个数据源都可以使用FilterInputStream的装饰类增强功能。

    与其对应的OutputStream下也有类似的装饰器,多数据源outputstream类结构。

    装饰器根据设计模式必须具有和他所装饰的对象相同的接口,然后重写父类的接口,在其之上提供服务,也可以扩展接口。
    因此,从上面的类继承图来看,可以实现很多功能。
    例如将FileInputStream和DataInputstream结合,也可增加BufferedInputStream装饰,可以完成数据类型的序列化存储。但是序列化和反序列化顺序必须保持一致。
    例如将内存的字节使用BytesInputStream,然后用PushBackInputStream装饰后,可以随机访问。

    而对于字符流,则Reader和Writer提供了兼容Unicode的功能。类结构也与字节流体系类似。

    即在使用IO时:

    字符和字节,有两种选择。
    数据在哪里,哪里读哪里写,有多种选择。
    可以加上额外特性,有四种选择。(还有一种已弃用的 LineNumberInputStream 类型,可以跟踪输入流的行号,调用该装饰类扩展的getLineNumber和setLineNumber控制行号)

    是不是很简单。

    • 对于要把来自 字节 层次结构的类和 字符 层次结构中的类结合使用,这时要用适配器类 : InputStreamReader将InputStrean转Reader,对应的还有 OutputStreamWriter。

    • 字符层次的出现是为了支持国际化,即能够以2B为单位解释字节。

    • 当使用readLine()时,不能使用DataInputStream,而应该用BufferedReader。

    • RandomAccessFile类不属于上面的继承结构。实现了DataInput和DataOutput接口(DataInputStream和DataOutputStream也实现了这两个接口)。这个类适于大小已知的记录文件,记录可以大小不同,依赖于程序员自己的认识。然后使用seek()移动指针。只有这个类支持文件的随机访问方法。JDK1.4中,大部分功能由nio存储映射文件取代。
      工作方式和DataInputStream和DataOutputStream另外添加了一些方法相同。
      构造器接受一个类似C中open函数的读取方式的参数
      getFilePointer
      seek
      length

    • read方法读到文件尾部,会返回-1。若使用类似readByte这种一次一个字节的方法,则使用avaliable是否为0来判断文件尾部。

    • 常用组合:
      文件复制

    
    //     BufferedInputFile 是一个工具类,可以将文件读入以字符串返回。
    BufferedReader in = new BufferedReader( new StringReader( BufferedInputFile.read("input.java") ) );
    PrintWriter    out = new PrintWriter( new BufferedWriter( new FileWriter( "output.java" ) ) );
    //    PrintWriter    out = new PrintWriter( "output.java"  );    也可以
    int lineCount = 1;
    String s;
    while((s = in.readLine()) != null){
        out.println(lineCount++ + ": " + s);
    }
    out.close();            //如果不显式关闭, 则缓冲区内容不会被刷新清空。
    
    
    • 常用组合:
      数据类型存储和回复
    DataOutputStream out = new DataOutputStream( new FileOutpurStream("Data.txt") );
    out.writeDoule(3.141592653589193238462643383279);
    out.writeUTF("I am a dog");
    out.close();
    DataInputStream in = new DataInputStream( new BufferedInputStream( new FileInputStream("Data.txt") ) );
    System.out.printf(in.readDouble());
    System.out.printf(in.readUTF());
    
    

    只需注意要以相同顺序读出即可。

    • RandomAccessFile 的使用
    RandomAccessFile rd = new RandomAccessFile ("input.t", "r");
    rd.readDouble();
    rd.readUTF();
    rd.close();
    rd = new RandomAccessFile ("input.t", "wr");
    rd.seek(1*8);   //跳过一个Double类型
    rd.writeUTF("this  is a string");
    rd.close();
    
    
    • 标准IO也可以使用装饰类封装,System.out和System.err都是被包装成了PrintStream对象,而System.in是未经过包装的InputStream对象,我们可以将其先转换成Reader然后添加缓冲装饰。 例如如下代码
    BufferedInputReader in = new BufferedReader( new InputStreamReader( System.in ) );
    String s;
    while(( s = in.readLine()) != null && s.length() != 0){
        System.out.println(s);
    }
    
    
    • 一般情况下流都应该加上缓冲。
    • System.out是PrintStream类型, 即OutputStream类型。另一边字节流中的PrintWriter提供了一个OutputStream作为参数的构造器,因此可以使用它进行转换。
    PrintWriter out = new PrintWriter( System.out, true );
    
    

    PrintWriter 构造方法接受第二个参数,为true则开启自动清空功能,否则可能会看不到结果。

    • java支持标准IO重定向。System类提供了静态方法SetIn(InputStream)、SetOut(PrintStream)、SetErr(PrintStream)方法,可以将标准IO替换为指定的流对象。
    PrintStream conso = new System.out;
    BufferedInputStream in = new BufferedInputStream( new FileInputStream( "in.file" ) );
    PrintStream out = new PrintStream( new BufferedOutputStream( new FileOutputStream( "test.out" ) ) );
    System.setIn(in);
    System.setOut(out);
    System.setErr(out);
    BufferedReader br = new BufferedReader( new InputStreamReader( System.in ); );
    String s;
    while(( s = br.readLine() ) != null){
    System.out.println(s);
    }
    out.close();
    System.setOut(conso);        //恢复out
    
    
    • 要记住操作的是字符流还是字节流,使用对应的方法。
    • 使用Java执行系统命令,然后输出结果的代码。
    	void static OSProcess(String command) throws IOException{
    		Process p = new ProcessBuilder(command.split(" ")).start();
    		
    		BufferedReader in =  new BufferedReader(new InputStreamReader(p.getInputStream()));
    		BufferedReader err = new BufferedReader(new InputStreamReader(p.getErrorStream()));
    		
    		String s;
    		boolean error = false;
    		while( (s = in.readLine()) != null ){
    			System.out.println(new String(s.getBytes(), "utf8"));
    		}
    		while( (s = err.readLine()) != null ){
    			error = true;
    			System.out.println(new String(s.getBytes(), "utf8"));
    		}
    		if(error){
    			//new Exception("命令执行有错误");
    		}
    	}
    
    

    例如,调用 OSProcess("cmd.exe /c netstat -a");,结果为

      TCP    0.0.0.0:135            lenovo-PC:0            LISTENING
      TCP    0.0.0.0:445            lenovo-PC:0            LISTENING
      TCP    0.0.0.0:1688           lenovo-PC:0            LISTENING
      TCP    0.0.0.0:2869           lenovo-PC:0            LISTENING
      TCP    0.0.0.0:49152          lenovo-PC:0            LISTENING
      TCP    0.0.0.0:49153          lenovo-PC:0            LISTENING
      TCP    0.0.0.0:49154          lenovo-PC:0            LISTENING
      TCP    0.0.0.0:49155          lenovo-PC:0            LISTENING
      TCP    0.0.0.0:49159          lenovo-PC:0            LISTENING
      TCP    127.0.0.1:4000         lenovo-PC:0            LISTENING
      TCP    127.0.0.1:4000         lenovo-PC:49365        ESTABLISHED
    
    
    • java.nio.* 包中引入了新的高性能IO库。而且旧的IO包也使用nio重构了。nio操作以Buffer为基本类型,使用类似于物理视图和逻辑视图的概念。其中ByteBuffer对象可以看成物理视图,而CharBuffer等是逻辑视图。物理视图是直接进行IO的数据,逻辑视图是将ByteBuffer解释成其他基本数据类型的方式。

    • 旧IO中FileInputStream, FileOutputStream, RandomAccessFile这三个 字节流类型 被修改成可以使用getChannel获取到相应的通道对象,然后在通道对象上使用nio的一套东西快速读写。
      下面是使用nio读写文件,并且移动指针的例子。

    		FileChannel fc = new FileOutputStream("test.txt").getChannel();
    		
    		fc.write(ByteBuffer.wrap("hello word".getBytes()));
    		fc.close();
    		
    		fc = new RandomAccessFile("test.txt", "rw").getChannel();
    		fc.position(fc.size());
    		fc.write(ByteBuffer.wrap(" end of file".getBytes()));
    		fc.close();
    		fc = new FileInputStream("test.txt").getChannel();
    		
    		ByteBuffer buff = ByteBuffer.allocate(100);
    		fc.read(buff);
    		buff.flip();        //在read后必须要调用,更新buff准备再次使用。
    		while(buff.hasRemaining()){
    			System.out.print ((char)buff.get());
    		}
    
    

    wrap工具方法可以将字符串转换为字节写入Buffer。
    对于ByteBuffer, 一次read, 一次flip。
    一次write, 一次clear。
    例如

    ByteBuffer buf = ByteBuffer.allocate(1024);
    while(in.read(buf) != -1){
    buf.flip(); //准备写。
    out.write(buf);
    buf.clear();    //准备读
    }
    
    
    • 通道只能处理ByteBuffer对象,并且使用FileInputStream, FileOutputStream, RandomAccessFile这三者的附加特性来读写。通道还可以添加文件锁或者部分锁。这点实际上是Linux低层文件锁的封装。
    • 两个通道对象即FileChannel 对象可以使用transferFrom或transferTo链接起来。
    • ByteBuffer有一系列视图缓冲器方法,提供基本类型的put和get,例如
    ByteBuffer buf = ByteBuffer.allocate(1024);
    bb.asCharBuffer().put("hello");
    while(( c = bb.getChar()) != 0){ printnb( c );;}
    bb.rewind();      //重置指针
    
    bb.asShortBuffer().put((short)13412323);    //只有short需要显式转型
    printnb( bb.getShort() )
    bb.rewind();      //重置指针
    
    bb.asLongBuffer().put(12314412313);
    printnb( bb.getLong() )
    bb.rewind();      //重置指针
    
    bb.asFloatBuffer().put(.23);
    printnb( bb.getFloat() )
    bb.rewind();      //重置指针
    
    bb.asDoubleBuffer().put(.434345);
    printnb( bb.getDouble() )
    bb.rewind();      //重置指针
    
    

    视图缓冲器就是将byte封装成各种基本类型然后供使用。对视图缓冲器修改会映射为对ByteBuffer的修改。
    即如图所示


    将字节解释成什么数据取决于选取的视图缓冲器的类型。这和String的encode,decode的原理相同。

    • ByteBuffer和网络字节序相同,使用大端字节序 ,也可以使用
      order(ByteOrder.LITTLE_ENDIAN)修改字节序。其实现是以字节为单位逆置字节数组。

    • 缓冲器的实现围绕 mark标记, position位置, limit界限, capacity容量 这四个索引构成。

    • 使用ByteBuffer或者视图缓冲器的get或put会自动移动position位置到下一字节,mark方法在当前position位置做标记,当调用reset方法时使position恢复到mark位置处。

    • 利用这种机制对buffer排序。

    • 内存映射文件即部分读入文件的一部分,对于程序员来看可以假定整个文件都读入到了内存。
      MappedByteBuffer 继承了ByteBuffer,因此有ByteBuffer的方法,例如get, put, asCharBuffer等。

    MappedByteBuffer out = new RandomAccessFile("test.dat","rw").getChannel(). map(FileChannel.MapMode.READ_WRITE, 0, 0x8FFFFFF);
    for( int i = 0 ; i < 0x8FFFFFF; i++){
    out.put((byte)'X');
    }
    for( int i = 0 ; i < 8; i++){
    printnb((char)out.get(i));
    }
    
    
    • 文件锁机制会映射到本地OS的加锁, 因此对文件的加锁会参与到其他Java的JVM进程、Java线程或者其他本地线程的竞争中。
    FileOutputStream fos = new FileOutputStream("file.txt");
    FileLock fl = fos.getChannel().tryLock();        // lock()是对应的阻塞版本。
    if(tl != null){ //加锁成功
        TimeUnit.MILLISECONDS.sleep(100);    
        fl.release();
        System.out.println("release lock");
    }
    fos.close();
    
    

    lock()是tryLock()对应的阻塞版本,直到索取到锁、所用的通道关闭或者被中断的时候返回。
    lock()和tryLock()参数相同,有重载方法可以对区域加锁。
    对于SocketChannel, DatagramChannel和ServerSocketChannel不需要加锁,因为他们一般是进程独占的。一般也不会和其他进程共享。

    • 对文件共享锁的支持受操作系统的支持,如果不支持共享锁,则实际上使用的是独占锁。
    • Java支持读写压缩格式的数据流。压缩类似于包装类的使用,这些类属于字节流的继承层次。如果要使用字节形式可以用InputStreamReader和OutputStreamWriter转换流格式即可。
      常见的压缩类有:
      CheckedInputStream 可以给字节流输入计算检查和
      CheckedOutputStream 可以给字节流输出计算检查和
      DeflaterOutputStream 压缩类的基类
      ZipOutputStream 将数据压缩
      GZIPOutputStream 将数据压缩
      InflaterInputStream 解压缩类的基类
      ZipInputStream 将数据解压缩
      GZIPInputStream 将数据解压缩

    下面是一个例子。

    BufferedReader in = new BufferedReader(new FileReader("inputfile"));
    BufferedOutputStream out = new BufferedOutputStream(  new GZIPOutputStream(new FIleOutputStream("testout")));
    int c;
    while((c = in.read() != -1))
        out.write(c);
    in.close();
    out.close();
    
    BufferedReader in2 = new BufferedReader(new InputStreamReader(new GZIPInputStream(FileInputStream("input"))););
    String s;
    while((s = in2.readLine()) != null){
        System.out.println(s);
    }
    
    
    
    • 对象序列化反序列化的主要用处在于 网络传输(例如RMI的参数传递)和持久化存储。
    • 对象序列化和反序列化的对象必须实现Serializable接口,这个是个空接口,从而该类型的对象可以转换成字节序列。例如Hadoop自己实现的序列化格式。在执行序列化中也使用了策略模式。
    • 对象序列化使用字节继承层次结构,即InputStream和OutputStream下面的流对象。使用装饰类ObjectInputStream和ObjectOutputStream。
    • 对象序列化也会保存对象内部所包含的引用关系,并且保存那些对象。因此,最好不要自己动手,让java使用优化的算法自动维护整个对象网。
    • transit元素不会被序列化。
    • 要想对序列化得到更好的控制,可以实现接口Externalizable。该接口有两个方法: writeExternal和readExternal。这两个方法是序列化和反序列化时操作输入流和输出流读取写入对象的方法。并且,使用这个接口会在反序列化对象时调用这个类的默认构造方法。而相对于Serializable接口却不会在反序列化时调用任何构造方法。
    • 注意, 要序列化的类的构造器必须是public的 ,因为在反序列化的时候java会创建该类的实例,如果是私有构造器则无法创建类对象。

    总结一下Serializable 和Externalizable 区别:

    1.前者使用transit 修饰字段不进入序列化过程,并且对其他字段统一进行序列化。后者默认不对全部变量序列化,序列化和反序列化的操作在接口中需要编写。
    2.前者相比较后者来说提供了自动的序列化控制。当需要对序列化和反序列化过程进行更多的控制的时候,就需要使用后者。

    • 只要将任何对象序列化到单一流中,就可以恢复出与我们写出时候一样的对象网,并且没有任何意外重复复制出的对象。例如,当我们将一个包含其他类引用的类对象实例化writeObject两次到一个序列化流中,当我们读出的时候这个对象只有一个在堆中。而当将这个对象序列化到另一个流中时,则会新构建了另一个对象网。
  • 相关阅读:
    springboot实现redis的分布式锁
    剑指offer--二维数组中查找
    剑指offer--二维数组中查找
    对JDK动态代理的模拟实现
    Spring(4)AOP
    设计模式之单例模式(Java)
    【Java并发系列】--Java内存模型
    maven 解决jar包冲突及简单使用
    基于注解的SpringAOP源码解析(三)
    Java代码中可以优化性能的小细节
  • 原文地址:https://www.cnblogs.com/yumingle/p/6674265.html
Copyright © 2020-2023  润新知