java IO学习随笔笔记
一、file类
1、描述:该类主要用于文件和目录的创建、文件的查找和文件的删除等。File对象代表磁盘中实际存在的文件和目录。通过以下构造方法创建一个File对象:
- File(File parent, String child);(通过给定的父抽象路径名和子路径名字符串创建一个新的File实例。)
- File(String pathname);(通过将给定路径名字符串转换成抽象路径名来创建一个新 File 实例。)
- File(String parent, String child);(根据 parent 路径名字符串和 child 路径名字符串创建一个新 File 实例。)
- File(URI uri);(通过将给定的 file: URI 转换成一个抽象路径名来创建一个新的 File 实例。
2、创建File对象成功后,可以使用以下列表中的方法操作文件:
1 | public String getName() 返回由此抽象路径名表示的文件或目录的名称。 |
2 | public String getParent()、 返回此抽象路径名的父路径名的路径名字符串,如果此路径名没有指定父目录,则返回 null 。 |
3 | public File getParentFile() 返回此抽象路径名的父路径名的抽象路径名,如果此路径名没有指定父目录,则返回 null 。 |
4 | public String getPath() 将此抽象路径名转换为一个路径名字符串。 |
5 | public boolean isAbsolute() 测试此抽象路径名是否为绝对路径名。 |
6 | public String getAbsolutePath() 返回抽象路径名的绝对路径名字符串。 |
7 | public boolean canRead() 测试应用程序是否可以读取此抽象路径名表示的文件。 |
8 | public boolean canWrite() 测试应用程序是否可以修改此抽象路径名表示的文件。 |
9 | public boolean exists() 测试此抽象路径名表示的文件或目录是否存在。 |
10 | public boolean isDirectory() 测试此抽象路径名表示的文件是否是一个目录。 |
11 | public boolean isFile() 测试此抽象路径名表示的文件是否是一个标准文件。 |
12 | public long lastModified() 返回此抽象路径名表示的文件最后一次被修改的时间。 |
13 | public long length() 返回由此抽象路径名表示的文件的长度。 |
14 | public boolean createNewFile() throws IOException 当且仅当不存在具有此抽象路径名指定的名称的文件时,原子地创建由此抽象路径名指定的一个新的空文件。 |
15 | public boolean delete() 删除此抽象路径名表示的文件或目录。 |
16 | public void deleteOnExit() 在虚拟机终止时,请求删除此抽象路径名表示的文件或目录。 |
17 | public String[] list() 返回由此抽象路径名所表示的目录中的文件和目录的名称所组成字符串数组。 |
18 | public String[] list(FilenameFilter filter) 返回由包含在目录中的文件和目录的名称所组成的字符串数组,这一目录是通过满足指定过滤器的抽象路径名来表示的。 |
19 | public File[] listFiles() 返回一个抽象路径名数组,这些路径名表示此抽象路径名所表示目录中的文件。 |
20 | public File[] listFiles(FileFilter filter) 返回表示此抽象路径名所表示目录中的文件和目录的抽象路径名数组,这些路径名满足特定过滤器。 |
21 | public boolean mkdir() 创建此抽象路径名指定的目录。 |
22 | public boolean mkdirs() 创建此抽象路径名指定的目录,包括创建必需但不存在的父目录。 |
23 | public boolean renameTo(File dest) 重新命名此抽象路径名表示的文件。 |
24 | public boolean setLastModified(long time) 设置由此抽象路径名所指定的文件或目录的最后一次修改时间。 |
25 | public boolean setReadOnly() 标记此抽象路径名指定的文件或目录,以便只可对其进行读操作。 |
26 | public static File createTempFile(String prefix, String suffix, File directory) throws IOException 在指定目录中创建一个新的空文件,使用给定的前缀和后缀字符串生成其名称。 |
27 | public static File createTempFile(String prefix, String suffix) throws IOException 在默认临时文件目录中创建一个空文件,使用给定前缀和后缀生成其名称。 |
28 | public int compareTo(File pathname) 按字母顺序比较两个抽象路径名。 |
29 | public int compareTo(Object o) 按字母顺序比较抽象路径名与给定对象。 |
30 | public boolean equals(Object obj) 测试此抽象路径名与给定对象是否相等。 |
31 | public String toString() 返回此抽象路径名的路径名字符串。 |
3、实测部分方法:
构造方法和常用获取信息方法:
@Test public void test() { try { //一、构造方法 //1、创建文件并指定路径,返回布尔值,如果该文件路径+文件名已经存在则返回false,不创建,否则返回true在指定路径创建文件 File file = new File("C:\\Users\\litchon\\Desktop\\2.txt"); boolean newFile = file.createNewFile(); System.out.println(newFile); //2、根据父目录文件+子路径在硬盘上创建文件(测试过,父目录只能是目录,不能指定到文件,这里指的父目录文件是根据指定目录构建的file对象,而不是指文件本身) File parentFile = new File("C:\\Users\\litchon\\Desktop"); File file1 = new File(parentFile,"3.txt"); boolean newFile1 = file1.createNewFile(); System.out.println(newFile1); //二、常用方法 //1、获取文件名 File file2 = new File("C:\\Users\\litchon\\Desktop\\1.txt"); String name = file2.getName(); System.out.println("文件名是:" + name);//文件名是:1.txt //2、获取绝对路径 System.out.println("绝对路径是:"+ file2.getAbsolutePath());//绝对路径是:C:\Users\litchon\Desktop\1.txt //3、得到文件父级目录 System.out.println("父级目录是:"+ file2.getParentFile());//父级目录是:C:\Users\litchon\Desktop //4、获取文件大小(这里按照字节来算) System.out.println("文件大小是(字节):"+ file2.length());//文件大小是(字节):160715(字节:utf8的情况下,英文字符对应一个字节,中文对应3个) //5、文件是否存在(在使用createNewFile方法也能获取到该该文件是否存在) System.out.println("文件是否存在:" + file2.exists());//文件是否存在:true //6、是否是一个文件 System.out.println("是否是文件:" + file2.isFile());//是否是文件:true //7、是否是一个目录 System.out.println("是否是目录:"+ file2.isDirectory());//是否是目录:false System.out.println("是否是目录:"+ parentFile.isDirectory());//是否是目录:true } catch (IOException e) { e.printStackTrace(); } System.out.println("文件创建成功"); }
目录操作方法:
//1、判断文件是否存在,存在则删除(delete方法只能删除空目录或者文件,如果目录下有文件或者子目录需要先删除子目录或者文件才能执行) //(测试中,如果有子目录会执行但返回false,如果不判断直接调用delete方法,也不会抛异常,会返回false) File file = new File("C:\\Users\\litchon\\Desktop\\7.txt"); boolean delete1 = file.delete(); System.out.println("不判断直接无脑删除:" + delete1); boolean exists = file.exists(); if (exists) { boolean delete = file.delete(); System.out.println("删除文件:" + delete); } //2、判断目录是否存在,存在即删除,同第一点,路径指定到目录即可(delete方法只能删除空目录或者文件,如果目录下有文件或者子目录需要先删除子目录或者文件才能执行) //java中,目录可以当成文件看待,只是比较特殊而已 //3、判断目录是否存在,如果存在则创建该目录(这里不是创建文件,而是创建目录)如果不判断直接创建,当目录存在也不会抛异常,返回false File file1 = new File("C:\\Users\\litchon\\Desktop\\3"); boolean mkdir = file1.mkdir(); System.out.println(mkdir); if (file1.exists()){ System.out.println("该目录已经存在"); }else { file1.mkdir();//只创建单级目录,如果创建多级用mkdirs System.out.println("目录创建成功"); } //4、创建多级目录 File file2 = new File("C:\\Users\\litchon\\Desktop\\3\\2\\1\\0"); boolean mkdirs = file2.mkdirs();//会自动补充,例如2 和 1 目录不存在会创建 System.out.println("创建多级目录:"+mkdirs);
二、IO流
1、简介:IO是input和output的简写,input是将文件从磁盘中读入内存中,output是从内存中写入磁盘中持久化,这个过程通常用流的方式实现(stream)
2、流的分类:
- 按操作数据单位不同分为:字节流(8bit)(相较效率低,但二进制文件例如音视频、图片等文件能保证无损操作),字符流(按字符)(文本文件用字符流操作效率更高)
- 按操作数据流的流向不同分为:输入流,输出流
- 按流的角色补通分为:节点流,处理流/包装流
1)java的IO流共涉及40多个类,但都是从如上四个抽象基类派生的。
2)由这四个类派生出来的字类名称都是以其父类名称作为字类名后缀。
3)以上四个类都是抽象类,无法直接创建
3、IO流使用
1、字节流的子类:FileInputStream、ObjectInputStream、BufferedInputStream、FileOutputStream、ObjectOutputStream、BufferedOutputStream(它直接父类是FilteOutputStream,输入流也一样)
字节输入流FileIntputStream测试:
int read = 0; byte[] readByte = new byte[50];//一次性读取50个字节 int readLength = 0; FileInputStream fileInputStream = null; try { fileInputStream = new FileInputStream("C:\\Users\\litchon\\Desktop\\2.txt"); //1、字节输入流单个字节读数据 //一个字节一个字节的读,如果字节读完,此方法将阻止继续执行。返回:数据的下一个字节,如果达到文件的末尾返回-1 /*while ((read = fileInputStream.read()) != -1){ System.out.print((char)read);//将字节转换成char并打印(根据access编码表转换的)但单个读写会出现中问乱码,因为一个中文占三个字节,只读到一个导致乱码 }*/ //2、字节输入流多个字符独写数据 //从该输入流读取最多b.length字节的数据到字节数组 返回:读入缓冲区的总字节数,如果没有更多的数据,返回-1,正常返回实际读取数量 fileInputStream.read(readByte) while ((readLength = fileInputStream.read(readByte)) != -1){ //通过String类的构造方法将字节数组转换为字符串,最后一个参数用字节数组中的实际读取到长度,不要用初始化的长度,否则可能出问题(就是read方法返回的实际读取长度) //如果不用实际读取的长度来构建可能出现的问题: //例如总文件字节长度是11,字节数组是5,第一次和第二次循环时正常,但第三次循环按道理只有最后一个字节,由于字节数组没有被清空,除了第一个元素被替换是正确的,后面4个元素都是上次循环的值 //会出现这种情况:helloword11(文件中的值),但实际读出来打印是:helloword11word 也就是说实际打印多了四个元素 System.out.print(new String(readByte,0, readLength)); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally { try { fileInputStream.close(); } catch (IOException e) { e.printStackTrace(); } }
字节输出流FileOutputStream测试
FileOutputStream fileOutputStream = null; FileOutputStream fileOutputStream1 = null; try { fileOutputStream = new FileOutputStream("C:\\Users\\litchon\\Desktop\\5.txt"); //1、字节输出流 往文件中写入单个字符 //通过测试发现,如果当前目录没有这个文件,则会创建该文件,并写入指定数据 //如果当前目录存在这个文件,则会在该文件中写入这个数据,并将原本的数据覆盖 //如果当前目录不存在,则会抛异常 //如果文件后缀随便乱指定,也能创建成功并有写入的字符 //fileOutputStream.write('B'); //2、字节输出流,往文件中写入字符串(不是字符) 将字符串类型转换成字节数组,然后传入write方法中即可 //fileOutputStream.write("你好啊".getBytes()); //3、字节输出流,往文件中写入字符串,但不将字符串所有元素都写入 byte[] bytes = "你好啊123".getBytes(); fileOutputStream.write(bytes,0, bytes.length - 2);//你好啊1 //4、通过上面的创建字节输出流的方式创建的文件,每次执行完毕后,会将之前的数据覆盖掉,通过以下方式能够设置不覆盖而是追加 fileOutputStream1 = new FileOutputStream("C:\\Users\\litchon\\Desktop\\5.txt",true);//如果设置为true会将字节写入文件末尾而不是开头 fileOutputStream1.write("追加啊追加".getBytes()); fileOutputStream1.write("再次追加啊追加".getBytes()); } catch (IOException e) { e.printStackTrace(); } finally { try { fileOutputStream.close(); fileOutputStream1.close(); } catch (IOException e) { e.printStackTrace(); } }
解和上面两个使用 实现文件拷贝:
FileInputStream fileInputStream = null; FileOutputStream fileOutputStream = null; byte[] bytes = new byte[10]; int byteLength = 0; try { fileInputStream = new FileInputStream("C:\\Users\\litchon\\Desktop\\图片\\9dc22a875627de483cbc5fe653c9d55.png"); fileOutputStream = new FileOutputStream("C:\\Users\\litchon\\Desktop\\2-5.png",true); while ((byteLength = fileInputStream.read(bytes)) != -1){ fileOutputStream.write(bytes,0,byteLength);//一边读一边写,这里仍旧注意不要直接扔字节数组,要指定实际长度,否则会出现之前描述的字节数组未清空问题导致文件异常 System.out.println(new String(bytes,0,byteLength)); } } catch (IOException e) { e.printStackTrace(); } finally { try { if (fileInputStream != null) { fileInputStream.close(); } if (fileOutputStream != null) { fileOutputStream.close(); } } catch (IOException e) { e.printStackTrace(); } }
2、字符流的类:FileReader(继承关系:-->FileReader-->InputStreamReader -->Reader)、FileWriter(继承关系: -->FileWriter--->OutputStreamWrider-->Wrider)
//字符输入输出流使用(FileReader和FileWriter) 使用方式跟字节输入输出流大同小异 //但字符流相对字节流来说,效率要更好,并且没有中问乱码问题,因为它就是一个字符一个字符完整的读,而不是字节,一个中文可能有多个字节 FileReader fileReader = null; FileWriter fileWriter = null; char[] chars = new char[3]; int byteLength = 0; try { fileReader = new FileReader("C:\\Users\\litchon\\Desktop\\2.txt"); fileWriter = new FileWriter("C:\\Users\\litchon\\Desktop\\2-6.txt",true); while ((byteLength = fileReader.read(chars)) != -1){ fileWriter.write(chars,0,byteLength);//一边读一边写,这里仍旧注意不要直接扔字符数组,要指定实际长度,否则会出现之前描述的字节数组未清空问题导致文件异常 System.out.println(new String(chars,0,byteLength)); } } catch (IOException e) { e.printStackTrace(); } finally { try { if (fileReader != null) { fileReader.close(); } if (fileWriter != null) { //fileWriter.flush();//字符输出流这里注意,使用这个方法刷新之前不会将数据写入文件中,只会生成空文件,当然,如果调用了close关闭流也会触发写入 fileWriter.close();//关闭文件流,同时也拥有着flush()方法的功能 } } catch (IOException e) { e.printStackTrace(); } }
3、节点流和处理流
简介:节点流可以从一个特定的数据源读写数据,如FileReader、FileWriter(即从一个节点读写到另一个节点(基础流),无论什么方式处理,包装流则是在此基础上,再次封装强化的流,例如BufferedReader等),处理流也叫做包装流,是连接已经存在的流之上,为程序提供更为强大的独写功能,如BufferedReder、BufferedWriter。
BufferedReader:它有个属性是Reader,也就是说可以接收Reader的任意子类,并且它自带缓冲区,相较基础的流而言,它封装的更加强大一些。其它Buffered的流也是同理(装饰模式)
测试代码:
//包装流的使用测试 BufferedWriter bufferedWriter = null; BufferedReader bufferedReader = null; char[] chars = new char[3]; int byteLength = 0; try { //这里面传入Reader的任意子类都可以,如果想用字节流,将BufferedWriter换成BufferedOutPutStream,将字符输出流换成FileOutputStream即可 bufferedWriter = new BufferedWriter(new FileWriter("C:\\Users\\litchon\\Desktop\\2-6.txt",true)); bufferedReader = new BufferedReader(new FileReader("C:\\Users\\litchon\\Desktop\\2.txt")); while ((byteLength = bufferedReader.read(chars)) != -1){ bufferedWriter.write(chars,0,byteLength);//一边读一边写,这里仍旧注意不要直接扔字符数组,要指定实际长度,否则会出现之前描述的字节数组未清空问题导致文件异常 System.out.println(new String(chars,0,byteLength)); } } catch (IOException e) { e.printStackTrace(); } finally { try { if (bufferedReader != null){ bufferedReader.close(); } if (bufferedWriter != null){ bufferedWriter.close(); } } catch (IOException e) { e.printStackTrace(); } }
处理流(包装流)BufferedInputStream和BufferedOutputStream的使用测试:
//包装流的使用测试 BufferedWriter bufferedWriter = null; BufferedReader bufferedReader = null; BufferedReader bufferedReader1 = null; BufferedWriter bufferedWriter1 = null; BufferedInputStream bufferedInputStream = null; BufferedOutputStream bufferedOutputStream = null; char[] chars = new char[3]; int byteLength = 0; String lineData; byte[] bytes = new byte[10]; int len = 0; try { //一、原本节点流使用方式 //这里面传入Reader的任意子类都可以,如果想用字节流,将BufferedWriter换成BufferedOutPutStream,将字符输出流换成FileOutputStream即可 bufferedWriter = new BufferedWriter(new FileWriter("C:\\Users\\litchon\\Desktop\\2-6.txt",true)); bufferedReader = new BufferedReader(new FileReader("C:\\Users\\litchon\\Desktop\\2.txt")); while ((byteLength = bufferedReader.read(chars)) != -1){ bufferedWriter.write(chars,0,byteLength);// System.out.println(new String(chars,0,byteLength)); } //二、按行读取(效率更高)(但一定要注意,这里面传入的是字符流,一定不要去读写二进制文件【声音、视频、word文档、pdf等】,会出问题,可能会造成文件损坏) bufferedReader1 = new BufferedReader(new FileReader("C:\\Users\\litchon\\Desktop\\2.txt")); bufferedWriter1 = new BufferedWriter(new FileWriter("C:\\Users\\litchon\\Desktop\\2-7.txt", true)); //bufferedReader1.readLine()方法会获取到行数据,如果没有数据了则返回null while ((lineData = bufferedReader1.readLine()) != null){ bufferedWriter1.write(lineData);//这里如果需要插入换行符的话,一定要注意跟系统相关,系统不同换行符也不同,也可以用newLine()方法 bufferedWriter1.newLine(); System.out.println(lineData); } //三、传入字节流拷贝 bufferedInputStream = new BufferedInputStream(new FileInputStream("C:\\Users\\litchon\\Desktop\\图片\\20220110_105511.mp3")); bufferedOutputStream = new BufferedOutputStream(new FileOutputStream("C:\\Users\\litchon\\Desktop\\2-8.mp3", true)); while ((len = bufferedInputStream.read(bytes)) != -1){//read方法返回的是实际读取长度 bufferedOutputStream.write(bytes,0,len);//细节一定要注意,指定实际长度而不是字节数组的总长度,或者每次循环完清空数组也行 System.out.println(new String(bytes,0,len)); } } catch (IOException e) { e.printStackTrace(); } finally { try { if (bufferedReader1 != null){ bufferedReader1.close(); } if (bufferedWriter1 != null){ bufferedWriter1.close(); } if (bufferedReader != null){ bufferedReader.close(); } if (bufferedWriter != null){ bufferedWriter.close(); } } catch (IOException e) { e.printStackTrace(); } } }
处理流中的ObjectInputStream和ObjectOutputStream:
简介:为什么需要有这两个类?因为有的时候我们保存数据的时候,同时希望能够保存数据的数据类型,以及有时候需要保存一些对象,并且希望还能够再次转换回来,简而言之,就是希望能够就基本数据类型或者对象进行序列化和反序列化操作。
@Test public void test() { //File file = new File("C:\\Users\\litchon\\Desktop\\2.txt"); ObjectOutputStream objectOutputStream = null; ObjectInputStream objectInputStream = null; byte[] bytes = new byte[1024]; int len = 0; //使用ObjectInputStream完成数据序列化保存以及反序列化读取,注意,序列化保存的对象必须实现Serializable接口 try { objectOutputStream = new ObjectOutputStream(new FileOutputStream("C:\\Users\\litchon\\Desktop\\8.dat")); objectInputStream = new ObjectInputStream(new FileInputStream("C:\\Users\\litchon\\Desktop\\8.dat")); //注意细节:这些传入的基本类型数据会被自动装箱成包装类型,而这些包装类型都已经实现了序列化接口,无需自己处理 //另外如果需要保存类型,就一定要用对应类型的方法,例如writeInt(),如果直接用write()方法在反序列化的时候会出问题 objectOutputStream.writeInt(1); objectOutputStream.writeBoolean(true); objectOutputStream.writeDouble(0.16); objectOutputStream.writeUTF("你现在还好吗");//字符串用这个UTF方法,没有writeString方法 objectOutputStream.writeObject(new Dog("小狗狗",12));//注意,写入的对象一定要实现Serializable接口! //注意: //1、反序列化的时候,不是再像基础的输入流一样读,那样读出来的数据并没有反序列化并且带有乱码 //2、读数据的时候一定要跟写数据时的顺序一致,第一个是int就要先读int,否则会出错 //3、反序列化的时候,如果在序列化之后改变了对象属性或者其它方法,在反序列化时会出问题。例如序列化时对象中没有toString方法,在序列化之后增加了toString方法,反序列化时会抛异常 //4、在序列化和反序列化时,如果涉及到对象,一定要用同包同类 //5、序列化对象时,默认会将所有属性都序列化,但除了static或者transient(不被序列化的关键字)修饰的成员 System.out.println(objectInputStream.readInt()); System.out.println(objectInputStream.readBoolean()); System.out.println(objectInputStream.readDouble()); System.out.println(objectInputStream.readUTF()); System.out.println(objectInputStream.readObject()); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } finally { try { //关流的顺序,先开后关,后开先关 if (objectInputStream != null){ objectInputStream.close(); } if (objectOutputStream != null){ objectOutputStream.close(); } } catch (IOException e) { e.printStackTrace(); } } } } class Dog implements Serializable{ //序列化的版本号,可以提高兼容,例如序列化后,给这个类加了一个属性,它只会认为版本不同,但不会认为这是两个类而导致出现异常 private static final long serialVersionUID = 1L; private String name; private Integer age; @Override public String toString() { return "Dog{" + "name='" + name + '\'' + ", age=" + age + '}'; } public Dog(String name, Integer age) { this.name = name; this.age = age; } }
4、转换流(将字符流转换成字节流就可以用转换流)为啥要用?:因为读取文件时,如果是默认的utf8编码没问题,但如果不是utf8编码且文件中又有汉字,就会出现乱码问题,因为读取文件的时候没有办法指定读取的解码方式,所以需要转换流,它可以指定解码方式。
BufferedReader bufferedReader = null; String data; try { //转换流:解决读数据时乱码问题,使用InputStreamReader,可以指定字符集。如果不好记,看命名规则,InputStream是字节输入流,而Reader是字符输入流,他们合在一起就是转换流 //使用bufferedReader包装类是为了更加方便灵活以及高效,可以不用,不要把BufferedReader跟转换流混淆 //下面的代码就是将FileInputStream转换成了InputStreamReader,字节转换成了字符流并指定了字符集 //如果直接使用字符流的话,无法指定字符集,可能发生乱码问题 bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream("C:\\Users\\litchon\\Desktop\\2.txt"),"utf-8")); while ((data = bufferedReader.readLine()) != null) { System.out.println(data); } //输出流转换同理 } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { try { bufferedReader.close(); } catch (IOException e) { e.printStackTrace(); } }
打印流:PrintStream(字节流,父类是filterOutputStream,而FilterOutputStream实现了OutputStream)和PrintWriter(字符流,继承关系原理同左)。打印流只有输出流,没有输入流。打印流不仅可以打印到显示器上,还能打印到文件里面
5、总结:
上面这么多流,搞清楚以下几点概念:
- 输入流和输出流的概念,输入流是从硬盘读到内存中,输出流是从内存持久化(保存)到硬盘中。
- 节点流和处理流的区别,节点流是基础流,如果你非要使用节点流也是可以的,不使用处理流也能完成需求,只不过在灵活性、简便性、效率性上比不上处理流。
在选择流这块,需要了解以下情况:
- 搞清楚流的分类,字节流和字符流,字节流:InputStream和OutPutStream,字符流:Writer和Reader,基础使用时,用FileInputStream和OutPutStream传入File对象即可,字符流基础使用时用FileWriter和FileReader。
- 字符流和字节流的选择,当我们需要读或者写二进制文件如图片、音频、视频等等文件时,选择字节流,因为字符流会导致文件受损,当我们需要读写文本文件时,选择字符流,因为字符流效率更高。
- 当我们需要使用字符流并指定字符编码的时候,使用IntputStreamWriter和OutputStreamReader,即转换流,转换流的构造方法中可以指定字符集。
- 当我们需要更加灵活、效率更高时,可以使用处理流,如BufferedWriter、BufferedReader、BufferedInputStream、BufferedOutputStream,他们可以传入对应的Reader、Writer、InputStream、OutputStream的所有子类,关流时只需要关Buffered即可。
- 如果我们即需要指定字符集又需要效率更高使用Buffered时,可以最外层使用Buffered,传入转换流InputStreamWirer或者OutputStreamReader,转换流中又传入字节流,如FileInputStream或者FileOutputStream。
- 当我们需要保存数据结构时,使用ObjectInputStream和ObjectOutputStream。
使用流时需要注意的细节问题:
- 无论什么流,使用了之后养成关闭流的习惯,如果你用了处理流包装则关闭处理流即可,关流顺序保持先开后关,后开先关顺序。
- 在使用流读写文件时,需要注意,如果使用了字节数组做缓冲一次性多个字节读写,一定要传入当前循环中实际读取到的字节长度,而不是使用整个字节数组的长度,这样最后可能会导致读写来的数据又多余。
- 在使用字符流写出时,一定要注意,必须关流或者使用flush()方法,否则最终会导致数据没有写入文件中。
- 在使用ObjectInputStream和ObjectOutStream时,需要注意实体类读入写出时使用的是用一个类,全限定类名必须一致,并且必须序列化,如果没有指定版本号,在写出数据之后,不能更改实体类的属性,会导致无法读取数据,且写出和读入各个类型的顺序必须一致。
File和流的区别:
File是文件类,使用这个类可以操作文件,获取文件信息、创建文件、删除文件、判断文件是否存在等等,但它只能指定到文件位置(可以是文件目录也可以仅仅是一个目录),并不能直接读取文件内容和往文件中写入数据,如果我们需要往文件中读取数据或者写入数据,则需要用流来完成,而流里面则有对应的FileInputStream、FileOutputStream、FileWriter、FileReader等类,可以直接指定文件目录,所以使用它们即可。