IO流




  • IO(Input Output)流概述

    • IO流用来处理设备之间的数据传输。
    • Java对数据的操作是通过流的方式。
    • Java用于操作流的对象都在IO包中。
    • IO流的分类:
      • 按操作数据分为两种:字节流与字符流 。
      • 按流向分为:输入流,输出流。
    • IO流常用基类:
      • 字节流的抽象基类:
        • InputStream
        • OutputStream
      • 字符流的抽象基类:
        • Reader
        • Writer
      • 注:由这四个类派生出来的子类名称都是 以其父类名作为子类名的后缀。
        • 如:InputStream的子类FileInputStream。
        • 如:Reader的子类FileReader。
    • 同一个程序中的两个流之间是没有关系的,需要“中转站”在它们之间建立联系,通常使用内存作为这个“中转站”。

  • 字符流

    • 字符输出流

      • 字符输出流的类层次关系图
      • Writer中的常用方法
        • 写操作:
          • void write(char[] cbuf);//写入字符数组。
          • abstract void write(char[] cbuf, int off, int len);//写入字符数组的某一部分。
          • void write(int c);//写入单个字符。
          • void write(String str);//写入字符串。
          • void write(String str, int off, int len);//写入字符串的某一部分。
        • 刷新流:
          • abstract void flush();//刷新该流的缓冲。
        • 关闭流:
          • abstract void close();//关闭此流,但要先刷新它。
      • FileWtriter
        • Writer中用于操作文件的子类。
        • FileWriter没有空参数的构造函数。
        • 对象一被初始化,就要明确要被操作的文件。该文件会被创建到指定目录下,如果该目录下有同名文件,同名文件将被覆盖。
        • 代码举例:
          package cn.itcast.heima;
          
          import java.io.FileWriter;
          import java.io.IOException;
          
          public class Test {
          	public static void main(String[] args) throws IOException {
          		FileWriter fw = new FileWriter("c:\\demo.txt");
          		
          		//将数据写入到流中,即内存中
          		fw.write("abcde");
          		
          		//将流对象缓冲中的数据刷到目的地
          		fw.flush();
          		
          		fw.write("haha");
          		
          		//关闭流资源,关闭之前刷新缓冲中的数据
          		fw.close();
          	}
          }
          
          demo.txt文件中显式内容如图:
        • close和flush方法的区别:
          • flush刷新后,流可以继续使用。
          • close刷新后,会将流关闭。
        • 文件的续写:
          • 如果想在原有文件上继续加入新的数据,可以使用另一个构造函数:
            • FileWriter(String fileName, boolean append);//append如果为true,则将数据写入文件末尾处。
          • 如果希望换行,要写入\r\n,windows不识别\n。
      • IO异常处理方式
        • 在实际开发中不要抛出异常,应该在方法中处理异常。
        • 代码举例:
          package cn.itcast.heima;
          
          import java.io.FileWriter;
          import java.io.IOException;
          
          public class Test {
          	public static void main(String[] args) {
          		
          		//由于finally中要调用fw的close方法,所以fw要定义在try语句外面
          		FileWriter fw = null;
          		
          		try {
          			fw = new FileWriter("c:\\demo.txt");
          			fw.write("abcde");
          		} catch (IOException e) {
          			e.printStackTrace();
          		}finally{
          			try {
          				
          				//如果fw在执行fw = new FileWriter("c:\\demo.txt");时抛出异常
          				//那么此时fw为null,执行close操作会导致异常
          				//所以此处要先判断fw是否为null
          				if (fw != null) {
          					fw.close();//close方法也会抛出IO异常,因此需要再次处理
          				}
          			} catch (IOException e) {
          				e.printStackTrace();
          			}
          		}
          	}
          }
          
          demo.txt文件中显式内容如图:
        • 如果finally中要释放多个资源,需要分别try-catch处理。
      • BufferedWriter
        • Java提供的带缓冲功能的Writer类。
        • 缓冲区的出现提高了对数据的读写效率。
        • 将需要被提高效率的流对象作为参数传递给BufferedWrtier的构造函数即可。
        • 代码举例:
          package cn.itcast.heima;
          
          import java.io.BufferedWriter;
          import java.io.FileWriter;
          import java.io.IOException;
          
          public class Test {
          	public static void main(String[] args) throws IOException {
          		
          		BufferedWriter bufw = new BufferedWriter(new FileWriter("c:\\demo.txt"));
          		
          		bufw.write("abcde");
          		
          		//跨平台的换行方法。
          		bufw.newLine();
          		
          		bufw.write("hello");
          		
          		//关闭缓冲区对象,就是在关闭缓冲区中的流对象
          		//调用BufferedWriter的close()方法后,不需要再调用FileWriter流对象的close()方法
          		bufw.close();
          	}
          }
          
          demo.txt文件中显式内容如图:

        • 注意:只要用到缓冲区,就需要刷新。
        • newLine()方法:跨平台的换行方法。该方法只有BuferedWriter对象可以调用。

    • 字符输入流

      • 字符输出流的类层次关系图
      • Reader中的常用方法
        • 读操作:
          • int read();//读取单个字符。
          • int read(char[] cbuf);//将字符读入数组,返回读取的字符数,读到末尾时返回-1。
          • abstract int read(char[] cbuf, int off, int len);//将字符读入数组的某一部分。
        • 关闭流:
          • abstract void close();//关闭该流并释放与之关联的所有资源。Reader类的close方法不刷新缓冲。
        • 注:Reader中没有刷新流。
      • FileReader
        • Reader中用于操作文件的子类。
        • FileReader没有空参数的构造函数。
        • 对象一被初始化,就要明确要被操作的文件。如果该文件不存在,会发生FileNotFoundException异常。
        • 代码举例:
          package cn.itcast.heima;
          
          import java.io.FileReader;
          import java.io.IOException;
          
          public class Test {
          	public static void main(String[] args) throws IOException {
          		
          		//读取方式一:将字符读入数组中
          		FileReader fr1 =new FileReader("c:\\demo.txt"); 
          		char[] buf = new char[1024];
          		int num = 0;
          		System.out.print("方式一:");
          		while ((num = fr1.read(buf)) != -1) {
          			System.out.println(new String(buf, 0, num));
          		}
          		fr1.close();
          		
          		//读取方式一:一次读一个字符
          		FileReader fr2 =new FileReader("c:\\demo.txt");
          		int ch = 0;
          		System.out.print("方式二:");
          		while ((ch = fr2.read()) != -1) {
          			System.out.print((char)ch);
          		}
          		fr2.close();
          	}
          }
          
          打印结果:
        • 用于存储字符的数组的长度通常定义为1024的整数倍。
      • BufferedReader
        • 基本原理同BufferedWriter。
        • 代码举例:
          package cn.itcast.heima;
          
          import java.io.BufferedReader;
          import java.io.FileReader;
          import java.io.IOException;
          
          public class Test {
          	public static void main(String[] args) throws IOException {
          		BufferedReader bufr = new BufferedReader(new FileReader("c:\\demo.txt"));
          		String line = null;
          		
          		//调用readLine()方法读取文件的一行内容,并打印
          		while ((line = bufr.readLine()) != null) {
          			System.out.println(line);
          		}
          		bufr.close();
          	}
          }
          
          demo.txt文件中显式内容如图:

          打印结果:
        • readLine()方法:
          • 一次读取一行数据,当返回null时,表示读到文件末尾。
          • 打印时需要换行输出,该方法不会读取行末尾的换行符。
      • LineNumberReader
        • BufferedReader的子类。
        • 此类定义了方法 setLineNumber(int) 和 getLineNumber(),它们可分别用于设置和获取当前行号。

  • 装饰设计模式(Decorator)

    • 装饰设计模式的概念

      • 当想要对已有的对象进行功能增强时,可以定义一个新类,将已有对象传入,基于已有的功能,提供增强功能。该自定义的新类称为装饰类。

    • 装饰设计模式的基本格式

      • 含有被装饰类的引用。
      • 通过构造函数传入被装饰类对象。
      • 和被装饰类含有同样的方法,其中调用被装饰类的方法,对其进行改进、增强。
      • 和被装饰类继承同一个类或实现同一个接口,可以当做被装饰类来使用。

    • 装饰设计模式的应用

      • BufferedReader、BufferedWriter都是装饰类,他们可以装饰一个Reader或Writer,给被装饰的Reader和Writer提供缓冲的功能。

    • 装饰和继承的区别

      • 装饰模式比继承要灵活,避免了继承体系的臃肿,而且降低了类与类之间的关系。
        比如:
        需要为某一个类或接口下面的很多子类提供增强功能。如果分别为每个子类设计新的继承类,则需要设计很多结构类似的类。
        如果设计一个装饰类,将需要增强的子类通过参数传给这个装饰类,那么只需要设计一个类就能解决所有子类的增强功能的需求。
        而这个装饰类只需要和这些子类具有相同的父类和接口即可。
      • 装饰类增强的是已有对象,具备的功能和已有对象的是相同的,只不过提供了更强功能。
      • 装饰类和被装饰类通常都是属于一个体系中的(即所属于同一个父类或同一个接口)。

  • 字节流

    • 字节流概述

      • 基本操作与字符流类相同。
      • 字节流可以操作任意类型数据。
      • OutputStream中的常用方法:
        • 写操作:
          • void write(byte[] b);//将 b.length 个字节从指定的 byte 数组写入此输出流。
          • void write(byte[] b, int off, int len);//将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此输出流。
          • abstract void write(int b);//将指定的字节写入此输出流。
        • 刷新流:
          • void flush();//OutputStream的flush方法不执行任何操作。
        • 关闭流:
          • void close();//关闭此输出流并释放与此流有关的所有系统资源。
      • InputStream中的常用方法:
        • 读操作:读方法是阻塞式方法,如果没有读到数据,就会一直等待。
          • abstract int read();//从输入流中读取数据的下一个字节。
          • int read(byte[] b);//从输入流中读取一定数量的字节,并将其存储在缓冲区数组b中。
          • int read(byte[] b, int off, int len);//将输入流中最多len个数据字节读入byte数组。
        • 关闭流:
          • void close();//关闭此输入流并释放与该流关联的所有系统资源。
        • 注:InputStream中没有刷新流。

    • 字节流的类层次关系图

      • 字节输出流的类层次关系图
      • 字节输入流的类层次关系图

    • FileOutputStream和FileInputStream

      • OutputStream 的 flush 方法不执行任何操作。FileOutputStream没有覆盖OutputStream 的 flush 方法,因此也不执行任何操作。
      • FileInputStream的特有方法:
        • int available();//返回文件字节数,用于定义缓冲字节数组的大小。但是不能超过系统内存,因此慎重使用。数据量太大时,建议使用字节数组循环的方式读取数据。
      • 代码举例:
        /*
         * 需求:复制一个图片
         * 思路:
         * 1.用字节读取流对象和图片关联
         * 2.用字节输出流对象创建一个图片文件,用于存储获取到的图片数据
         * 3.通过循环读写,完成数据的存储
         * 4.关闭资源
         */
        package cn.itcast.heima;
        
        import java.io.FileInputStream;
        import java.io.FileOutputStream;
        import java.io.IOException;
        
        public class Test {
        	public static void main(String[] args) {
        		FileOutputStream fos = null;
        		FileInputStream fis = null;
        		try {
        			fos = new FileOutputStream("c:\\2.jpg");
        			fis = new FileInputStream("c:\\1.jpg");
        			
        			byte[] buf = new byte[1024];
        			
        			int len = 0;
        			
        			while ((len = fis.read(buf)) != -1) {
        				fos.write(buf, 0, len);
        			}
        		} catch (IOException e) {
        			throw new RuntimeException("复制图片失败!");
        		}finally{
        			if(fis != null){
        				try {
        					fis.close();
        				} catch (IOException e) {
        					throw new RuntimeException("读取流关闭失败!");
        				}
        			}
        			if(fos != null){
        				try {
        					fos.close();
        				} catch (IOException e) {
        					throw new RuntimeException("输出流关闭失败!");
        				}
        			}
        		}
        	}
        }
        
        程序执行结果:查看c:\2.jpg文件,与c:\1.jpg文件完全相同,复制功能实现。
      • 注意:不能使用字符流对象复制媒体文件,因为字符流读取数据后会根据编码表进行查表,如果编码表没有找到对应的符号,会存储一个近似的符号,导致数据被修改,从而无法完全复制。

    • BufferedOutputStream和BufferedInputStream

      • 原理同字符缓冲流。
      • 代码举例:
        /*
         * 需求:复制一个mp3文件,通过缓冲区
         */
        package cn.itcast.heima;
        
        import java.io.BufferedInputStream;
        import java.io.BufferedOutputStream;
        import java.io.FileInputStream;
        import java.io.FileOutputStream;
        import java.io.IOException;
        
        public class Test {
        	public static void main(String[] args) {
        		BufferedInputStream bufis = null; 
        		BufferedOutputStream bufos = null;
        		try {
        			bufis = new BufferedInputStream(new FileInputStream("c:\\1.mp3"));
        			bufos = new BufferedOutputStream(new FileOutputStream("c:\\2.mp3"));
        			
        			int ch = 0;
        			
        			while ((ch = bufis.read()) != -1) {
        				bufos.write(ch);
        			}
        		} catch (Exception e) {
        			throw new RuntimeException("复制文件失败!");
        		}finally{
        			try {
        				if(bufis != null)
        					bufis.close();
        			} catch (IOException e) {
        				throw new RuntimeException("输入流关闭失败!");
        			}
        			try {
        				if(bufos != null)
        					bufos.close();
        			} catch (IOException e) {
        				throw new RuntimeException("输出流关闭失败!");
        			}
        		}
        	}
        }
        
        程序执行结果:查看c:\2.mp3文件,与c:\1.mp3文件完全相同,复制功能实现。
      • 为什么read方法返回的类型是int而不是byte:
        • 避免因为读到8个1而误以为返回-1,错误地结束读取。所以将byte类型提升为int类型。
        • 为了保证最高位都为0,read方法将读到的字节数据与255做&运算,从而实现只保留读到的最低字节,int值的其它字节均为0。
        • 从而保证了程序不会因为读到连续8个1而结束读取。
        • 为了保证数据的原样性,write方法虽然接收int型参数,但是在输出时,会去掉高字节,只保留最低8位。

  • 标准输入输出流

    • System类中的成员变量in和out,它们各代表了系统标准的输入和输出设备。
    • 默认输入设备是键盘,输出设备是显示器。
    • System.in的类型是InputStream。
    • System.out的类型是PrintStream是OutputStream的子类FilterOutputStream 的子类。
    • InputStream的read方法时阻塞式方法,如果键盘录入的数据少于输入流读取的数据,那么系统会等待键盘的再次输入。
    • 改变标准输入输出设备:
      • System.setIn(InputStream in);//改变输入设备
      • System.setOut(PrintStream out);//改变输出设备

  • 转换流

    • 转换流概述

      • 转换流的由来:
        • 字符流与字节流之间的桥梁。
        • 方便了字符流与字节流之间的操作。
      • 转换流的应用:
        • 字节流中的数据都是字符时,转成字符流操作更高效。

    • 读取转换流:InputStreamReader

      • 字节通向字符的桥梁,可以将字节流对象作为参数传递给InputStreamReader的构造函数。

    • 写入转换流:OutputStreamWriter

      • 字符通向字节的桥梁,可以将字节流对象作为参数传递给OutputStreamWriter的构造函数。

    • 代码举例

      • package cn.itcast.heima;
        
        import java.io.BufferedReader;
        import java.io.BufferedWriter;
        import java.io.IOException;
        import java.io.InputStreamReader;
        import java.io.OutputStreamWriter;
        
        public class Test {
        	public static void main(String[] args) throws IOException {
        		
        		//将字节输入流转换为带缓冲的字符输入流
        		BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
        		
        		//将字节输出流转换为带缓冲的字符输出流
        		BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));
        		
        		String line = null;
        		
        		//如果输入为over,结束录入
        		while (!(line = bufr.readLine()).equals("over")) {
        			bufw.write(line.toUpperCase());
        			bufw.newLine();//换行
        			bufw.flush();//刷新缓冲区
        		}
        		
        		bufr.close();
        		bufw.close();
        	}
        }
        
        打印结果:

  • 流操作规律

    • 流对象的选择

      1. 明确源和目的:
        • 源:输入流。InputStream,Reader。
        • 目的:输出流。OutputStream,Wrtier。
      2. 操作的数据是否是纯文本:
        • 是:字符流。
        • 不是:字节流。
      3. 体系明确后,通过设备进行区分:
        • 源设备:
          • 键盘:System.in
          • 硬盘:FileStream
          • 内存:ArrayStream
        • 目的设备:
          • 控制台:System.out
          • 硬盘:FileStream
          • 内存:ArrayStream
      4. 是否需要提高效率:
        • 是:使用缓冲区装饰类。
        • BufferedReader,BufferedWriter;BufferedInputStream,BufferedOutputStream。

    • 转换流什么时候使用

      • 字符和字节之间的桥梁。
      • 涉及到字符编码转换时。
        • InputStreamReader和OutputStreamWriter可以在构造函数中指定字符集。

    • 应用举例

      • 将一个文本文件中的数据存储到另一个文件中。复制文件:
        • BufferedReader bufr = new BufferedReader(new FileReader("a.txt"));
        • BufferedWriter bufw = new BufferedWriter(new FileWriter("b.txt"));
      • 将一个图片文件中的数据存储到另一个文件中。复制文件:
        • BufferedInputStream bufin = new BufferedInputStream(new FileInputStream("a.txt"));
        • BufferedOutputStream bufout = new BufferedOutputStream(new FileOutputStream("b.txt"));
      • 将键盘录入的数据保存到一个文本文件中:
        • BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
        • BufferedWriter bufw = new BufferedWriter(new FileWriter("b.txt"));
      • 将键盘录入的数据按照指定的编码表(utf-8)保存到一个文本文件中:
        • BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
        • BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("b.txt"),"UTF-8"));
      • 将一个文本数据打印在控制台上:
        • BufferedReader bufr = new BufferedReader(new FileReader("a.txt");
        • BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));
      • 异常的日志信息可以使用IO流保存到文件中。实际开发时,可以下载log4j工具帮助我们显式日志信息。

  • File类

    • File类概述

      • File类用来将文件或者文件夹封装成对象,方便对文件与文件夹的属性信息进行操作 。
      • File类可以操作文件和文件夹,还可以操作文件的属性信息。这是流不具备的功能,流只能操作数据。
      • File对象可以作为参数传递给流的构造函数。
      • File.separator:跨平台的系统目录分隔符。

    • File类的构造方法

      • File(File parent, String child);
      • File(String pathname);
      • File(String parent, String child);
      • File(URI uri);

    • File类的常用方法

      1. 创建:
        • boolean createNewFile();//在指定位置创建文件。如果该文件已经存在,则不创建,返回false。
          创建File对象并不会创建被封装的文件,需要调用createNewFile()方法才会创建。
          和输出流不一样,输出流的对象一建立就会创建文件,而且如果文件已经存在,就会覆盖文件。
        • static File createTempFile(String prefix, String suffix);//创建临时文件
        • static File createTempFile(String prefix, String suffix, File directory);//创建临时文件
        • boolean mkdir();//创建文件夹
        • boolean mkdirs();//创建多级目录
      2. 删除:
        • boolean delete();//删除失败时返回false。
        • void deleteOnExit();//在程序退出时删除指定文件。即使系统发生异常退出程序,也会执行删除文件。
      3. 判断:
        • boolean canExecute();//判断文件是否可以执行
        • boolean canRead();
        • boolean canWrite();
        • int compareTo(File pathname);
        • boolean exists();
        • boolean isDirectory();
        • boolean isFile();
        • boolean isHidden();
        • boolean isAbsolute();//不管文件存在与否,都可以判断
        • 注意:判断一个对象是否为文件或者目录前,要先判断这个对象封装的内容是否存在,通过exists()方法判断。如果对象封装的内容不存在,判断是否为文件或目录的方法永远返回false。
      4. 获取信息:
        • String getName();
        • String getPath();
        • String getParent();//该方法返回获取的父目录,如果获取的相对路径中只有文件名,返回null。
        • String getAbsolutePath();
        • File getAbsoluteFile();
        • File getParentFile();
        • long lastModified();
        • long length();
        • boolean renameTo(File dest);//相当于系统的剪切+重命名
      5. 文件列表:
        • static File[] listRoots();//静态方法,返回系统的所有盘符。
        • String[] list();//返回被封装路径下的文件和文件夹列表,包含隐藏文件,不包含子目录下的文件和文件夹。要求被封装的必须是一个存在的路径,否则返回null。
        • String[] list(FilenameFilter filter)://返回满足过滤器条件的文件和文件夹。
          • FilenameFilter是一个接口,用于过滤文件。
          • 可以创建该接口的子类,覆盖boolean accept(File dir, String name)方法,该方法通过返回值判断文件名为name的文件是否符合过滤条件。
        • File[] listFiles();//返回的元素是封装好的对象,实际开发中这种方式更理想
        • File[] listFiles(FileFilter filter);//实际开发中这种方式更理想
        • File[] listFiles(FilenameFilter filter);//实际开发中这种方式更理想

  • 递归

    • 函数自己调用自己。
    • 注意:
      1. 要有限定条件,避免无限循环。
      2. 要注意递归的次数,避免内存溢出。
    • 应用场景:
      • 当某一功能要重复使用时。
    • 代码举例:
      package cn.itcast.heima;
      //建立指定目录下的java文件列表文件
      import java.io.*;
      import java.util.*;
      class JavaFileList
      {
      	public static void main(String[] args) throws IOException
      	{
      		File dir = new File("F:\\java_heima");
      		File f = new File("f:\\java_heima\\fileList.txt");
      		List<File> list = new ArrayList<File>();
      		fileToList(dir, list);
      		writeToFile(list,f);
      	}
      
      	//利用递归列出符合条件的文件
      	public static void fileToList(File dir, List<File> list) throws IOException
      	{
      		File[] files = dir.listFiles();
      		for (File file : files)
      		{
      			if (file.isDirectory())
      				fileToList(file, list);
      			else if (file.toString().endsWith(".java"))
      				list.add(file);
      		}
      	}
      
      	//将集合中的内容存储到指定文件中
      	public static void writeToFile(List<File> list, File f) throws IOException
      	{
      		BufferedWriter bufw = new BufferedWriter(new FileWriter(f));
      		for (File file : list)
      		{
      			bufw.write(file.getAbsolutePath());
      			bufw.newLine();
      			bufw.flush();
      		}
      		bufw.close();
      	}
      }
      
      程序执行结果:文件列表存储到了指定文件中。

  • IO包中的其他类

    • PrintStream和PrintWriter

      • 打印流提供了打印方法print,可以将各种数据类型的数据都原样打印。
      • 使用流对象作为参数的构造函数创建对象时,可以通过参数设置自动刷新,否则需要手动刷新。
      • PrintStream:字节流
        • 构造函数可以接收的类型:
          1. file对象。File
          2. 字符串路径。String
          3. 字节输出流。OutputStrem
      • PrintWriter:字符流,适用性极强,可以套在BufferedOutputStream或者BufferedWriter外面使用。
        • 构造函数可以接收的类型:
          1. file对象。File
          2. 字符串路径。String
          3. 字节输出流。OutputStrem
          4. 字符输出流。Wrtier
      • 代码举例:
        //将键盘录入保存到文件中
        package cn.itcast.heima;
        import java.io.*;
        class PrintStreamDemo 
        {
        	public static void main(String[] args) throws IOException
        	{
        		//接收键盘录入
        		BufferedReader bufr =
        			new BufferedReader(new InputStreamReader(System.in));
        		
        		//打印流与文件关联,并设置自动刷新
        		PrintWriter out = new PrintWriter(new FileWriter("c.txt"), true);
        		String line = null;
        		while ((line = bufr.readLine()) != null)
        		{
        			if (line.equals("over"))
        				break;
        			out.println(line.toUpperCase());
        		}
        
        		bufr.close();
        		out.close();
        	}
        }
        
        代码执行效果:程序每接收到一行录入,就自动保存到文件中,即使程序没有结束,也不影响输出流的刷新。

    • 合并流:SequenceInputStream

      • 没有对应的输出流。
      • 对多个流进行有序的合并。
      • 应用:
        • 将多个文件的内容合并到一个文件中。
        • 代码举例:
          package cn.itcast.heima;
          //将多个文件的内容合并到一个文件中
          import java.io.*;
          import java.util.*;
          class SequenceDemo 
          {
          	public static void main(String[] args) throws IOException
          	{
          		Vector<FileInputStream> v = new Vector<FileInputStream>();
          
          		v.add(new FileInputStream("1.txt"));
          		v.add(new FileInputStream("2.txt"));
          		v.add(new FileInputStream("3.txt"));
          		Enumeration<FileInputStream> en = v.elements();
          
          		//构造函数SequenceInputStream(Enumeration<? extends InputStream> e);
          		SequenceInputStream sis = new SequenceInputStream(en);
          
          		int len = 0;
          		byte[] buf = new byte[1024];
          		FileOutputStream fos = new FileOutputStream("4.txt");
          		while ((len = sis.read(buf)) != -1)
          		{
          			fos.write(buf, 0, len);
          		}
          
          		fos.close();
          		sis.close();
          	}
          }
          
          代码执行效果:将三个文件的内容按顺序复制到4.txt文件中。

    • 对象的序列化:ObjectInputStream和ObjectOutputStream

      • 用于将堆内存中的对象存储到硬盘上,即对象的序列化存储。
      • ObjectInputStream和ObjectOutputStream需要成对使用。
      • 对象想要实现序列化,必须实现Serializable接口。
      • Serializable接口是一个标记接口,该接口内没有需要实现的方法。
      • Serializable接口原理:
        • 根据分配的serialVersionUID判断被序列化的对象和类是否匹配,防止类被修改而无法匹配。
        • 如果修改类文件后仍旧希望可以进行反序列化操作,可以利用手动设置类的serialVersionUID的值,从而保证类文件的一致性。
        • 非静态成员希望不被序列化时,可以加上transient修饰符。
        • 静态成员在方法区,不在堆中,所以不会被序列化。
      • ObjectOutputStream可以将实现了Serializable的接口的对象转成字节写出到流中。
      • ObjectInputStream可以从流中读取一个ObjectOutputStream流写出的对象。
      • 代码举例:
        package cn.itcast.heima;
        
        import java.io.FileInputStream;
        import java.io.FileNotFoundException;
        import java.io.FileOutputStream;
        import java.io.IOException;
        import java.io.ObjectInputStream;
        import java.io.ObjectOutputStream;
        import java.io.Serializable;
        
        public class Test {
        	public static void main(String[] args) throws IOException, ClassNotFoundException {
        		writeObj();
        		readObj();
        	}
        	
        	//将对象序列化
        	public static void writeObj() throws FileNotFoundException, IOException{
        		ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("c:\\person.object"));
        		oos.writeObject(new Person("zhangsan", 29, "korea"));
        	}
        	
        	//将对象反序列化
        	public static void readObj() throws FileNotFoundException, IOException, ClassNotFoundException{
        		ObjectInputStream ois = new ObjectInputStream(new FileInputStream("c:\\person.object"));
        		Person person = (Person)ois.readObject();
        		System.out.println(person);
        	}
        }
        
        //定义一个实现了Serializable的类
        class Person implements Serializable{
        	
        	//手动设置serialVersionUID,保证程序反序列化时不会受到Person类修改的影响
        	static final long serialVersionUID = 42L;
        	
        	String name;
        	transient int age;//age被transient修饰,因此不会被序列化
        	static String country = "cn";//country为静态成员,不会被序列化
        	
        	public Person(String name, int age, String country){
        		this.name = name;
        		this.age = age;
        		this.country = country;
        	}
        	
        	@Override
        	public String toString() {
        		return name + "," + age + "," + country;
        	}
        }
        程序执行效果:
        • 如果writeObj()和readObj()方法一起执行,此时打印结果为

          因为在调用writeObj()方法时,已将方法区中静态变量country的值修改为"kr",之后再调用readObj()方法时,程序会去方法区查找country的值,即"kr"。
        • 如果先执行一次writeObj()方法,程序结束后再执行一次readObj()方法,此时打印结果为

          因为当程序第二次运行时,只调用了readObj()方法,此时方法区中Person类的country变量的值为"cn",所以程序打印的country变量的值也为"cn"。
        • 总之,被transient或static修饰的成员变量不会被序列化。

    • 管道流:PipedInputStream和PipedOutputStream

      • PipedInputStream:管道输入流,可以从管道输出流中读取数据。
      • PipedOutputStream:管道输出流,可以向管道输入流中写入数据。
      • 输入输出可以直接进行连接,通过结合线程使用。一个线程的PipedInputStream对象能够从另一个线程的PipedOutputStream对象中读取数据。输入输出使用不同的线程,防止死锁。
      • PipedInputStream和PipedOutputStream建立连接:
        • 构造函数或者connect方法。
      • 管道输出流是管道的发送端。
      • 代码举例:
        package cn.itcast.heima;
        
        import java.io.IOException;
        import java.io.PipedInputStream;
        import java.io.PipedOutputStream;
        
        public class Test {
        	public static void main(String[] args) throws IOException, ClassNotFoundException {
        		PipedInputStream pis = new PipedInputStream();
        		PipedOutputStream pos = new PipedOutputStream();
        		pis.connect(pos);//连接管道输入流和管道输出流
        		
        		//开启管道输入流读取线程
        		new Thread(new Read(pis)).start();
        		
        		//开启管道输出流写入线程
        		new Thread(new Write(pos)).start();
        	}
        }
        
        //管道输入流读取线程
        class Read implements Runnable{
        	private PipedInputStream pis;
        	
        	Read(PipedInputStream pis){
        		this.pis = pis;
        	}
        	
        	@Override
        	public void run() {
        		try {
        			byte[] buf = new byte[1024];
        			int len = pis.read(buf);//等待从管道输出流读取数据
        			System.out.println(new String(buf, 0, len));
        			pis.close();
        		} catch (IOException e) {
        			throw new RuntimeException("读取数据失败。");
        		}
        	}
        }
        
        //管道输出流写入线程
        class Write implements Runnable{
        	private PipedOutputStream pos;
        	
        	Write(PipedOutputStream pos){
        		this.pos = pos;
        	}
        	
        	@Override
        	public void run() {
        		try {
        			pos.write("hello world".getBytes());//向管道输入流中写入数据
        			pos.close();
        		} catch (IOException e) {
        			throw new RuntimeException("发送数据失败。");
        		}
        		
        	}
        }
        打印结果:

    • 随机访问文件:RandomAccessFile

      • 不是IO体系中的子类,继承自Object。
      • 是IO包中的成员,同时具备读和写的功能。
      • 内部封装了一个数组,通过指针对数组的元素进行操作。可以通过getFilePointer()获取指针位置,同时可以通过skipBytes(int x)或者seek(long pos)改变指针的位置。
      • 完成读写的原理:内部封装了字节输入流和字节输出流。
      • 通过构造函数看到,该类只能操作文件:
        • RandomAccessFile(File file, String mode);
        • RandomAccessFile(String name, String mode);
        • 其中,模式mode:只读r,读写rw
          • 如果模式为r,不会创建文件。读取的文件如果不存在,会出现异常。
          • 如果模式为rw,会创建不存在的文件,如果文件存在,不会被覆盖。
      • 随机:指的是可以通过调整指针实现从任意指定位置开始操作数据。
      • RandomAccessFile可以在指定位置添加或者修改数据。
      • 应用:
        • 可以实现多线程下载。每个线程负责其中一段数据的写入,互不影响。

    • DataInputStream和DataOutputStream

      • 操作基本数据类型时使用。
      • 可以按照基本数据类型占用空间大小读写数据。

    • 操作内存缓冲数组

      • ByteArrayInputStream和ByteArrayOutputStream
        • ByteArrayInputStream:构造时需要接收数据源,数据源是一个字节数组。
        • ByteArrayOutputStream:内部封装可变长度的字节数组,即数据目的地。所以构造时不需要定义数据目的地。
        • 对数组的操作都可以使用这两个类进行操作。
        • 由于数组流操作的源是数组,不调用底层资源,关闭操作无效(即使关闭了也能继续使用),不产生IO异常。
      • CharArrayReader和CharArrayWrite
        • 操作字符数组。
        • 原理同上。
      • StringReader和StringWriter
        • 操作字符串。
        • 原理同上。
      • 这些类的使用体现了用流的读写思想来操作数据。

  • 字符编码

    • 概述

      • 字符流的出现为了方便操作字符。
      • 更重要是的加入了编码转换。
      • 通过子类转换流来完成:在两个对象进行构造的时候可以加入字符 集。
        • InputStreamReader
        • OutputStreamWriter
      • 编码表的由来:
        • 计算机只能识别二进制数据,早期由来是 电信号。
        • 为了方便应用计算机,让它可以识别各个 国家的文字。
        • 就将各个国家的文字用数字来表示,并一 一对应,形成一张表。
        • 这就是编码表。

    • 常见的编码表

      • ASCII:美国标准信息交换码。
        • 用一个字节的7位可以表示。
      • ISO8859-1:拉丁码表。欧洲码表。
        • 用一个字节的8位表示。
      • GB2312:中国的中文编码表。
      • GBK:中国的中文编码表升级,融合了更多的中文文字符号。
      • Unicode:国际标准码,融合了多种文字。
        • 所有文字都用两个字节来表示,Java语言使用的就是unicode。
      • UTF-8:最多用三个字节来表示一个字符。

    • 转换流的编码应用

      • 可以将字符以指定编码格式存储。
      • 可以对文本数据指定编码格式来解读。

    • 字符编码和解码

      • 编码:字符串->字节数组
      • 解码:字节数组->字符串
      • 如果编码时使用了错误的字符集,会导致问题可能无法修复。
      • 如果编码正确,解码时选择了错误的字符集,可以将解码得到的字符串再按照错误的字符集编码回去得到原来的字节数组,然后重新按照正确的字符集进行解码。但是这种做法不适用与GBK与UTF-8之间的转换,因为这两种字符集都识别中文,会导致得到错误的字节数组,从而无法修复。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值