一、流的分类
按输入输出分类
- 输入流:将外设中的数据读取到内存中。
- 输出流:将内存中的数据写到外设中。
按字节,字符分类
- 字节流:传输过程中,传输数据的最基本单位是字节的流。
- 字符流:传输过程中,传输数据的最基本单位是字符的流。
二、字符流
由来
字节流读取字节数据后,先不操作数据,而是去查找指定的编码表,然后再对文字进行操作。简单来说,字符流=字节流+编码表。
操作文本的字符流
向文件中写数据
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行开始读取。
自定义缓冲区
代码如下:
//自定义BufferedReader
public 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 == '\r')
continue; //遇到'\r'继续读取
if (c == '\n')
//直接就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。