第一部分 IO流概括
1、IO流是用来处理设备之间的数据传输,Java通过数据的操作是通过流的方式。用于操作流的对象在IO包中。
注:输入流和输出流是相对于内存设备而言。
2、区分输入和输出:
输入:将外设的数据读入到内存中
输出:将内存的数据读入到外设设备中。
3、分类:
流按照操作数据可分为:
a、字节流
字节流的抽象基类:InputStream、OutputStream
b、字符流
字符流的抽象基类:Reader、Writer
由这四个类派生出来的子类都是以其父类名作为子类名的后缀。
注:字符流的由来--字节流读取文字字节数据后,不直接操作而是先查指定编码表,获取相应的文字后再对这个文字进行操作。
第二部分:FileWriter--文本文件字符写入流
1、FileWriter是适用于操作文件的字符流
2、需求:将一些文字存储到一个文件中
代码示例:
class
{
public static void main(String[] args) throws IOException
{
//FileWrtier创建流对象
//创建一个FileWriter对象。该对象一被初始化,就必须要明确被操作的文件
//而且该文件会被创建到指定目录下。如果该目录下已有同名文件,将被覆盖
//其实该步就是在明确数据要存在目的地
FileWriter fw = new FileWriter("E:\\黑马文件\\demo.txt");
//调用write方法,将字符串写入到流中
fw.write("abcds");
<pre name="code" class="java">
//刷新流对象的中缓冲的数据,将数据刷入到目的地中//fw.flush();//关闭流资源,关闭之前会刷新一次内部缓冲中的数据。//将数据刷到目的地中//和flush的区别在于,flush刷新后,流可以继续使用;close刷新后,会将流关闭fw.close();//最终一定要关闭资源。}}
总结:
a、调用wirte方法后要调用flush刷新数据才能将数据写入。
b、close方法只能用一次,并且调用了以后不能进行写入,即不能调用writer方法。
上面的代码有一个弊端,就是即使存在已有文件,也会将其覆盖。所以为了保证不覆盖并且能在文件的末尾出续写,可以往FileWriter中传入参数true
代码示例:
FileWriter fw = null;
fw = new FileWriter("demo.txt",true);//关键参数true
fw.write("haha\r\nnihao");//在windows中\r\n表示换行
第三部分:FileReader--文本文件字符读取流
1、字符流都有编码,FileReader使用的是系统默认编码。
2、文本文件的读取方式有两种:字符字节读取和字符数组读取。
字符字节读取示例代码:
class
{
public static void main(String[] args)
{
//创建一个文件读取流对象,和指定名称文件相关联
//要保证该文件已经存在,如果不存在,会发生FileNoFoundException异常
FileReader fr = new FileReader("demo.txt");
//调用读取流对象的read方法
//read():一次读一个字符,而且会自动往下读,读到-1时候停止读取
int ch = 0;
while(ch=fr.read()!=-1)
{
System.out.println((char)ch);//这里需要强制转换,因为read方法返回的是一个int类型
}
fr.close();
}
}
字符字节读取的方式较为麻烦,所以今后更常用的是使用字符数组进行读取
代码示例:
<span style="white-space:pre"> </span>char[] buf = new char[1024];
int num = 0;
while((num=fr.read(buf))!=-1)
{
System.out.print(num+":"+new String(buf,0,num));//这里用的是String的方法
}
通过fr.read(buf)的调用实现将读取的文本数据写入到字符数组buf中
总结:以上代码中FileWriter类中的方法read()会返回读取的字符数,并且会自动往下读,直到返回-1停止。
3、练习:
需求:从C盘将一个txt文本文件复制到D盘中。
复制的原理:其实就是将C盘下的数据存储到D盘的一个文件中
步骤:
a、在D盘创建一个文件,用于存储C盘文件中的数据
b、定义读取流和C盘文件关联
c、通过不断的读写完成数据存储
d、关闭资源
步骤:
a、在D盘创建一个文件,用于存储C盘文件中的数据
b、定义读取流和C盘文件关联
c、通过不断的读写完成数据存储
d、关闭资源
代码示例:
public static void copy_2()
{
FileWriter fw = null;
FileReader fr = null;
try
{
fw = new FileWriter("D:\\new.txt");//新文件
fr = new FileReader("C:\\test.txt");//旧文件
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
{
//这里必须分开try
if(fr!=null)
try
{
fr.close();
}
catch (IOException e)
{
throw new RuntimeException("文件字符读取流关闭失败");
}
if(fw!=null)
try
{
fw.close();
}
catch (IOException e)
{
throw new RuntimeException("文件字符写入流关闭失败");
}
}
}
注:调用write方法时,从0开始写,知道读取的字符数len,不是len-1。第四部分:BufferedWriter--字符写入缓冲流
1、带缓冲区的字符流是为了提高数据的读写效率,提高流的操作效率。
2、注意的一点:在创建缓冲区之前,必须要先有流对象!缓冲区要结合流才能用,实质是在流的基础上对流的功能进行了增强。
3、该缓冲区提供了一个跨平台的换行方法:newLine()。
4、使用代码示例:
class
{
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("avads"+x);
bufw.newLine();//跨平台的换行方法
//记住,只要用到缓冲区,就要记得刷新
bufw.flush();
}
//其实关闭缓冲区,就是在关闭缓冲区中的流对象。因此fw.close()不用写
bufw.close();
}
}
第五部分:BufferedReader--字符读取缓冲流
1、和BufferedWriter一样,要使用缓冲区,那事先必须已经创建好流对象。
2、和FileWriter的read一次读一个字符的方法不同,BufferedReader提供了readLine方法,一次读一行文本数据,方便了数据的获取。当返回null时,表示读到了文件末尾。
3、注:readLine方法读取到的内容中只包括每行数据的回车换行符之前的内容。因此如果同时调用BufferedWriter的write方法时,要同时调用newLine方法,才能实现换行。
4、代码示例:
需求:使用缓冲区完成文件的复制。
class
{
public static void main(String[] args)
{
BufferedReader bufr = null;
BufferedWriter bufw = null;
try
{
bufr = new BufferedReader(new FileReader("D:\\old.txt"));
bufr = new BufferedWriter(new FileWriter("E:\\new.txt"));
String line = null;
while((line = bufr.readLine())!=null)
{
bufw.write(line);
//readLine方法不返回换行符,所以需要手动换行
bufw.newLine();
bufw.flush();
}
}
catch (IOException e)
{
throw new RuntimeException("读写失败");
}
finally
{
try
{
if(bufr!=null)
bufr.close();
}
catch (IOException e)
{
throw new RuntimeException("读取关闭失败")
}
try
{
if(bufw!=null)
bufw.close();
}
catch (IOException e)
{
throw new RuntimeException("写入关闭失败")
}
}
}
}
第六部分:装饰设计模式
1、装饰设计模式:当想要对已有的对象进行功能增强时,可以定义新的类然后将原有的对象传入,基于已有的对象并提供加强的功能。这个自定义的类称为装饰类。
2、装饰类通常会通过,构造方法接收被装饰的对象。
3、装饰和继承的区别:
原先使用继承的例子:
MyReader 专门用于读取数据的类
|----MyTextReader
|----|----MyBufferedTextReader
|----MyTextReader
|----|----MyBufferedTextReader
|----MyMediaReader
|----|----MyBufferedMediaReader
|----MyDataReader
|----|----MyBufferedDataReader
|----|----MyBufferedMediaReader
|----MyDataReader
|----|----MyBufferedDataReader
这时候如果针对BufferedReader提出了新的解决方案:
class MyBufferedReader
{
MyBufferedReader(MyTextReader text)
{}
....
}
这样的结果是扩展性很差。
如果找到其参数的共同类型,通过多态的形式,可以提高扩展性。优化后的代码为:
class MyBufferedReader extends MyReader
{
private MyReader r;
MyBufferedReader(MyReader r)
{}
....
}
优化后的体系为:
MyReader
|----MyTextReader
|----MyMediaReader
|----MyDataReader
|----MuBufferedReader
|----MyTextReader
|----MyMediaReader
|----MyDataReader
|----MuBufferedReader
总结:
a、装饰模式比继承模式要灵活,避免了继承体系的臃肿。而且降低了类与类之间的关系。
b、装饰类因为增强已有对象,具备的功能和已有的是相同的,只不过提供了更强的功能。所以装饰类与被装饰类通常都属于一个体系中(如例子中MyBufferedReader是装饰类和MyReader是被装饰类)。
4、需求:自定义装饰类,提供FileReader类中比read方法更强大的myReadLine方法。
class MyBufferedReader extends Reader//首先继承Reader类
{
private FileReader r;//FileReader是被装饰类
MyBufferedReader(FileReader r)
{
this.r = r;
}
//可以一次读一行的方法
public String myReadLine() throws IOException
{
//定义一个临时容器,原BufferedReader封装的是一个字符数组
//为了演示方便,定义一个StringBuilder容器。因为最终还是要将数据变成字符串
StringBuilder sb = new StringBuilder();
int ch = 0;
while((ch = r.read())!=-1)
{
if(ch=='\r')
continue;
if(ch=='\n')
return sb.toString();
else
sb.append((char)ch);
}
//文本数据的最后一行有可能会读取不到最后一行,因此防止读取不到换行符时候读取不到数据防止读取不到换行符时候读取不到数据
if(sb.length()!=0)
return sb.toString();
return null;
}
public void myClose() throws IOException
{
r.close();
}
//覆盖Reader类中的抽象方法
public void close() throws IOException
{
r.close();
}
public int read(char[] cbuf,int off, int len)
{
return r.read(cbuf,off,len);
}
}
第七部分:装饰类LineNumberReader的应用
class
{
public static void main(String[] args)
{
FileReader fr = new FileReader("buf.txt");
LineNumberReader lnr = new LineNumberReader(fr);
String line = null;
//起始行号设为100,默认是0
lnr.setLineNumber(100);
while((line=lnr.readLine())!=null)
{
System.out.println(lnr.getLineNumber()+":"+line);
}
lnr.close();
}
}
第八部分:File字节流的读写操作
1、使用字节流进行文件的读取时有两个类
读操作:FileInputStream
写操作:FileOutputStream
2、使用FileInputStream读取File文件的方式
a、方式一:使用read方法,每次读一个字节
FileInputStream fis = new FileInputStream("fos.txt");
int ch = 0;
while((ch=fis.read())!=-1)
{
System.out.println((char)ch);
}
fis.close();
b、方式二:使用基于字符数组的read(char[] buf)方法。
FileInputStream fis = new FileInputStream("fos.txt");
byte[] buf = new byte[1024];
int len = 0;
while((len=fis.read(buf))!=-1)
{
System.out.println(new String(buf,0,len);
}
fis.close();
c、字节流的读取的特有方式:使用FileInputStream的available方法,返回的是一共需要写入的字节数。
FileInputStream fis = new FileInputStream("fos.txt");
//定义一个刚刚好的缓冲区,不用循环了
byte[] buf = new byte[fis.available()];
fis.read(buf);
System.out.println(new String(buf));
fis.close();
注:如果读取的文件太大,比如达到几个G,超出了物理内存,这样的话就会复制失败,要慎用。3、FileOutputStream的write方法写入文件
和FileWriter的write方法不同,这里的不需要刷新动作!
代码示例:
代码示例:
FileOutputStream fos = new FileOutputStream("fos.txt");
//不需要进行刷新动作
fos.write("adfad".getBytes());
fos.close();