一、流的分类
按输入输出分类
- 输入流:将外设中的数据读取到内存中。
- 输出流:将内存中的数据写到外设中。
按字节,字符分类
- 字节流:传输过程中,传输数据的最基本单位是字节的流。
- 字符流:传输过程中,传输数据的最基本单位是字符的流。
二、字符流
由来
字节流读取字节数据后,先不操作数据,而是去查找指定的编码表,然后再对文字进行操作。简单来说,字符流=字节流+编码表。
操作文本的字符流
向文件中写数据
FileWriter
主要构造方法:
- new FileWriter(String path):如果此文件不存在,则会自动创建此文件;如果,此文件已经存在,则会覆盖此文件;下同。
- new FileWriter(String path,boolean append):如果append为true,那么表示可以追加数据(也就是多次使用write方法,不会把以前写入文本的数据覆盖掉,可以在文本后面追加数据),下同。
- new FileWriter(File file)
- new FileWriter(File file,boolean append)
实用方法:
- write(String str):把字符串str写入到缓冲区中
- write(char []ch):把字符数组ch写入到缓冲区中
- write(char ch[],int start,int offset):把字符数组ch中从start开始的长度为offset的字符写入到缓冲区中
- write(int c):把字符c写入缓冲区中
- flush():把缓冲区中的数据刷新到磁盘中
- close():关闭流,关闭资源。在关闭之前要先刷新数据(close会自动调用flush方法)
换行:
- 因为在不同的操作系统中,换行符是不一样的,所以需要适配操作系统。Java中已经为我们解决了该问题。
- 换行符的获取:final String line=System.getProperty("line.separator");
IO异常的处理:
- 标准写法:
FileWriter fw = null; try { fw = new FileWriter("G:/xxx.txt", true); fw.write("窗外的麻雀,在电线杆上多嘴,你说这一句,很有夏天的感觉"); } catch (IOException e) { System.out.println(e.getMessage()); } finally { // 只有当fw不为空的时候,才能关闭资源,否则会有空指针异常 if (fw != null) { try { fw.close(); } catch (IOException e) { throw new RuntimeException("老铁,关闭资源失败!"); } } }
读取文件中的数据
FileReader
构造方法:
- new FileReader(String path)
- new FileReader(File file)
实用方法:
- int read():每次读取一个字符,返回读到的字符(int类型);如果返回的是-1,那么就表示已经读到了流的末尾。
- int read(char ch[]):每次返回读取到的字符数,即读到了几个字符
该方法原理图:
- int read(char ch[],int start,int len)
- close():关闭资源。
小练习(复制并粘贴一个文本文件)
- 思路:使用FileReader不断地把文本从源文件中读取出来,并把读到的字符(或字符数组)用FileWriter写入到另一个文件。
- 注意点:复制的过程中可能会产生中文乱码,这是因为编码方式不一致导致的:FileReader读取字符以及FileWriter写字符时的编码方式是根据操作系统的编码方式来的,而txt文本的编码方式可能会与上述编码方式不一致,所以会产生乱码。
- 文本文件复制原理图:
- 实现代码:
public void test01() { FileReader fr = null; FileWriter fw = null; try { fr = new FileReader("G:系统属性.txt"); fw = new FileWriter("F:系统属性.txt", true); char ch[] = new char[1024]; int len = -1; while ((len = fr.read(ch)) != -1) { fw.write(ch, 0, len); fw.flush(); } } catch (IOException e) { e.printStackTrace(); } finally { if (fr != null) { try { fr.close(); } catch (IOException e) { throw new RuntimeException("异常......"); } } if (fw != null) { try { fw.close(); } catch (IOException e) { throw new RuntimeException("异常......"); } } } }
缓冲区
BufferedWriter
构造方法:
- new BufferedWriter(Writer writer):创建BufferedWriter时必须把字符输出流传入,这相当于把资源传入。缓冲区大小为默认。
- new BufferedWriter(Writer writer,int size):指定缓冲区大小为size。
实用方法:
- write(String str)
- write(int c)
- write(char ch[])
- write(char ch[],int start,int len)
- newLine():换行。
- flush():刷新,最好每使用缓冲流写一次数据就刷新一次。
- close():关闭资源。注意: 关闭BufferedWriter后,传入的字符输出流也已经被关闭了
BufferedReader
构造方法:
- new BufferedReader(Reader reader):创建BufferedReader,并把Reader传入。缓冲区大小为默认。
- new BufferedReader(Reader reader,int size):指定缓冲区大小为size。
实用方法:
- int read()
- int read(char ch[]):读取字符,并存入字符数组,读取规律同Reader。
- int read(char ch[],int start,int len)。
- String readlLine():读取一行文本,返回读取到的字符串,如果读取到流的末尾,则返回空。
read()以及readLine()方法原理图:
LineNumberReader
介绍:LineNumberReader是BufferedReader的子类,LineNumberReader记录了当前行号,也可以获取/设置行号。
构造方法:
- new LineNumberReader(Reader reader)
实用方法:
- String readLine()
- read(char ch[])
- read(char[],int start,int len)
- int getLineNumber():获取读取的当前行(是第几行)
- void setLineNumber():行数默认是0,此时就从第一行开始读取;如果自己设置行为n,那么就从第n+1行开始读取。
自定义缓冲区
代码如下:
//自定义BufferedReaderpublic class MyBufferedReader { private FileReader fr; private char ch[] = new char[10]; //缓冲区 private int pos = -1; //缓冲区中指针的位置 private int count = 0; //缓冲区中元素个数 //构造方法 public MyBufferedReader(FileReader fr) { this.fr = fr; } //自定义read方法 //br中的read方法读取数据时从缓冲区中读取 public int myRead() throws IOException { //缓冲区中元素个数为0 if (this.count == 0) { pos = -1; //角标赋值为-1 //从数据源中读取数据存入缓冲区 int len = fr.read(this.ch); if (len == -1) return -1; this.count = len; //从缓冲区中读取一个字符 --this.count; //缓冲区中的字符减少一个 return (int) ch[++pos]; } else { //缓冲区有数据 --this.count; return (int) ch[++pos]; } } //读取一行文本 public String myReadLine() throws IOException { StringBuffer sb = new StringBuffer(); int len = 0; while ((len = this.myRead()) != -1) { char c = (char) len; if (c == '') continue; //遇到''继续读取 if (c == '') //直接就return, // 因为如果跳出了循环,那么就没法分清是读取到了流末尾出的循环, // 还是用break跳出的循环 return sb.toString(); sb.append(c); } return null; //返回null表示上面的循环结束,即为读到了流末尾 } public void close() throws IOException { fr.close(); }}
装帧设计模式
- 介绍:装帧设计模式是为了使类的功能更加强大。
- 实现原理: -请看如下代码:
public class Person { public void eat() { System.out.println("恰饭!!!"); }}public class NewPerson { private Person p; public NewPerson(Person p) { this.p = p; } public void eat() { System.out.println("饭前喝汤"); p.eat(); System.out.println("饭后甜点"); }}
- 装帧设计模式与继承的区别:
- 装饰类比继承更加精炼
三、字符流
FileInputStream
构造方法:
- new FileInputStream(String path)
- new FileInputStream(File file)
实用方法:
- int read():读取一个字节的数据。若返回-1表示读取到了流的末尾。
- int read(byte b[]):读取数组b的长度的字节数据进入b数组中,返回读取到的字节数,返回-1表示读到了流的末尾。详细的读取数据到数组b中的过程同上,此处不再赘述。
- int read(byte b[],int off,int len)
- int available():返回文件中可以读取的数据的估计值(返回字节大小);本方法可以在创建字节数组时定义数组的大小,不过,当文件很大的时候,不能用此方法。
FileOutputStream
构造方法:
- new FileOutputStream(String path)
- new FileOutputStream(File file)
- new FileOutputStream(String path,boolean flag)
- new FileOutputStream(File file,boolean flag)
实用方法
- void write(byte b[])
- void write(byte b[],int off,int len)
- void write(int b):写入一个字符(char),只不过是以int来表示。
BufferedInputStream
构造方法:
- new BufferedInputStream(InputStream fis)
- new BufferedInputStream(InputStream fis, int size)
实用方法:
- int read()
- int read(byte []b)
- int read(byte []b,int off,int len)
- close()
BufferedOutputStream
构造方法:
- new BufferedOutputStream(OutputStream fos)
- new BufferedOutputStream(OutputStream fos,int size)
实用方法:
- void write(byte []b)
- void write(byte []b,int off,int len)
- void write(int ch)
四、转换流
解释
- 看不懂:磁盘中的存储的数据都是“看不懂”的,也就是以字节形式存储的。
- 看得懂:内存中存储的数据都是“看得懂”的,也就是以字符形式存储的。
InputStreamReader
- new InputStreamReader(InputStream in):
- new InputStreamReader(InputStream in,String charsetName):需要传入一个InputStream对象,采用当前平台(操作系统)的解码方式来解码。
OutputStreamWriter:使用指定的解码方式来解码。
- 把“看得懂”的数据转换成"看不懂"的数据,即把字符流编码成字节流。
- new OutputStreamWriter(OutputStream out):需要传入一个OutputStream对象,使用当前平台(操作系统)的编码方式来编码。
- new OutputStreamWriter(OutputStream out,String charsetName):使用指定的编码方式来编码。
转换流原理图
以指定编码方式来读取某文本
遇到这种需求时,就需要用到转换流,因为只有转换流才能指定编解码格式。GBK把一个汉字解析成两个字节,UTF-8把一个汉字解析成三个字节。所以,如果写入文本用的编码格式与读取文本用的解码格式不一样的话,就会读取到乱码(也可能是问号?)。 代码如下:
@Test //使用指定编码方式写入一个字符串到文本文件中 public void test03() throws IOException { OutputStreamWriter outputStreamWriter = new OutputStreamWriter(new FileOutputStream("G:转换流.txt"), "GBK"); outputStreamWriter.write("你好,大兄弟!!"); outputStreamWriter.flush(); } @Test //使用指定的解码格式读取文本文件中的字符串 public void test04() throws IOException { InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream("G:转换流.txt"), "UTF-8"); char[] ch = new char[10]; int len = -1; while ((len = inputStreamReader.read(ch)) != -1) { System.out.println(new String(ch, 0, len)); } }
五、File
构造方法:
- new File(String path):根据路径path把该文件(夹)封装成一个对象。
- new File(String parentPath,String childPath):根据两个字符串的路径,把该文件(夹)封装成对象。这样做的好处是可以灵活填写路径。
- new File(File f,String path):根据文件对象f以及路径path把对应的文件(夹)封装成对象。
File里面的字段(final类型):
- 与系统有关的默认名称分隔符
- File.separator:String类型。Windows下是"",Linux下是"/"。
- File.separatorChar:char类型。
- 与系统有关的路径分隔符
- File.pathSeparator: 此字符用于分隔以路径列表形式给定的文件序列中的文件名,在 UNIX 系统上此字段为:,在 Microsoft Windows 系统上,它为;。
- File.pathSeparatorChar:char类型。
常用方法:
- 获取
- 文件(夹)名字:String getName()
- 文件(夹)路径相对路径:String getPath();是相对于项目所处的路径,File构造方法里面传入什么路径,getPath就获取什么路径。因为路径里面不写磁盘的话,那就一定是相对路径。写了磁盘就是绝对路径。绝对路径:String getAbsolutePath()文件(夹)大小:long length();返回字节大小。文件(夹)最后修改时间:long lastModified(),返回距离1970年1月1日的毫秒数。系统目录及其容量static File [] listRoots():获取系统目录(可以理解为磁盘目录),返回一个File数组。long getFreeSpace():获取该目录(磁盘)的可用空间,返回字节数(long类型)。long getTotalSpace():获取该目录(磁盘)的总空间,返回字节数(long类型)。long getUsableSpace():获取该目录(磁盘)可以被虚拟机使用的空间,返回字节数(long类型)。当前文件夹里面的文件或文件夹名字String [] list()String [] list(FilenameFilter filter):传入一个过滤器(通过文件名字来过滤),过滤器的解释请看下文。注意:File封装的必须是一个文件夹(目录),如果封装的是一个文件,则会抛空指针异常。当前文件夹里面的文件(夹)File [] listFiles():获取某个文件夹里面的文件(夹),返回File数组,可以用来操作文件,而不仅仅是获取文件名字。File [] listFiles(FilenameFilter filter):传入一个过滤器(通过文件名字来过滤)。File [] listFiles(FileFilter filter):传入一个过滤器(通过文件来过滤)。过滤器解释过滤器分为两种,一种是FilenameFilter(通过文件名字来过滤,比如可以过滤掉非.txt后缀的文件),另一种是FileFilter(通过文件来过滤,比如可以过滤掉隐藏文件)。FilenameFilter:FilenameFilter是一个接口,它里面有一个抽象方法boolean accept(File f,String name),文件的过滤就是通过该方法来过滤的。自定义过滤器实现该接口后,实现accept()方法,如果该方法返回true,那么就留下当前遍历到的文件,否则就不留下。FileFilter:FileFilter过滤器的原理与FilenameFilter的原理是一样的,只不过该接口里面的抽象方法是boolean accept(File f),方法参数里面只有一个File对象,并没有文件名。因为该过滤器是通过File来过滤的,与文件名无关。过滤器原理图
- 创建与删除
- 文件创建:boolean createNewFile();根据文件的路径创建一个文件,如果该文件存在,则不创建并返回false;否则创建并返回true。下同,自己体会。(与IO流那边是不同的,那边每一次都是直接覆盖)。删除:boolean delete();删除该文件。删除成功返回true,删除失败返回false。下同,自己体会。如果某个文件里面有内容,比如File f=new File("G:/a"),而a文件里面有其他的文件或文件夹,那么f.delete()就会失败。Windows删除文件是从里面往外面删的。
- 文件创建:boolean mkdir():创建单个文件夹,例如只创建一个a文件夹。boolean mkdirs():创建多层文件夹,例如创建a/b/c/d/e/f。删除:boolean delete();删除该文件。如果File f=new File("G:/a/b/c/d/e/f"),那么此时定位的文件夹是f,故f.delete()删除的是f文件夹,其余文件夹不会被删除。
- 判断
- boolean exists():判断文件(夹)是否存在,存在则返回true,否则返回false。
- boolean isFile():判断是否为文件。
- boolean isHidden():判断文件(夹)是否为隐藏文件。
- boolean isDirectory():判断是否为文件夹。
- boolean canRead():判断该文件(夹)是否可读。
- boolean canWrite():判断该文件(夹)是否可写。
- boolean canExecute():判断该文件(夹)是否可运行。
- 重命名
- boolean renameTo(File des):如果源文件src和目标文件des在同一个磁盘下,那么src.renameTo(des)则会以des的名字来重命名src。如果src和des不在同一个磁盘里,那么src.renameTo(des)则会把src文件剪切到des所在的磁盘,并以des的名字重命名src。