Java 基础回顾-7:IO 体系

Java IO流体系继承结构图_动力节点Java学院整理 主要介绍了Java IO流体系继承结构图,非常不错,具有参考借鉴价值,需要的朋友可以参考下 立即下载

1、Java IO 体系

下图是 Java 中的 IO 体系及其之间的继承关系。

IO 流主要分成字节流和字符流两大类,分别用来向磁盘以字节和字符的形式进行读写。抽象类 InputStream 和 OutputStream 构成了输入/输出 (IO) 类层次的结构基础。因为 Java 中使用的 Unicode 都是双字节的,所以抽象类 Reader 和 Writer 就是设计了的专门用于处理Unicode 字符的单独的类层次结构。即 InputStream 和 OutputStream 是基于单字节的,而 Reader 和 Writer 是基于双字节的。

Java IO 体系

当完成对流的读写,应该通过 close() 关闭它,这将释放有限的操作系统资源。关闭输出流还会冲刷用于该输出流的缓冲区:所有被临时置于缓冲区中,以便使用更大的包的形式传递的字符在关闭输出流时都将被送出。如果不关闭文件,那么写出字节的最后一个包可能永远得不到传递。此外,也可以使用 flush() 方法人为地冲刷输出。

直接对数据源进行操作的流叫做节点流,其他流可以在它们的基础之上进行一层包装来实现自己的逻辑。那些不是直接对数据源进行操作的流就是非节点流。这是两个基本的概念,后面的讲到 IO 体系的设计的时候会用到这两个概念。

装饰者模式

Java 的 IO 系统的设计使用了装饰者设计,注意这里的FilterInputStream,它的定义是:

    public class FilterInputStream extends InputStream {

        protected volatile InputStream in;

        protected FilterInputStream(InputStream in) {
            this.in = in;
        }

        public int read() throws IOException {
            return in.read();
        }

        // ...其他方法,它内部还定义了一些其他的方法,这里我们只给出一部分
    }

从这里也可以看出,它内部维护了一个 InputStream 对象,并在构造方法对其进行赋值,然后将具体的方法的实现都在内部交给它来执行。

理解了这个设计原理,我们就会容易得理解上面这张图的框架的设计的层次。InputStream 是一个抽象的基类,它的直接子类中除了上面提到的 FilterInputStream,都对应于不同的输入类型。FilterInputStream 是一个抽象的装饰器,它的子类分别代表着不同的功能。而 FilterInputStream 的作用则是进行装饰,意在对来自不同的输入类型的结果进行一层包装。

使用装饰者模式的好处是:用户可以使用装饰器组合来实现流操作的复杂功能,某些流(节点流)能够从文件或其他位置获取字节,而其他流可以将字节组装成有用的数据类型,也就是责任是分开的。

2、字节流的读写

2.1 二进制文件的读写

FileInputStream 和 FileOutputStream 提供了附着在磁盘文件的输入流和输出流,只要像其构造器传入文件名或者文件路径即可。

    // 用来打开指定文件对应的输入流
    public FileInputStream(String name)
    public FileInputStream(File file)
    public FileInputStream(FileDescriptor fdObj)

    // 用来打开指定文件对应的输出流
    public FileOutputStream(File file) 					 
    public FileOutputStream(File file, boolean append)  	

1.关于文件分隔符的说明:
反斜杠 \ 是转义字符,因此在写文件路径的时候要使用 \\ 或者 /(实际的文件路径只有一个反斜杠)。但是,为了使程序便于移植最好使用常量字符串 java.io.File.separator 来获取文件分隔符。
2.关于 append 参数:
如果 append 为 true,那么数据将被添加到文件尾,而具有相同名字的文件不会被删除;否则,该方法会删除所有具有相同名字的已有文件。

