知识点总结于毕向东Java基础视频教程25天版本,侵权请联系删除。
IO流(一)
IO(Input Output)流
概述
- IO流用来处理设备之间的数据传输;
- Java对数据的操作是通过流的方式;
- Java用于操作流的对象都在IO包中;
- 流按操作数据分为两种:字节流与字符流;
字节流抽象基类: InputStream ,OutputStream
字符流抽象基类: Reader ,Writer
由这四个类派生出来的子类名称都是以其父类名作为子类名的后缀
如:InputStream的子类FileInputStream,:Reader的子类FileReader
- 流按流向分为:输入流,输出流。
注意:
- 定义文件路径时,可以用“/”或者“\”;
- 在创建一个文件时,如果目录下有同名文件将被覆盖;
- 在读取文件时,必须保证该文件已存在,否则出异常。
FileWriter
FileWriter类从OutputStreamWriter类继承而来。该类按字符向流中写入数据。
构造方法:
FileWriter(File file)//给一个File对象构造一个FileWriter对象。
FileWriter(File file, boolean append)//给一个File对象构造一个FileWriter对象。
FileWriter(FileDescriptor fd)//构造与文件描述符关联的FileWriter对象。
FileWriter(String fileName)//构造一个给定文件名的FileWriter对象。
FileWriter(String fileName, boolean append)
//构造一个FileWriter对象,给出一个带有布尔值的文件名,表示是否附加写入的数据。
FilerWriter类中只有构造方法,所以具体方法只要参考父类
注意:
- FileWriter一被初始化就需要明确被操作的文件,即构造方法不能为空;
- 当FileWirter被实例化,就必须对异常进行处理;
- write方法只是将数据写入到流中,需要通过刷新的方式将缓冲中的数据刷到目的地。
import java.io.*;
class FileWriterDemo
{
public static void main(String[] args) throws IOException
{
//创建一个FileWriter对象。该对象一被初始化就必须要明确被操作的文件。
//而且该文件会被创建到指定目录下。如果该目录下已有同名文件,将被覆盖。
//其实该步就是在明确数据要存放的目的地。
FileWriter fw = new FileWriter("demo.txt");
//调用write方法,将字符串写入到流中。
fw.write("abcde");
//刷新流对象中的缓冲中的数据。
//将数据刷到目的地中。
//fw.flush();
//关闭流资源,但是关闭之前会刷新一次内部的缓冲中的数据。
//将数据刷到目的地中。
//和flush区别:flush刷新后,流可以继续使用,close刷新后,会将流关闭。
fw.close();
}
}
IO异常处理方式
class Demo
{
public static void main(String[] args)
{
//声明FileWriter类型引用,避免在try中创建了局部变量导致其它代码块无法识别
FileWriter fw = null;
try
{
fw = new FileWriter("demo.txt");//初始化对象
fw.write("abcdefg");
}
catch (IOException e)
{
System.out.println("catch:"+e.toString());
}
finally
{
//对于任何对流的操作都会抛出IO异常,所以也需要close方法进行异常处理
try
{
//如果引用为空则说明初始化失败,则无需进行关闭流的动作
if(fw!=null)//如果不加if会出现空指针异常,因为fw没有具体的引用的实例
fw.close(); //关闭流的动作一定要执行
}
catch (IOException e)
{
System.out.println(e.toString());
}
}
}
}
文件的续写
当指定目录下已经存在要写入的文件,如何通过FileWriter使它不会覆盖原文件并续写内容呢?
import java.io.*;
class Demo
{
public static void main(String[] args) throws IOException
{
/*因为FileWriter在初始化时就会创建文件,所以如果不想出现覆盖,则应该观察它的构造函数*/
//构造一个FileWriter对象,给出一个带有布尔值的文件名,表示是否附加写入的数据
FileWriter fw =new FileWriter("D:\\Demo.txt",true);
fw.write("hh");
fw.write("nihao\r\nxiexie"); //xiexie换行
fw.close();
}
}
注意:
在Windows系统中,回车符是由两个字符来表示"\r\n"
FileReader
- FileReader类从InputStreamReader类继承而来。该类按字符读取流中数据。
所以FileReader类只有构造方法,对对象的操作方法需要参考父类Reader
可以通过以下几种构造方法创建需要的对象:
FileReader(File file) //在给定从中读取数据的 File 的情况下创建一个新 FileReader
FileReader(FileDescriptor fd) //在给定从中读取数据的 FileDescriptor 的情况下创建一个新 FileReader
FileReader(String fileName) //在给定从中读取数据的文件名的情况下创建一个新 FileReader
文本文件读取方式一
Reader中的一些共性方法:
int read():读一个字符,返回int
//如果已经达到流的末尾,则返回-1
abstract void close():关闭流并释放与之相关联的任何系统资源
import java.io.*;
class Demo
{
public static void main(String[] args) throws IOException
{
//创建一个文件读取流对象,和指定名称的文件相关联。
//要保证该文件是已经存在的,如果不存在,会发生异常FileNotFoundException
FileReader fr = new FileReader("demo.txt");
//调用读取流对象的read方法。
//read():一次读一个字符。而且会自动往下读。
int ch = 0;
while((ch=fr.read())!=-1)
{
System.out.println(
}
fr.close();
}
}
文本文件读取方式二
int read(char[] cbuf):将字符读入数组
//事先定义字符数组,再将读到的字符按顺序存入数组,并返回读取的字符个数
//如果已经达到流的结尾,则为-1
注意:如果数组存满,则返回数组的大小,因为无法再存入
import java.io.*;
class Demo2
{
public static void main(String[] args) throws IOException
{
FileReader fr = new FileReader("demo.txt");
//定义一个字符数组。用于存储读到字符。
//该read(char[])返回的是读到字符个数。
char[] buf = new char[1024];//一般定义数组大小为1024的倍数,即2个字节
int num = 0;
while((num=fr.read(buf))!=-1)
{
//将字符数组转换成字符串,转换的长度为0到每次读取字符个数
System.out.print(new String(buf,0,num));
//这是为了避免最后一次读取时由于剩下的字符个数小于字符数组长度,导致输出了未被覆盖的字符
}
fr.close();
}
}
注意:如果文本文件字符数超过了数组长度,那么如果是换行打印(println)会出现换行,那么与原格式就不符,所以应当使用print打印。
拷贝文本文件
/*
复制的原理:
其实就是将C盘下的文件数据存储到D盘的一个文件中。
步骤:
1,在目标盘创建一个文件。用于存储原盘文件中的数据。
2,定义读取流和原盘文件关联。
3,通过不断的读写完成数据存储。
4,关闭资源。
*/
import java.io.*;
class CopyDemo
{
public static void main(String[] args) throws IOException
{
//copy_1(); 边读边写
copy_2(); //先存再写 速度快
}
public static void copy_2()
{
FileWriter fw = null;
FileReader fr = null;
try
{
fw = new FileWriter("DemoA_copy.txt");
fr = new FileReader("DemoA.java");
char[] buf = new char[1024];
int len = 0;
while((len=fr.read(buf))!=-1)
{
fw.write(buf,0,len); //避免浪费空间
}
}
catch (IOException e)
{
throw new RuntimeException("读写失败");
}
finally
{
if(fr!=null)
try
{
fr.close();
}
catch (IOException e)
{
}
if(fw!=null)
try
{
fw.close();
}
catch (IOException e)
{
}
}
}
//从C盘读一个字符,就往D盘写一个字符。
public static void copy_1()throws IOException
{
//创建目的地。
FileWriter fw = new FileWriter("DemoA_copy.txt");
//与已有文件关联。
FileReader fr = new FileReader("DemoA.java");
int ch = 0;
while((ch=fr.read())!=-1)
{
fw.write(ch);
}
fw.close();
fr.close();
}
}
字符流的缓冲区
程序频繁地操作一个资源(如文件),则性能会很低,此时为了提升性能,就可以将一部分数据暂时读入到内存的一块区域中,以后直接从此区域中读取数据即可,因为读内存速度比较快,这样提高性能。在IO中引入缓冲区,主要是提高流的读写效率。
BufferedWriter
将文本写入字符输出流,缓冲字符,以提供单个字符,数组和字符串的高效写入。
除了Writer类中的方法,BufferedWriter也有特有的方法:
public void newLine() throws IOException:写入一个行分隔符
//与Windows中\r\n不同的是,该方法在其它平台也能换行,具有跨平台性
public void close() throws IOException:关闭此流,但要先刷新它
//实际上调用了Writer类的close方法,所以后续不需要再去关闭流对象
import java.io.*;
class Demo
{
public static void main(String[] args) throws IOException
{
//创建一个字符写入流对象。
FileWriter fw = new FileWriter("buf.txt");
//为了提高字符写入流效率。加入了缓冲技术。
//只要将需要被提高效率的流对象作为参数传递给缓冲区的构造函数即可。
BufferedWriter bufw = new BufferedWriter(fw);
for(int x=1; x<5; x++)
{
bufw.write("abcd"+x);
bufw.newLine();
bufw.flush();
}
//记住,只要用到缓冲区,就要记得刷新。
//bufw.flush();
//其实关闭缓冲区,就是在关闭缓冲区中的流对象。
bufw.close();
}
}
BufferedReader
从字符输入流读取文本,缓冲字符,以提供字符,数组和行的高效读取。
Buffered的特有方法:
public String readLine() throws IOException:读取一个文本行。
//通过下列字符之一即可认为某行已终止:换行 ('\n')、回车 ('\r') 或回车后直接跟着换行
//返回包含行的内容的字符串,不包括任何行终止字符,如果已达到流的末尾,则为null
通过FileOuputStream类中的available()方法发现,我的win10系统换行符只占一位,经测试为\r
import java.io.*;
class BufferedReaderDemo
{
public static void main(String[] args) throws IOException
{
//创建一个读取流对象和文件相关联。
FileReader fr = new FileReader("buf.txt");
//为了提高效率。加入缓冲技术。将字符读取流对象作为参数传递给缓冲对象的构造函数。
BufferedReader bufr = new BufferedReader(fr);
String line = null;
while((line=bufr.readLine())!=null)
{
System.out.println(line); //回车符没有被返回,需要手动换行
}
bufr.close();
}
}
装饰设计模式
在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。
- 对原有类进行了功能的改变,增强。
- 装饰模式的基本格式:
1.通过构造传参把需要加强的类传过来(比如要装饰房子类,那么就必须先有房子)
2.描述具体需要增强的功能
装饰设计模式和继承的区别:
通过继承的方式可以使子类具有父类的属性和方法。子类继承父类后,因为一些业务需求可以通过重写的方式来加强父类的方法的一些功能,也可以重新定义某些属性,即覆盖父类的原有属性和方法,使其获得与父类不同的功能。而装饰者模式的最基本的功能就是对传入的一个对象进行功能的加强与优化。那么问题来了,既然继承方式也可以对已有类或对象进行加强,那为什么还要衍生出装饰者模式这一思想呢?
装饰者模式的意图定义为:动态地给一个对象添加一些额外的职责。单单就这简短的依据话就可以知道,除了最基本的增强已有对象的功能外,恐怕装饰者模式存在的更重要的意义就在于动态的为对象添加一些功能(或分配额外职责)。那么这句话到底是什么意思呢?它到底解决了什么样的问题呢?下面通过一些最简单的示例来具体分析一下:
定义个MyReader类,专门用于读取数据。
由于文件的不同,读取数据的方式也不同,所以需要定义子类用于不同文件的读取。且为了提高效率,需要采取缓冲的方式,那么就需要再定义MyBufferReader类。
如果采用继承的方式,就是如下结构:
MyReader
|–MyTextReader
|–MyBufferTextReader
|–MyMediaReader
|–MyBufferMediaReader
|–MyDataReader
|–MyBufferDataReader
我们会发现,都是为了提高效率,却用了相同的方式创建了各自的子类,扩展性很差。
那是否可以找到其参数的共同类型,通过多态的形式,提高扩展性呢?
这时候就需要采用装饰者模式了:
MyReader
|–MyTextReader
|–MyMediaReader
|–MyDataReader
|–MyBufferReader
通过继承将每一个子类都具备缓冲功能。那么继承体系会复杂,并不利于扩展。
现在优化思想,单独描述一下缓冲内容。将需要被缓冲的对象传递进来。也就是,谁需要被缓冲,谁就作为参数传递给缓冲区。
这样继承体系就变得很简单,优化了体系结构。
装饰模式比继承要灵活。避免了继承体系臃肿,而且降低了类于类之间的关系。
装饰类因为增强已有对象,具备的功能和已有的是相同的,只不过提供了更强功能。
所以装饰类和被装饰类通常是都属于一个体系中的。
LineNumberReader
LineNumberReader 是java字符流中的一员,它继承自 BufferedReader,只是在 BufferedReader 基础上,提供了对当前流位置所在文本行的标记记录。
int getLineNumber():获取当前行号,默认情况下,行编号从 0 开始
void setLineNumber(int lineNumber):设置当前行号
/*setLineNumber(int)方法仅仅是改变从getLineNumber()返回的值而已,而不会改变流的当前位置。
也就是说lineNumber只是一个记录值,并不影响流的读取过程。*/
import java.io.*;
class Demo
{
public static void main(String[] args)throws IOException
{
FileReader fr = new FileReader("DemoA.java");
LineNumberReader lnr = new LineNumberReader(fr);
String line = null;
lnr.setLineNumber(100);
while((line=lnr.readLine())!=null)
{
System.out.println(lnr.getLineNumber()+":"+line);
}
lnr.close();
}
}
字节流File读写操作
FileOutputStream
FileOutputStream可以往文件里写入字节流,它是OutputStream的子类,所以可以像使用OutputStream那样使用FileOutputStream。
构造方法:
FileOutputStream(File file):
使用File对象打开本地文件,从文件读取数据
FileOutputStream(File file, boolean append):
使用File对象打开本地文件,从文件读取数据,并选择是否追加
FileOutputStream(FileDescriptor fdObj):
创建文件输出流以写入指定的文件描述符,表示与文件系统中实际文件的现有连接。
FileOutputStream(String name):
不使用File对象,直接传入文件路径
FileOutputStream(String name, boolean append):
不使用File对象,直接传入文件路径,并选择是否追加。
import java.io.*;
class Demo
{
public static void main(String[] args)throws IOException{
FileOutputStream fos = new FileOutputStream("D:\\fos.txt");
//因为字符也是字节,所以只需要把对应的字符转换成字节就可以通过字节流操作
fos.write("abcde".getBytes());
fos.close();
}
}
FileInputStream
FileInputStream流被称为文件字节输入流,意思指对文件数据以字节的形式进行读取操作如读取图片视频等。
构造方法:
FileInputStream(File file)
通过打开与File类对象代表的实际文件的链接来创建FileInputStream流对象
FileInputStream(FileDescriptor fdObj)
创建 FileInputStream通过使用文件描述符 fdObj ,其表示在文件系统中的现有连接到一个实际的文件。
FileInputStream(String name)
通过指定的字符串参数来创建File类对象,再与File对象所代表的实际路径建立链接创建FileInputStream流对象
特有方法:
int available():返回的实际可读字节数,也就是总大小(包括换行符等)
import java.io.*;
class Demo
{
public static void main(String[] args)throws IOException{
FileInputStream fis = new FileInputStream("D:\\fos.txt");
byte[] buf = new byte[fis.available()];//定义一个刚刚好的缓冲区。不用在循环了。
fis.read(buf);
System.out.println(new String(buf));
fis.close();
}
}
注意:虽然使用available方法可以定义文件对象的实际长度,但是如果文件过大,而虚拟机设置的内存无法满足,那么程序就会崩溃。所以谨慎使用,一般还是考虑定义容量为1024的倍数的数组配合循环较为安全高效。
字节流的缓冲区
字节流的缓冲区同字符流的缓冲区一样,都是为了提高字节流的读写效率。
/*
演示jpg的复制。通过缓冲区。
BufferedOutputStream
BufferedInputStream
*/
import java.io.*;
class Demo
{
public static void main(String[] args) throws IOException
{
BufferedInputStream bufis = new BufferedInputStream(new FileInputStream("D:\\img.jpg"));
BufferedOutputStream bufos = new BufferedOutputStream(new FileOutputStream("D:\\img_copy.jpg"));
int by = 0;
while((by=bufis.read())!=-1)
{
bufos.write(by);
}
bufos.close();
bufis.close();
}
}
自定义字节流的缓冲区(read和write的特点)
import java.io.*;
class Demo
{
public static void main(String[] args) throws IOException
{
MyBufferedInputStream bufis = new MyBufferedInputStream(new FileInputStream("D:\\img.jpg"));
BufferedOutputStream bufos = new BufferedOutputStream(new FileOutputStream("D:\\img_copy.jpg"));
int count=0;
while((count=bufis.myRead())!=-1){
bufos.write(count);
}
bufis.myClose();
bufos.close();
}
}
class MyBufferedInputStream
{
private FileInputStream fis;
private byte[] buf=new byte[1024*4];
private int pos=0,count=0;
MyBufferedInputStream(FileInputStream in){
this.fis=in;
}
public int myRead() throws IOException{
if(count==0){//当计数器为0即存完的时候,重新初始化一次
count=fis.read(buf);//将读到到字节存储到数组buf中
if(count==-1)return count;//判断是否已到文件尾
pos=0;//初始化指针,即记录的数组的下标位置
byte b=buf[0];//读取数组存储第一个字符
count--;pos++;//数组容量-1,下标进一位
return b&255;//让b和int型255进行与运算,类型提升为int
}
else if(count>0){//当计数器>0即数组还有容量时
byte b=buf[pos];//读取当前记录的数组下标位的存储的字节
count--;pos++;
return b&0xff;//让b和16进制表示的255进行与运算,类型提升为int
}
return -1;
}
public void myClose() throws IOException{
fis.close();
}
}
为什么要让b和255进行与运算呢?
因为read方法读取的是一个字节,或者字节数组。而一个字节占8位二进制,即0000 0000—1111 1111,而1111 1111刚好就是int的-1。
(-1就是+1的取反加1,0000 0001 取反 -> 1111 1110,加1 -> 1111 1111)
如果文件开头第一个字节就是1111 1111,则返回的直接就是十进制的-1(方法返回类型为int,32个1),这样就被当做是读到文件末尾而导致读取结束。因此要与上255,即(00000000 00000000 00000000 11111111),这样的话,才保证原字节值没变,而且不被当做-1而导致读取结束。
把字节提升为int后,读一个字节的数据返回的容量就是四个字节,那么容量不就是直接提升了原来的4倍吗?
api中的read方法也是将字节数据向上提升(即二进制位补0提升了容量),为了避免字节数据出现-1的情况发生,而write方法就是将转换后的int型数据的最低8位写出,剩下的抛弃,做了强制转换的动作,避免了数据的冗余同时保证原数据的不变。
读取键盘录入
System.in读取标准输入设备数据(从标准输入获取数据,一般是键盘),其数据类型为InputStream。
/*需求:
通过键盘录入数据。
当录入一行数据后,就将该行数据进行打印。
如果录入的数据是over,那么停止录入。*/
import java.io.*;
class Demo
{
public static void main(String[] args) throws IOException
{
InputStream in = System.in;
StringBuilder sb = new StringBuilder();
while(true)
{
int ch = in.read();
if(ch=='\r')
continue;
if(ch=='\n')
{
String s = sb.toString();
if("over".equals(s))
break;
System.out.println(s.toUpperCase());
sb.delete(0,sb.length()); //每次输出都要清空一次,不然内容与over肯定不相同
}
else
sb.append((char)ch);
}
}
}
转换流
读取转换流InputStreamReader
转换流 java.io.InputStreamReader,是Reader的子类,是从字节流到字符流的桥梁。
该类读取字节,并使用指定的字符集将其解码为字符。它的字符集可以由名称指定,也可以接受平台的默认字符集。
通过转换流,我们就可以简化上面的读取键盘录入的代码:
import java.io.*;
class Demo
{
public static void main(String[] args) throws IOException
{
//获取键盘录入对象。
//InputStream in = System.in;
//将字节流对象转成字符流对象,使用转换流。InputStreamReader
//InputStreamReader isr = new InputStreamReader(in);
//为了提高效率,将字符串进行缓冲区技术高效操作。使用BufferedReader
//BufferedReader bufr = new BufferedReader(isr);
//键盘的最常见写法。
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
String line = null;
while((line=bufr.readLine())!=null)
{
if("over".equals(line))
break;
System.out.println(line.toUpperCase());
}
bufr.close();
}
}
写入转换流OutputStreamWriter
OutputStreamWriter是字符通向字节流的桥梁:可以将指定的charset将要写入流中的字符编码成字节。它使用的字符集可以由名称指定,也可以被明确指定,或者可以接受平台的默认字符集。
import java.io.*;
class TransStreamDemo
{
public static void main(String[] args) throws IOException
{
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
// OutputStream out = System.out;
// OutputStreamWriter osw = new OutputStreamWriter(out);
// BufferedWriter bufw = new BufferedWriter(osw);
BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));
String line = null;
while((line=bufr.readLine())!=null)
{
if("over".equals(line))
break;
bufw.write(line.toUpperCase());
bufw.newLine();
bufw.flush();
}
bufr.close();
bufw.close();
}
}
流操作的基本规律
流对象有很多,不知道该用哪一个,那么该如何选择呢?
通过三个明确来完成。
- 明确源和目的;
源:输入流。InputStream Reader
目的:输出流。OutputStream Writer - 操作的数据是否是纯文本;
是:字符流。
不是:字节流。 - 当体系明确后,在明确要使用哪个具体的对象。
通过设备来进行区分:
源设备:内存,硬盘,键盘
目的设备:内存,硬盘,控制台。
示例:
需求:
将键盘录入的数据保存到一个文件中。
这个需求中有源和目的都存在。
那么分别分析:
源:InputStream Reader
是不是纯文本?是!选择Reader
设备:键盘。对应的对象是System.in.
不是选择Reader吗?System.in对应的不是字节流吗?
为了操作键盘的文本数据方便。转成字符流按照字符串操作是最方便的。
所以既然明确了Reader,那么就将System.in转换成Reader。
用了Reader体系中转换流,InputStreamReader
InputStreamReader isr = new InputStreamReader(System.in);
需要提高效率吗?需要!BufferedReader
BufferedReader bufr = new BufferedReader(isr);
目的:OutputStream Writer
是否是存文本?是!Writer。
设备:硬盘。一个文件。使用 FileWriter。
FileWriter fw = new FileWriter("c.txt");
需要提高效率吗?需要。
BufferedWriter bufw = new BufferedWriter(fw);
**************
扩展一下,想要把录入的数据按照指定的编码表(utf-8),将数据存到文件中。
目的:OutputStream Writer
是否是存文本?是!Writer。
设备:硬盘。一个文件。使用 FileWriter。
但是FileWriter是使用的默认编码表。GBK.
但是存储时,需要加入指定编码表utf-8。而指定的编码表只有转换流可以指定。
所以要使用的对象是OutputStreamWriter。
而该转换流对象要接收一个字节输出流。而且还可以操作的文件的字节输出流。FileOutputStream
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("d.txt"),"UTF-8");
需要高效吗?需要。
BufferedWriter bufw = new BufferedWriter(osw);
所以,记住。转换流什么使用。字符和字节之间的桥梁,通常,涉及到字符编码转换时,
需要用到转换流。
改变标准输入输出设备
import java.io.*;
class Demo
{
public static void main(String[] args) throws IOException
{
System.setIn(new FileInputStream("D:\\Demo.txt")); //原本txt内容:nihao
char firstchar=(char)System.in.read();
System.out.println(firstchar); //n
System.setOut(new PrintStream("D:\\Demo.txt"));
System.out.println("改变了标准输出流"); //txt内容变为:改变了标准输出流
}
}