一个示例程序,

    public static void main(String ...args) {
        File file = new File("." + File.separator + "io_test.txt");
        try {
            boolean succeed = file.exists() ? (file.delete() && file.createNewFile()) : file.createNewFile();
            // output
            OutputStream fos = new FileOutputStream(file);
            String str = "字节流的写入和读取的测试字符串";
            fos.write(str.getBytes());
            // input
            byte[] bytes = new byte[1024];
            InputStream fis = new FileInputStream(file);
            int ret = fis.read(bytes);
            System.out.println(new String(bytes));
            System.out.println(ret);
            fos.close();
            fis.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

=======================================================================
输出结果:

	字节流的写入和读取的测试字符串
    45

2.2 基本数据类型的读写

基本数据类型的读写需要使用 DataIntputStream 和 DataOutputStream. DataInputStream 和 DataOutputStream 不是节点流,无法从磁盘中直接读取字节,它只有一个构造方法,需要传入节点流:

    DataInputStream(InputStream in) 
    DataOutputStream(OutputStream out) 

该类具有一些列的读写数据类型的方法,

    byte readByte()   
    char readChar()  
    // ...其他的数据类型的读取

    void writeByte(int v)      将一个 byte 值以 1-byte 值形式写出到基础输出流中。 
    void writeBytes(String s)  将字符串按字节顺序写出到基础输出流中。 
    void writeChar(int v)      将一个 char 值以 2-byte 值形式写入基础输出流中,
    void writeChars(String s)  将字符串按字符顺序写入基础输出流。 
    void writeDouble(double v)
    // ...其他的数据类型写入

示例程序,

    public static void main(String ...args) {
        File file = new File("." + File.separator + "data_io.txt");
        Person person = new Person("Shhng", "Wng", 20, true, 10000);
        try {
            boolean succeed = file.exists() ? (file.delete() && file.createNewFile()) : file.createNewFile();
            OutputStream os = new FileOutputStream(file);
            DataOutputStream dos = new DataOutputStream(os);
            dos.writeUTF(person.firstName);
            dos.writeUTF(person.lastName);
            dos.writeInt(person.age);
            dos.writeBoolean(person.male);
            dos.writeDouble(person.wage);
            InputStream is = new FileInputStream(file);
            DataInputStream dis = new DataInputStream(is);
            Person ps = new Person(
                    dis.readUTF(),
                    dis.readUTF(),
                    dis.readInt(),
                    dis.readBoolean(),
                    dis.readDouble());
            System.out.println(ps);
            os.flush();
            os.close();
            is.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static class Person {
        String firstName;
        String lastName;
        int age;
        boolean male;
        double wage;

        Person(String firstName, String lastName, int age, boolean male, double wage) {
            this.firstName = firstName;
            this.lastName = lastName;
            this.age = age;
            this.male = male;
            this.wage = wage;
        }

        @Override
        public String toString() {
            return "Person{" +
                    "firstName='" + firstName + '\'' +
                    ", lastName='" + lastName + '\'' +
                    ", age=" + age +
                    ", male=" + male +
                    ", wage=" + wage +
                    '}';
        }
    }

=======================================================================
输出结果:

    Person{firstName='Shhng', lastName='Wng', age=20, male=true, wage=10000.0}

说明:
如果文件中不包含指定的数据类型的话,就会弹出异常。输出的类型的顺序要和输入的顺序严格匹配,不然可能会有读写错误。

2.3 使用字节缓冲流提高读写效率

缓冲流不是节点流,需要为其指定一个节点流,它的作用是创建一个缓冲区,从而不必每次读入字符时都访问设备。一般将缓冲流放在节点流和另一种流之间,前者用于访问设备数据,后者提供获取各种数据类型的方法。

缓冲流也提供了 BufferedInputStream 和 BufferedOutputStream,分别用做字节缓冲输入流和字节缓冲输出流。它们的构造方法定义如下,

    // 创建带缓冲区的流,带缓冲区的流在从流中读入字符时,不会每次都对设备访问,当缓冲区为空时,会向缓冲区读入一个新的的数据块。
    BufferedInputStream(InputStream in, int size) 

    // 创建一个带缓冲的流,带缓冲区的输出流在收集要写出的字符时,不会每次都对设备访问,当缓冲区填满或者流被冲刷时,数据被写出。
    BufferedOutputStream(OutputStream out, int size)

示例程序,

    public static void main(String ...args) {
        File fileToCopy = new File("." + File.separator + "who will succeed.mp4");
        File fileCopied = new File("." + File.separator + "who will succeed(copy).mp4");
        try {
            boolean succeed = fileCopied.exists() ? (fileCopied.delete() && fileCopied.createNewFile()) : fileCopied.createNewFile();
            if (!succeed) System.out.println("error to delete or create");
            copyWithBuffer(fileToCopy, fileCopied);
            boolean succeed2 = fileCopied.exists() ? (fileCopied.delete() && fileCopied.createNewFile()) : fileCopied.createNewFile();
            if (!succeed2) System.out.println("error to delete or create");
            copy(fileToCopy, fileCopied);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void copy(File fileToCopy, File fileCopied) throws IOException {
        long st = System.currentTimeMillis();
        InputStream is = new FileInputStream(fileToCopy);
        OutputStream os = new FileOutputStream(fileCopied);
        int b;
        while ((b = is.read()) != -1) {
            os.write(b);
        }
        os.flush();
        os.close();
        is.close();
        System.out.println(System.currentTimeMillis() - st);
    }

    private static void copyWithBuffer(File fileToCopy, File fileCopied) throws IOException {
        long st = System.currentTimeMillis();
        InputStream is = new FileInputStream(fileToCopy);
        OutputStream os = new FileOutputStream(fileCopied);
        BufferedInputStream bis = new BufferedInputStream(is, 15 * 1024);
        BufferedOutputStream bos = new BufferedOutputStream(os, 15 * 1024);
        int b;
        while ((b = bis.read()) != -1) {
            bos.write(b);
        }
        bos.flush();
        bos.close();
        bis.close();
        os.close();
        is.close();
        System.out.println(System.currentTimeMillis() - st);
    }

===========================================================================
输出结果:

	564
    64606

3、字符流的读写

以上代码的输出都是以字节输出的二进制,通过记事本打开的都是“乱码”。所以,Java 提供了 Unicode 字符流的写入和读取的类,用于对文本类型进行读写。

3.1 字符数据的读写

FileWriter 和 FileReader 类使用 Window 默认字符编码 GB 2112 的字符输入输出。若要指定文本编码方式可以使用 InputStreamReader 类和 OutputStreamWriter 类。这两种流都不是节点流,需要向其中传入节点流创建对象,所以,其机制大致是先使用节点流从设备读取字符,然后使用指定字符集编码。注意这里传入的节点流是字节流,InputSteam 和 OutputSteam 及其子类,而不是 FileReader 和 FileWriter.

字符输入/输出流也不是节点流,需要向其传入一个节点流,其构造方法,

    // 字符输入
    InputStreamReader(InputStream in)                       // 使用默认字符集
    InputStreamReader(InputStream in, Charset cs) 
    InputStreamReader(InputStream in, String charsetName) 

    // 字符输出
    OutputStreamWriter(OutputStream out)                    // 使用默认字符编码
    OutputStreamWriter(OutputStream out, Charset cs) 		
    OutputStreamWriter(OutputStream out, String charsetName) 

3.2 使用字符缓冲提高读写速度

对应地,字符流也提供了缓冲流用来提升读写的效率。它们是 BufferedWriter 和 BufferedReader. BufferedWriter 使用了装饰器模式,用来对传入的 Writer 进行一层封装。其构造方法是

    // 字符写缓冲流
    BufferedWriter(Writer out)

    // 字符读缓冲流
    BufferedReader(Reader in)

3.3 文本文件的读写

这里的 FileReader 和 FileWriter 两个类分别继承了 OutputStreamReader 和OutputStreamWriter. 相对于它们的父类而言,除了构造方法,它们没有增加任何新的方法。它们跟父类的区别仅在于,它们在构造方法中需要传入文件相关的信息,然后直接使用这些信息创建了 FileOutputStream 和 FileInputStream,并将创建的实例传递给父类的构造方法。

FileReader 继承了OutputStreamReader,其构造方法为:FileReader(...). 然后可以使用该类提供的read()方法进行单个字符的读取

FileWriter 与 FileReader类似,实际上内部使用了 FileOutputStream 来获取输出流的。该类提供了写出字符的方法: write(int c).

示例程序,

    public static void main(String ...args) {
        File file = new File("." + File.separator + "data_io.txt");
        try {
            boolean succeed = file.exists() ? (file.delete() && file.createNewFile()) : file.createNewFile();
            StringBuilder sb = new StringBuilder();
            for (int i=0;i<100000;i++) {
                sb.append((char) i);
            }
            writeToFile(file, sb.toString());
            readFile(file);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void readFile(File file) throws IOException {
        Reader reader = new FileReader(file);
        BufferedReader br = new BufferedReader(reader, 10 * 1024);
        String s ;
        while ((s = br.readLine()) != null) {
            System.out.println(s);
        }
        reader.close();
        br.close();
    }

    private static void writeToFile(File file, String s) throws IOException {
        Writer writer = new FileWriter(file);
        BufferedWriter bw = new BufferedWriter(writer);
        bw.write(s);
        bw.flush();
        bw.close();
        writer.close();
    }

=====================================================================
输出结果:

    惨不忍睹

3.4 文本的读写

对文本输出,可以使用 PrintWriter,该类具有以文本格式打印字符串和数字的方法,该类的构造方法比较多,根据传入的文件方式,大致可以将其分成下面四种主要类型,其他方法只是在这四种方式的基础上,规定了编码方式和是否启用自动刷新,

    PrintWriter(File file) 
    PrintWriter(OutputStream out) 
    PrintWriter(String fileName) 
    PrintWriter(Writer out) 

该类还有一系列的输出数据类型的方法,比如

    print(基本数据类型 var)
    println(基本数据类型 var)
    write(String s) 
    write(int c) 
    print(Object obj) 

可以用它来打印数字、字符、boolean 值、字符串和对象。

在 Java SE5 之前,处理文本输入的唯一方式是 BufferReader 类,它拥有 readLine 方法,可以读入一行文本。但是它没有任何用于读入数字的方法,而现在可以使用 Scanner 来读如文本输入。

示例程序,

	package ioprojects06;
	
	public class Prog06 {
		private final static String seperator = java.io.File.separator;
		private final static String Parent = 
			"C:" + seperator + "Users" + seperator + "wangshouheng" 
	        + seperator + "Documents" + seperator + "JavaIO" + seperator;
		private final static String fileName = "文本输入输出.txt";
		public static void main(String[] args) throws IOException {
			testPrintWriter();
			testBufferReader();
			testScanner();
		}
		private static void testPrintWriter() throws FileNotFoundException{
			PrintWriter pWriter = new PrintWriter(Parent + fileName);
			pWriter.println("四大名著");
			pWriter.println(32.24);
			pWriter.println(true);
			pWriter.println(pWriter);
			pWriter.flush();  pWriter.close();
		}
		private static void testScanner() throws FileNotFoundException{
			Scanner scan = new Scanner(new File(Parent + fileName));
			System.out.print(scan.nextLine() + " ");
			System.out.print(scan.nextDouble() + " ");
			System.out.print(scan.nextBoolean() + " ");
			System.out.print(scan.next() + " ");
			scan.close();
		}
		private static void testBufferReader() throws IOException{
			BufferedReader bReader = new BufferedReader(new FileReader(Parent + fileName));
			String line;
			while((line = bReader.readLine())!=null)  
                System.out.print(line + " ");
			System.out.println();
		}
	}

=================================================================
输出结果:

    四大名著 32.24 true java.io.PrintWriter@499a12ee 
    四大名著 32.24 true java.io.PrintWriter@499a12ee 

4、随机文件的访问

随机文件访问用于,比如 MP3 播放的快进功能,跳到指定位置进行播放.

使用随机文件的时候,需要先创建 RandomAccessFile 对象。其构造方法如下,

    RandomAccessFile(File file, String mode) 
    RandomAccessFile(String name, String mode)

说明:关于mode的值与含义

符号含义
r以只读方式打开
rw打开以便读取和写入
rws打开以便读取和写入,对于 “rw”,还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备
rwd打开以便读取和写入,对于 “rw”,还要求对文件内容的每个更新都同步写入到底层存储设备

对于写出,该类提供了以下几个重要的方法:

    void write(int b)  
    void writeBoolean(boolean v)    除此之外还有其他的基本数据类型

对于读取,该类提供了以下几个重要的方法:

    int read() 
    boolean readBoolean()      还有其他基本数据类型的读取方法
    String readLine() 	

对于定位,该类提供了以下几个重要的方法:

    long getFilePointer()       返回此文件中的当前偏移量。 
    long length()               返回此文件的长度。 
    void seek(long pos)         设置到此文件开头测量到的文件指针偏移量,在该位置发生下一个读取或写入操作。 

示例程序,

	package ioprojects07;

	public class Prog07 {
		private final static String seperator = java.io.File.separator;
		private final static String Parent = 
				"C:" + seperator + "Users" + seperator + "wangshouheng" 
	              + seperator + "Documents" + seperator + "JavaIO" + seperator;
		private final static String fileName = "随机文本.txt";
		public static void main(String[] args) throws IOException {
			outRandFile();
			inRandFile();
		}
		private static void outRandFile() throws IOException{
			RandomAccessFile randOut = new RandomAccessFile(Parent + fileName, "rw");
			for(int i=1;i<16;i++)  randOut.writeInt(i);
			randOut.close();
		}
		private static void inRandFile() throws IOException{
			RandomAccessFile randIn = new RandomAccessFile(Parent + fileName, "r");
			int RECORD_SIZE = 4; //每个记录的长度,字节
			long len = randIn.length(); //文件的总长度,字节
			int n = (int)(len / RECORD_SIZE); //记录的总数
			for(int i=n-1;i>=0;i--){ //从最后一个往前输出每个记录
				randIn.seek(RECORD_SIZE*i);  readData(randIn);
			}
	         randIn.close();
		}
		private static void readData(RandomAccessFile in) throws IOException{
			int d = in.readInt();  System.out.print(d + " ");
		}
	}

=====================================================================
输出结果:

    15 14 13 12 11 10 9 8 7 6 5 4 3 2 1  

说明:可见上面的示例程序按照与写入相反的顺序读取了写入到文件当中的内容。主要是先获取文件的长度,然后根据每个记录的长度求得记录的总数,最后按照从后往前的顺序读取了每条记录。

5、读取ZIP文档

每个 ZIP 文档都有一个头,包含诸如文件名字和所使用的压缩方法等信息,在 Java 中,可以使用 ZipInputStream 和 ZipOutputStream 来读写 ZIP 文档。可能需要浏览单独的项,getNextEntry() 方法就可以返回一个描述项的 ZipEntry 类型对象。ZipInputStream 的 read() 方法在碰到当前项的结尾时返回 -1(不是 Zip 文件结尾),然后你应该使用 closeEntry() 来读入下一项。

典型的通读 Zip 文件的代码是

    ZipInputStream zin = new ZipInputStream(new FileInputStream(zipname));
    ZipEntry entry;
    while((entry=zin.getNextEntry())!=null){
        analyze entry
        read content of zin
        zin.closeEntry();
    }
    zin.close();

当然也可以使用其他的读入方式,比如

    Scanner in = new Scanner(zin);
    while(in.hasNextLine())  do something with in.nextLine

注意:读取单个 Zip 项之后不要关闭Zip输入流,否则将无法读取后续的项。

写出 Zip 文档时要用 ZipOutputStream,对要放入到 Zip 文件的每一项,都应该创建一个ZipEntry 对象,将文件名传递给 ZipEntry 构造器,它将设置诸如日期、解压方式等信息。然后,调用Z ipOutputStream 的 putNextEntry() 方法写出新文件,并将文件发送到 ZIP 流中。框架是:

	FileOutputStream fout = new FileOutputStream(zipname);
	ZipOutputStream zout = new ZipOutputStream(fout);
	for all files{
	    ZipEntry ze = new ZipEntry(filename);
	    zout.putNextEntry(ze);
	    send data to zout
	    zout.closeEntry();
	}
	zout.close();

Jar 是一种带有特殊项的 ZIP 文件,可以使用 JarInputStream 和 JarOutputStream 来写清单项。

示例程序,

	package ioprojects08;
	
    public class Prog08 {
		private final static String seperator = java.io.File.separator;
		private final static String Parent = 
				"C:" + seperator + "Users" + seperator + "wangshouheng" 
				+ seperator + "Documents" + seperator + "JavaIO" + seperator;
		private final static String fileName = "压缩文件.zip";
		private final static Charset charset = Charset.forName("GBK"); //创建字符集
		public static void main(String[] args) throws IOException {
			writeZip();
			readZip();
		}
		private static void readZip() throws IOException{  //读取ZIP文档
			ZipInputStream zin = new ZipInputStream(new FileInputStream(Parent + fileName), charset); //ZIP读入流
			//ZipInputStream zin = new ZipInputStream(new FileInputStream(Parent + fileName));
			ZipEntry entry;
			while((entry = zin.getNextEntry()) != null){  //获取ZIP的项
				Scanner scan = new Scanner(zin);  //使用Scanner进行读取
				while(scan.hasNextLine())  System.out.print(scan.nextLine());  //输出读取的字符串
				System.out.println();
				zin.closeEntry();  //关闭项
			}
			zin.close();  //关闭流
		}
		private static void writeZip() throws IOException{  //写出ZIP文档
			ZipOutputStream zout = new ZipOutputStream(new FileOutputStream(Parent + fileName), charset);  //ZIP写出流
			String[] files = new String[]{
					"观棋柯烂,伐木丁丁,云边谷口徐行。"
					+ "卖薪沽酒,狂笑自陶情。苍径秋高,对月枕松根,一觉天明。"
					+ "认旧林,登崖过岭,持斧断枯藤。收来成一担,行歌市上,易米三升。"
					+ "更无些子争竞,时价平平。不会机谋巧算,没荣辱,恬淡延生。"
					+ "相逢处,非仙即道,静坐讲《黄庭》。",
					"Later, respectively, wander and suffer sorrow."};
			int i=1;
			for(String str : files){  //对每个字符串创建一个文件,名字是 文件1 ...
				ZipEntry ze = new ZipEntry("文件"+i+++".txt");
				zout.putNextEntry(ze);
				zout.write(str.getBytes());  //获取字符串的字节并输出
				//PrintWriter pWriter = new PrintWriter(zout);  
				//pWriter.write(str);
				zout.closeEntry();
			}
			zout.close();  //关闭流
		}
	}

===================================================================
输出结果:

    观棋柯烂,伐木丁丁,云边谷口徐行。卖薪沽酒,狂笑自陶情。苍径秋高,对月枕松根,一觉天明。认旧林,登崖过岭,持斧断枯藤。收来成一担,行歌市上,易米三升。更无些子争竞,时价平平。不会机谋巧算,没荣辱,恬淡延生。相逢处,非仙即道,静坐讲《黄庭》。
    Later, respectively, wander and suffer sorrow.

说明:如果在使用ZipInputStream和ZipOutputStream的时候不指定字符集,将出现以下错误

Exception in thread "main" java.lang.IllegalArgumentException: MALFORMED
	at java.util.zip.ZipCoder.toString(ZipCoder.java:58)
	at java.util.zip.ZipInputStream.readLOC(ZipInputStream.java:297)
	at java.util.zip.ZipInputStream.getNextEntry(ZipInputStream.java:121)
	at ioprojects08.Prog08.readZIP(Prog08.java:28)
	at ioprojects08.Prog08.main(Prog08.java:21)

6、对象流域序列化

java 支持对象序列化机制,它将任意对象写到流中,并在之后将其读回。为保存对象,需要先打开一个 ObjectOutputStream 对象。可以直接使用该对象的 writeObject() 方法。为将数据读回,需要使用 ObjectInputStream,它有一个 readObject() 方法可以将数据读回。

但是要想在对象流中恢复和存储的类都应该实现 Serializable 接口,该接口中没有任何方法,实现了即可。

程序示例,

	package ioprojects09;

	public class Prog09 {
		private final static String seperator = java.io.File.separator;
		private final static String Parent = 
				"C:" + seperator + "Users" + seperator + "wangshouheng" 
				+ seperator + "Documents" + seperator + "JavaIO" + seperator;
		private final static String fileName = "对象数据.dat";
		public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
			//写出对象
	         ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(Parent + fileName));
			Product cup = new Product("茶杯", 10, 20.0d, new Shape(Shape.ROUND));
			Product pen = new Product("笔", 15, 3.0d, new Shape(Shape.RECTANGLE));
			out.writeObject(cup);
			out.writeObject(pen);
	         //读取对象
			ObjectInputStream in = new ObjectInputStream(new FileInputStream(Parent + fileName));
			Product p1 = (Product) in.readObject();
			Product p2 = (Product) in.readObject();
	         //输出对象信息
			System.out.println("名字:" + p1.getName() + ";数量:" + p1.getNum() + ";价格:" + p1.getPrice() + ";形状=" + p1.getShape().getShape());
			System.out.println("名字:" + p2.getName() + ";数量:" + p2.getNum() + ";价格:" + p2.getPrice() + ";形状=" + p2.getShape().getShape());
		}
		//自定义类:产品
		private static class Product implements Serializable{
			private String name;
			private int num;
			private double price;
			private Shape shape;  //自定义类:产品形状
			public Product(String name, int num, double price, Shape shape){
				this.name = name;
				this.num = num;
				this.price = price;
				this.shape = shape;
			}
			public String getName(){  return name;  }
			public int getNum(){  return num;  }
			public double getPrice(){  return price;  }
			public Shape getShape(){  return shape;  }
		}
		//自定义类:形状
		private static class Shape implements Serializable{
			private String shape;
			public static final String ROUND = "ROUND";
			public static final String RECTANGLE = "RECTANGLE";
			public Shape(String shape){  this.shape = shape;  }
			public String getShape(){  return shape;  }
		}
	}

========================================================================
输出结果:

    名字:茶杯;数量:10;价格:20.0;形状=ROUND
    名字:笔;数量:15;价格:3.0;形状=RECTANGLE

说明:可以看出当被序列化写出的对象中包含引用类型时候无需对引用类型进行独立地调用 writeObject(),而且该引用类型会被自动地映射到指定的对象中去。但是被引用的类型必须也是实现了 Serializable 接口的。另外,当两个类共享一个引用类型的时候也无需对其独立地调用 writeObject(),它会被自动地映射。

7、磁盘、目录和文件的基本操作

7.1 Path 类

Path 是一个接口,表示的是目录名序列,也可以跟一个文件名。通常使用类 Paths 的静态方法来获取 Path 对象。这里简要地给出这两个类的一些比较重要的方法

Paths类

    // 通过连接指定的字符串创建一个路径
    static Path get(String first, String ..more) 

Path类

    Path getParent();                   // 获取父路径
    Path getFileName();                 // 后去该路径最后的一个部件
    Path getRoot()                      // 获取根部件
    toFile()                            // 创建 File 对象

而 File 类中也有方法 toPath() 可以用来获取一个 Path 对象

示例程序,

	package ioprojects10;

	public class Prog10 {
		public static void main(String[] args) {
			Path p = Paths.get("C:", "users", "wangshouheng" ,"Documents", "JavaIO", "对象数据.dat");
			System.out.println(p.getParent());
			System.out.println(p.getFileName());
			System.out.println(p.getRoot());
		}
	}

==========================================================
输出结果:

    C:\users\wangshouheng\Documents\JavaIO
    对象数据.dat
    C:\

7.2 Files类

读写

该类提供了一些便捷的操作文件的方法。

  1. 读取文件所有内容: byte[] bytes = Files.readAllBytes(path)然后可以将字节输入转换成字符串: String content = new String(bytes, charSet)
  2. 将文件按行读入: Files.readAllLines(path, charSet)
  3. 将字符串写到文件中: Files.write(path, content.getBytes(charset))
  4. 向指定文件追加内容: Files.write(path, content.getBytes(charset), StandardOpenOption.APPEND);
  5. 直接打开输入流和输出流:newInputStream(...)和newOutputStream()
复制、移动和删除文件

下面的这些方法适用于处理中等长度的文本文件,如果文件比较大,或者是二进制文件,尽量使用前面的那些流:

  1. 将一个文件从一个位置移动到另一个位置,可以使用:Files.copy(...)
  2. 移动文件(复制并删除原文件):Files.move(...)
  3. 如果目标路径存在该文件,那么复制或移动将会失败,若要覆盖已有的目标路径,可以使用REPLACE_EXISTING,如 Files.copy(...)
  4. 删除文件:Files.delete(...), Files.deleteIfExists(...)
创建文件和目录
  1. 创建新目录

    1. Files.createDirectory(...) 除了最后一个部件外,其他部分都必须是已存在的
    2. Files.createDirectories(...) 与上面的相比,它可以创建中间路径,没有上面的限制
    3. Files.createFile(...) 创建空文件
  2. 创建临时目录和文件的方法: Files.createTempDirectory(...)或Files.createTempFile(...)

获取文件信息
  1. 返回文件的字节数: Files.size(...)
  2. 略,它还有很多静态的边界方法,简单地传入文件路径就可以获取到文件的信息

7.4 File 类概述

File 类构造方法

使用 File 的构造方法获取 File 实例,我们可以通过传入文件路径、URI 和父文件并指定一个文件名的方式创建。(文件路径的分隔与系统有关,可以使用 File.seprator 和 File.sepratorChar 代替分隔符)

文件和目录的基本操作
  1. 构建表示文件路径的File对象: new File(...)
  2. 判断目录/文件是否存在: exists()
  3. 删除目录/文件: delete()/deleteOnExit()
  4. 创建目录: mkdir()/mkdirs()
  5. 创建新文件: createNewFile()
  6. 重命名/移动目录/文件: renameTo(File dist)
  7. 获取目录中的内容
    1. listFiles(...): 返回目录中的文件和目录的File对象数组
    2. list(...): 返回目录中文件和目录的字符串数组
  8. 获取父目录
    1. getParentFile(): 返回父目录的File对象
    2. getPatent()
  9. 创建临时文件: createTempFile(...)
  10. 获取和设置目录/文件的属性

说明:

在 File.list() 方法中,有的需要我们传入一个 FilenameFilter 实例,这是一个接口。它使用了策略模式——我们只需要实现自己的接口方法,并在其中加入文件名称的匹配逻辑就可以对文件进行过滤。

8、Preferences

Preferences 被用来存储一些小型的键值对信息,通常用里存储一些用户配置信息:

    public static void main(String...args) {
        Preferences prefer = Preferences.userNodeForPackage(PreferIOTest.class);
        // output
        prefer.put(Keys.USER_NAME.name(), "Wng");
        prefer.putLong(Keys.USER_PHONE.name(), 1582212198);
        prefer.putBoolean(Keys.IS_MALE.name(), true);
        prefer.putDouble(Keys.WAGE.name(), 45.5);
        // input
        System.out.println(prefer.getBoolean(Keys.IS_MALE.name(), true));
        System.out.println(prefer.getDouble(Keys.WAGE.name(), 0));
        System.out.println(prefer.getLong(Keys.USER_PHONE.name(), 0));
    }

    private enum Keys {
        USER_NAME,
        USER_PHONE,
        IS_MALE,
        WAGE;
    }

==================================================================
输出:
    true
    45.5
    1582212198

Java 基础回顾系列文章,给你的 Java 查缺补漏:

本系列以及其他系列的文章均维护在 Github 上面:Github / Awesome-Java,欢迎 Star & Fork. 如果你喜欢这篇文章,愿意支持作者的工作,请为这篇文章点个赞?!

unity转微信小游戏:UI大小设置 阅读详情

相关推荐

抖音直播行为规范考试

抖音直播行为规范考试

Q1642968641的博客 1万+

Java IO知识体系详解

创建一个好的I/O系统不是一件容易的事,难度似乎来自于需要覆盖所有的可能性。因为不仅存在各种I/O源端(文件、控制台、网络连接等),还需要以多种不同的方式与这些I/O源端(顺序、随机、缓冲、二进制、按字符、按行、按字)进行通信。Java类库的设计者通过创建大量的类来解决这个问题。...

在路上 1474

告别单一帧:在CFD-POST中完整加载Fluent瞬态计算结果的实战指南

本文详细介绍了在CFD-POST中完整加载Fluent瞬态计算结果的实战指南,解决工程师常见的只能看到最后一帧数据的问题。通过Fluent端的正确设置和CFD-POST端的完整加载技巧,帮助用户高效处理瞬态数据,提升后处理效率。文章还提供了常见问题排查、性能优化和高级后处理技巧,适用于Workbench环境下的流体流动分析。

weixin_29284657的博客 537

Java的IO体系

而编码和解码转换之间是需要遵循规则的,即编码和解码都遵循同一种规则才能将文字信息正常显示,如果编码跟解码使用了不同的规则,就会出现乱码的情况。这意味着在使用"rws"模式打开文件时,不仅对文件内容的更新会被立即写入,还包括文件的元数据,比如文件属性、修改时间等。不同的是,"rwd"模式只要求对文件内容的更新被立即写入,而元数据可能会被延迟写入,这意味着文件的属性信息等可能不会立即更新到磁盘。它们分别属于不同的编码集。文件内容通常指的是文件中存储的实际数据,即由应用程序创建的、用户需要读取或操作的数据。

落日的博客 1135

Ubuntu22.04系统安装及配置

下载Ubuntu22.04系统镜像(下载地址:链接:https://caiyun.139.com/m/i?提取码:fLRJ),结合rufus和系统镜像制作USB启动盘,在物理机服务器上通过USB启动盘进行安装;或通过Esxi虚拟化平台创建虚拟机,然后在虚拟机中使用系统镜像进行安装。

cheagoun的博客 8万+

elevation mapping算法3之使用D435i相机离线或在线订阅点云和tf关系生成高程图

本文主要学习使用D435i相机发出的点云作为高程图的点云输入,如何离线输入点云生成高程图,进一步学习如何在线订阅点云生成高程图。

重点记录机器人和自动驾驶相关领域的技术,尤其是计算机视觉,视觉SLAM等,不仅有前言文献或必备算法的学习,也会有实际应用的点滴经验 3488

Java开发中的IO体系

Java IO体系提供了丰富的工具,用于处理各种类型的数据源,支持从字节级别到字符级别的数据操作。通过掌握这些概念和技巧,可以高效、安全地执行各种IO操作,特别是在处理大文件、高并发网络应用时,使用NIO可以显著提升性能。注意资源管理、字符编码、异常处理等细节,将有助于编写健壮的IO代码。

goTsHgo的博客 1386

2026年全国青少年信息素养大赛算法应用主题赛(C++赛项初赛模拟卷1及答案解析)

2026年全国青少年信息素养大赛算法应用主题赛(C++赛项初赛模拟卷1及答案解析)

王老师青少年编程 2023

java I/O流概念及其体系结构

I和O,分别是Input和Output两个单词的缩写,Input是输入,Output是输出。流:是一种抽象概念,是对数据传输的总称.也就是说数据在设备间的传输称为流,流的本质是数据传输。IO流就是用来处理设备间数据传输问题的。常见的应用: 文件复制、文件上传、 文件下载等。字节流抽象基类: 以字节读写文件InputStream:这个抽象类是表示字节输入流的所有类的超类OutputStream:这个抽象类是表示字节输出流的所有类的超类 根据交互设备的不同,有不同的具体子类。

m0_52363223的博客 488

手把手教你学Simulink——基础电机控制场景实例:基于Simulink的永磁同步发电机温度场耦合仿真

摘要:本文详细介绍了基于Simulink的永磁同步发电机(PMSG)电-热耦合仿真方法。通过Simscape Electrical搭建含热端口的PMSG模型,结合集总参数热网络(LPTN)实现损耗计算与温度反馈,分析了过载工况下温升对电阻和永磁体性能的影响。仿真结果表明:150%过载20秒会导致绕组温升132°C,电阻增加42%,磁链衰减10.5%。文章还提供了自然冷却与强迫风冷的对比数据,并建议通过多物理场耦合仿真预防实机过热问题。该方法可有效支撑高功率密度电机热设计,避免永磁体退磁风险。

MHD0815的博客 248

C#里用HttpListener实现WEB API接口服务端

本文介绍了一个基于WinForm的HTTP服务实现方案。该方案使用HttpListener类创建了一个支持JSON POST请求处理的轻量级Web服务器,主要特点包括:1) 纯线程方式实现请求监听和处理;2) 提供标准化的JSON响应格式;3) 支持自定义请求处理逻辑;4) 包含完整的日志记录功能。代码示例展示了如何初始化服务、处理不同类型的请求(包括JSON POST),并提供了线程安全的UI更新机制。该实现适用于需要简单HTTP接口的Windows桌面应用程序,可作为程序间通信的基础设施。

大坡3D软件开发 226

基本功:超全面 IO 流教程,小白也能看懂

点击关注公众号,实用技术文章及时了解Java领域的io模块是一个非常庞大的知识体系,在大家求职面试的过程中通常也是被问到比较多的一个模块,今天我特意整理了一份关于IO知识体系相关的干货和大家分享,希望各位读者们喜欢。如何理解io流io可以理解为是input 和 output的两个缩写,分别代表了数据的”输入“和”输出“。io流则是描述了将数据从内存和数据源之间拷贝的一个过...

Java知音 644

YOLO26涨点改进 |TGRS 2026 | 全网独家改进、特征融合创新篇 | 引入CMIFE跨模态交互特征提取模块,充分挖掘互补信息,适合多模态图像融合,目标检测,实例分割,遥感图像分类高效涨点

本文提出CMIFE模块用于改进YOLO26网络模型,通过跨模态交互特征提取实现多尺度特征的精细化融合。该模块采用双注意力机制(自注意力强化模态内一致性,交叉注意力实现跨模态互补信息筛选),有效解决了传统YOLO融合中的信息冗余和负迁移问题。实验表明,CMIFE模块在保持模型实时性的同时,显著提升了小目标检测、遮挡场景等复杂环境下的检测精度。文章详细介绍了模块原理、网络结构、核心代码实现方法,并提供了三种改进版YOLO26的配置文件方案(yolo26_CMIFE-1/2/3.yaml)。

Ai缝合怪、助力小伙伴高效发论文 145

Java基础总结回顾

title: Java基础总结回顾 date: 2022-04-12 17:31:14 tags: Java categories: Java cover: https://cover.png feature: false 1. 说明 1.1 编译与运行 编译:是指将编写的 Java 源文件翻译成 JVM 认识的 class 文件,在这个过程中, javac 编译器会检查所写的程序是否有错误,有错误就会提示出来,如果没有错误就会编译成功 运行:是指将 class文件 交给 JVM 去运行,此时 J.

凡的博客 3098

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

java基础回顾

Integer、Short、Byte、Character、Long 都有一个常量池,常量池的范围是-128~127之间。如果此方法定义的包装类的值在这个范围内,则会直接返回内部缓存池中已经存在的对象的引用(new的方式jdk9后就淘汰了)

b810411的博客 851

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

java学习回顾与总结

网易云java架构师课程学习,回顾总结 一.基础回顾 1.java基础内容 1.1 java语法 1.2 方法的重写与重载,覆盖与隐藏 1.3 java修饰词与访问权限 1.4 继承 1.5 封装 1.6 多态 1.7 super与this 1.8 内存分配 1.9 封装 1.10 继承 1.11 封装 2.java中级内容 二.重点掌握 三.个人总结 四.未来展望 ...

lk的博客 730

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

【Java基础回顾】Java基础回顾重难点笔记

Java基础回顾一、Java概览1.Java的运行过程2.Java的跨平台的解释2.Maven基础二、Java的基本语法1.Java中的注释2.Java中的关键字3.关键字总结3.java中的标识符4.多态5.泛型6.反射7.集合8.正则9.引用分类10.Java八种数据类型三、集合1.List2.Set3.Map四、 一、Java概览 1.Java的运行过程 🍎Java程序运行时,必须经过编译和运行两个步骤。 🍊首先将后缀名为.java的源文件进行编译,最终生成后缀名为.class的字节码文件。然后Ja

斯基的朦胧 900

【Java回顾】Day7 Java IO|分类(传输方式,数据操作)|零拷贝和NIO

参考:https://javaguide.cn/java/io/io-model.html#nio-non-blocking-new-i-oNIO是IO多路复用模型线程首先发起调用,询问内核数据是否准备就绪。等准备就绪后,用户线程再发起read调用(数据从内核空间->用户空间)是阻塞的。select调用,支持一次查询多个系统调用的可用状态,几乎所有的操作系统都支持。epoll调用,linux 2.6内核,select调用的增强版本,优化IO执行效率NIO中的多路复用器selector。

爱因斯小柚的博客 1143

java知识回顾_Java7 –回顾

java知识回顾 我开始写博客文章,介绍即将发布的Java8版本中的新增功能 ,并认为我将从快速回顾一下Java7带给我们的内容开始。 Java7于2011年7月发布,被描述为“更具进化性而非革命性”。 “虽然有了一些重大改进,但并没有真正破土动工或突破性的功能。” – Oracle首席Java架构师Mark Reinhold 例如,它不包含被大肆宣传的lambda表达式。 尽管如...

最佳 Java 编程 253

Java基础知识回顾

Java基础知识回顾 Java 程序可以认为是一系列对象的集合,而这些对象通过调用彼此的方法来协同工作。有类、对象、方法和实例变量的概念。 概述: 知识点回顾 :对Java基础知识回顾从基础数据类型,标识符,修饰符,三种特性,集合,多线程,IO流,反射,异常这些方面进行知识回顾,除了基础回顾之外,增加相应理解。 基本数据类型 基本类型:byte, short, int, long, float, double, boolean, char 基本类型详解: byte : 数据类型是8位、有符号

鲜花怒马少年时,一日看尽长安花 198

一、JAVA的IO演进之路

1.1IO基础入门 java1.4之前的版本中,对IO的支持并不完善,主要问题如下: 没有数据缓冲区,IO性能存在问题 没有C/C++中Channel的概念,只有输入输出流 只支持同步阻塞式IO(BIO),经常导致通信线程被长时间阻塞 支持的字符集有限,硬件可移植性不好 1.1.1 Linux网络IO模型简介 Unix提供了5种IO模型: 阻塞IO模型 非阻塞IO模型 IO复用模...

MY BLOG 1208

Java修仙,法力无边(光速回顾Java基础~)

一起来回顾Java基础吧~向着大牛冲冲冲!!

小菜鸡的小博客 1115

回顾Java基础

Java基础 JDK、JRE、JVM的区别 JDK是Java程序开发包,通过它可以编写Java程序 JRE是运行Java的环境集合,没有它无法运行Java程序 JVM是Java虚拟机用于运行Java字节码文件(.class),是Java的核心 为什么Java能够实现跨平台 JVM只认识字节码文件,针对不同的操作系统有不同的JVM实现 常用数字类型的区别 整型和单精度占用4个字节,长整型和双精度占用8个字节 整型的取值范围是:-21亿~+21亿 以下程序的执行结果? package com.xiaopeng.

moonlightpp的博客 282

第22章 Java 基础技术回顾:21 章精华速览

方法封装重复逻辑:定义一次,调用多次Java 是值传递:形参永远拿到实参的拷贝;引用类型通过拷贝的地址可以改对象内容重载 = 同名不同参数列表,与返回值无关;精确匹配优先可变参数本质是数组,必须放参数列表最后递归必须有出口,警惕栈溢出;能用循环改写就优先用循环。

成熟的标题 603

Java7 –回顾

我开始写博客文章,介绍即将发布的Java8版本中的新增功能 ,并认为我将从快速回顾一下Java7带给我们的内容开始。 Java7于2011年7月发布,被描述为“更具进化性而非革命性”。 “有一些重大的改进,但是并没有真正令人惊叹或突破性的功能。” – Oracle首席Java架构师Mark Reinhold 例如,它不包含被大肆宣传的lambda表达式。 尽管如此,它确实为Jav...

最佳 Java 编程 786

java知识点回顾(7):文件操作、IO流

java知识点回顾(7):文件操作、IO流

qq_50843126的博客 190
上一篇: Java基础知识梳理--线程
下一篇: Java 基础回顾-5:容器类
开发者如是说
博客等级 码龄10年 67粉丝 163原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值