十五、IO流
1、系统结构图(xmind)
2、IO流继承体系简图
3、tips
——1.字符流Writer
需求:将一些文字存储到硬盘一个文件中。
如果要操作文字数据,建议优先考虑字符流。而且要将数据从内存写到硬盘上,要使用字符流中的输出流:Writer。硬盘的数据基本体现是文件,希望找到一个可以操作文件的Writer:FileWrite。
代码:
import java.io.FileWriter;
import java.io.IOException;
public class FileWriterDemo
{
public static void main(String[] args) throws IOException
{
//创建一个可以往文件中写入字符数据的字符输出流对象
//既然是往一个文件中写入文字数据,那么在创建对象时,就必须明确该文件(用于存储数据的目的地)
//如果文件不存在,则会自动创建
//如果文件存在,则会被覆盖
FileWriter fw = new FileWriter("demo.txt" );
//调用Writer对象中的write(string)方法,写入数据
//其实数据写入到临时存储缓冲区中
fw.write( "abcde");
//进行刷新,将数据直接写入到目的地中
fw.flush();
//关闭流,关闭资源,在关闭前会先调用flush刷新缓冲中的数据到目的地。
fw.close();
}
}
1.close方法只能用一次。
——2.字符流Reader
1.使用read()方法读取文本文件数据。
栗:
import java.io.FileReader;
import java.io.IOException;
public class FileReaderDemo
{
public static void main(String[] args) throws IOException
{
FileReader fr = new FileReader("demo.txt" );
//用Reader中的read方法读取字符
int ch = 0;
while((ch = fr.read()) != -1)
{
System.out.print(( char)ch+" ");
}
fr.close();
}
}
运行结果:
public int read()
throw IOException
读取单个字符。在字符可用、发生I/O错误或者已达到流的末尾前,此方法一直阻塞。用于支持高效的单字符输入的子类应重写此方法。
返回:作为整数读取的字符,范围在0到65535之间(0x00-0xffff),如果已达到流的末尾,则返回-1
抛出:IOException-如果发生I/O错误。
2.使用read(char[])方法读取文本文件数据。
栗:
import java.io.FileReader;
import java.io.IOException;
public class FileReaderDemo2
{
public static void main(String[] args)throws IOException
{
FileReader fr = new FileReader("demo.txt" );
//使用read(char[])读取文本文件数据
//先创建字符数组
char[] buf = new char[3];
int len = 0;
while((len = fr.read(buf)) != -1)
{
System.out.println( new String(buf,0,len));
}
fr.close();
}
}
运行结果:
——3.字符流缓冲区
写入换行使用BufferedWriter类中的newLine()方法。读取一行数据使用BufferedReader类中的readLine()方法。bufr.read():这个read方法是从缓冲区中读取字符数据,所以覆盖了父类中的read方法。bufr.readLine():另外开辟了一个缓冲区,存储的是原缓冲区一行的数据,不包含换行符。原理:使用了读取缓冲区的read方法,将读取到的字符进行缓冲并判断换行标记,将标记前的缓冲数据变成字符串返回。
栗:
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
public class CopyTextBufTest
{
public static void main(String[] args) throws Exception
{
FileReader fr = new FileReader("buf.txt" );
BufferedReader bufr = new BufferedReader(fr);
FileWriter fw = new FileWriter("buf_copy.txt" );
BufferedWriter bufw = new BufferedWriter(fw);
String line = null;
//方式一
while((line = bufr.readLine()) != null)
{
bufw.write(line);
bufw.newLine();
bufw.flush();
}
//方式二
/*
int ch = 0;
while((ch = bufr.read()) != -1)
{
bufw.write(ch);
}
*/
bufr.close();
bufw.close();
}
}
运行结果:
——4.装饰设计模式
1.当想对已有对象进行功能增强时,可定义类:将已有对象传入,基于已有对象的功能,并提供加强功能,那么自定义的该类称之为装饰类。
2.特点:装饰类通常都会通过构造方法接收被装饰的对象,并基于被装饰的对象的功能,提供更强的功能。
3.装饰和继承的区别:
1)装饰模式比继承要灵活。避免了继承体系的臃肿,且降低了类与类之间的关系。
2)装饰类因为增强已有对象,具备的功能和已有的是相同的,只不过提供了更强的功能,所以装饰类和被装饰的类通常都是属于一个体系。
3)从继承结构转为组合结构。
在定义类的时候,不要以继承为主;可通过装饰设计模式进行增强类功能。灵活性较强,当装饰类中的功能不适合,可再使用被装饰类的功能。
——5.字节流
栗1:
import java.io.FileOutputStream;
import java.io.IOException;
public class ByteStreamDemo
{
public static void main(String[] args) throws IOException
{
demo_write();
}
public static void demo_write() throws IOException
{
//1、创建字节输出流对象,用于操作文件
FileOutputStream fos = new FileOutputStream( "bytedemo.txt");
//2、写数据,直接写入到了目的地中
fos.write( "abcdefg".getBytes());
//关闭资源动作要完成
fos.close();
}
}
运行结果:
栗2:
/*
练习:
复制一个图片
思路:
1、用字节读取流对象和图片关联。
2、用字节写入流对象创建一个图片文件。用于存储获取到的图片数据。
3、通过循环读写,完成数据的存储。
4、关闭资源。
*/
import java.io.*;
class CopyPic
{
public static void main(String[] args)
{
//常用方法复制
byteArrayCopy();
//利用输入流的available方法进行复制
availableCopy();
//利用available方法对复制文件大小有限制,慎用
}
//使用available方法进行复制
public static void availableCopy()
{
FileInputStream fis=null;
FileOutputStream fos=null;
try
{
//关联要复制的文件
fis=new FileInputStream("D:/桌面/1.jpg");
//指定复制的路径
fos=new FileOutputStream("D:/桌面/2.jpg");
//利用available方法指定数组长度
byte[] b=new byte[fis.available()];
fis.read(b);//复制关联文件数据
fos.write(b);//粘贴到指定路径
}
catch (IOException e)
{
throw new RuntimeException("图片复制失败");
}
finally
{
try
{
if(fis!=null)
fis.close();//关闭输入字节流
}
catch (IOException e)
{
throw new RuntimeException("读取字节流关闭失败");
}
try
{
if(fos!=null)
fos.close();//关闭输出字节流
}
catch (IOException e)
{
throw new RuntimeException("写入字节流关闭失败");
}
}
}
//使用读数组方式进行复制
public static void byteArrayCopy()
{
FileInputStream fis=null;
FileOutputStream fos=null;
try
{
//关联要复制的文件
fis=new FileInputStream("D:/桌面/1.jpg");
//指定复制的路径
fos=new FileOutputStream("D:/桌面/3.jpg");
//利用数组的读取方式
byte[] b=new byte[1024];
int len=0;
while ((len=fis.read(b))!=-1)//复制文件的全部数据
{
fos.write(b,0,len);//粘贴到指定路径
}
}
catch (IOException e)
{
throw new RuntimeException("图片复制失败");
}
finally
{
try
{
if(fis!=null)
fis.close();//关闭输入字节流
}
catch (IOException e)
{
throw new RuntimeException("读取字节流关闭失败");
}
try
{
if(fos!=null)
fos.close();//关闭输出字节流
}
catch (IOException e)
{
throw new RuntimeException("写入字节流关闭失败");
}
}
}
}
运行结果:
——6.字节流缓冲区
需求:根据字节流缓冲区的原理,自定义一个字节流缓冲区。
注意:
1、字节流的读一个字节的read方法为什么返回值类型不是byte,而是int。因为有可能会读到连续8个二进制1的情况,8个二进制1对应的十进制是-1.那么就会数据还没有读完,就结束的情况。因为我们判断读取结束是通过结尾标记-1来确定的。所以,为了避免这种情况将读到的字节进行int类型的提升。并在保留原字节数据的情况前面了补了24个0,变成了int类型的数值。而在写入数据时,只写该int类型数据的最低8位。
2、byte类型的-1提升为int类型时还是-1。原因:因为在bit8个1前面补的全是1导致的。如果在bit8个1前面补0,即可以保留原字节数据不变,又可以避免-1的出现。这时将byte型数据&0xff即255即可。
栗:
/*
自定义字节流读取缓冲区
思路:
1、定义一个固定长度的数组
2、定义一个指针和计数器用于读取数组长度,和计数数组元素是否取完为0
3、每次将字节数据存入元素要先将数组中的元素取完
*/
import java.io.*;
class MyBufferedInputStream
{
private InputStream in;
private byte[] by=new byte[1024];
private int count=0,pos=0;
MyBufferedInputStream(InputStream in)
{
this.in = in;
}
//自定义读方法,一次读一个字节
public int myRead()throws IOException
{
//通过in对象读取硬盘上数据,并存储by中。
//存储在数组中的数据被读取完,再通过in对象从硬盘上读取数据
if(count == 0)
{
count = in.read(by);
if(count < 0)//文件数据全部被读取出来了
return -1;
pos = 0;//初始化指针
byte b = by[pos];
count--;//每被读一个字节,表示数组中的字节数少一个
pos++;//指针加1
return b&255;//返回的byte类型提升为int类型,字节数增加,且高24位被补1,原字节数据改变。
//通过与上255,主动将byte类型提升为int类型,将高24位补0,原字节数据不变。
//而在输出字节流写入数据时,只写该int类型数据的最低8位。
}
else if(count > 0)//如果数组中的数据没被读取完,则继续读取
{
byte b = by[pos];
count--;
pos++;
return b&0xff;
}
return -1;
}
//自定义关闭资源方法
public void close()throws IOException
{
in.close();
}
}
//测试自定义输入字节流缓冲区
class MyBufferedCopyMp3
{
public static void main(String[] args)
{
long start = System.currentTimeMillis();
//利用字节流的缓冲区进行复制
copy_2();
long end=System.currentTimeMillis();
System.out.println("复制共用时:"+(end-start)+"毫秒");
}
//使用字节流的缓冲区进行复制
public static void copy_2()
{
BufferedOutputStream bout=null;
MyBufferedInputStream bin=null;
try
{
//关联复制文件输入流对象到缓冲区
bin = new MyBufferedInputStream(new FileInputStream("D:/桌面/Sugar.mp3"));
//指定文件粘贴位置的输出流对象到缓冲区
bout = new BufferedOutputStream(new FileOutputStream("D:/桌面/Sugar2.mp3"));
int by=0;
while((by = bin.myRead())!=-1)
{
bout.write(by);//将缓冲区中的数据写入指定文件中
}
}
catch(IOException e)
{
throw new RuntimeException("MP3复制失败");
}
finally
{
try
{
if(bin != null)
bin.close();//关闭输入字节流
}
catch(IOException e)
{
throw new RuntimeException("读取字节流关闭失败");
}
try
{
if(bout != null)
bout.close();//关闭输出字节流
}
catch(IOException e)
{
throw new RuntimeException("写入字节流关闭失败");
}
}
}
}
运行结果:
——7.转换流
1.InputStreamReader将字节流通向字符流
1、获取键盘录入对象。
InputStream in=System.in;
2、将字节流对象转成字符流对象,使用转换流。
InputStreamReaderisr=new InputStreamReader(in);
3、为了提高效率,将字符串进行缓冲区技术高效操作。使用BufferedReader
BufferedReaderbr=new BufferedReader(isr);
//键盘录入最常见写法
BufferedReaderin=new BufferedReader(new InputStreamReader(System.in));
2. OutputStreamWriter字符流通向字节流
字符通向字节:录入的是字符,存到硬盘上的是字节。步骤和InputStreamReader转换流一样。
/*
需求:将键盘录入的数据,显示在控制台,当输入over时,表示结束
源:键盘录入。
目的:控制台。
*/
import java.io.*;
class TransStreamDemo
{
public static void main(String[] args)throws IOException
{
//获取键盘录入对象。
//InputStream in=System.in;
//将字节流对象转成字符流对象,使用转换流。
//InputStreamReader isr=new InputStreamReader(in);
//为了提高效率,将字符串进行缓冲区技术高效操作。使用BufferedReader
//BufferedReader br=new BufferedReader(isr);
//键盘录入最常见写法
BufferedReader in=new BufferedReader(new InputStreamReader(System.in));
//字符流通向字节流
BufferedWriter bw =new BufferedWriter(new OutputStreamWriter(System.out));
String s=null;
while((s=in.readLine())!=null)
{
if("over".equals(s))
break;
bw.write(s.toUpperCase());//写入数据
bw.newLine();//换行
bw.flush();//刷新
}
bw.close();//关闭流资源
in.close();
}
}
运行结果:
——8.Properties集合
Map
|--Hashtable
|--Properties
特点:
1. 该集合中的键和值都是字符串类型。
2. 集合中的数据可以保存到流中,或者从流中获取。
3. 通常该集合用于操作以键值对形式存在的配置文件。
栗:
import java.util.Properties;
import java.util.Set;
public class PropertiesDemo
{
public static void main(String[] args)
{
propertiesDemo();
}
public static void propertiesDemo()
{
//创建一个Properties集合
Properties prop = new Properties();
//存储元素
prop.setProperty( "zhangsan","10" );
prop.setProperty( "lisi","20" );
prop.setProperty( "wangwu","30" );
prop.setProperty( "zhaoliu","40" );
//修改元素
prop.setProperty( "wangwu","26" );
//取出所有元素
Set<String> names = prop.stringPropertyNames();
for(String name : names)
{
String value = prop.getProperty(name);
System.out.println(name + ":" + value);
}
}
}
运行结果:
——9.实例
/*
需求:获取指定目录下,指定扩展名的文件(包含子目录中的),并且将这些文件的绝对路径写入到
一个文本文件中。
简单说:就是建立一个指定扩展名的文件的列表。
思路:
1. 必须进行深度遍历。
2. 要在遍历的过程中进行过滤,将符合条件的内容都存储到容器中。
3. 对容器中的内容进行遍历并将绝对路径写入到文件中。
*/
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class Test
{
public static void main(String[] args) throws IOException
{
File dir = new File("E:/Java/day21" );
FilenameFilter filter = new FilenameFilter()
{
public boolean accept(File dir,String name)
{
return name.endsWith(".java" );
}
};
List<File> list = new ArrayList<File>();
getFiles(dir,filter,list);
File destFile = new File(dir,"javalist.txt" );
write2File(list,destFile);
}
/*
对指定目录中的内容进行深度遍历,并按照指定过滤器,进行过滤。
将过滤后的内容存储到指定容器List中。
*/
public static void getFiles(File dir,FilenameFilter filter,List<File>list)
{
File[] files = dir.listFiles();
for(File file : files)
{
//递归
if(file.isDirectory())
{
getFiles(file,filter,list);
}
else
{
//对便利到的文件进行过滤器的过滤。将符合条件File对象,存储到List集合中
if(filter.accept(dir,file.getName()))
{
list.add(file);
}
}
}
}
public static void write2File(List<File> list,File destFile) throws IOException
{
BufferedWriter bufw = null;
try
{
bufw = new BufferedWriter(new FileWriter(destFile));
for(File file : list)
{
bufw.write(file.getAbsolutePath());
bufw.newLine();
bufw.flush();
}
}
finally
{
if(bufw!=null)
try
{
bufw.close();
}
catch(IOException e)
{
throw new RuntimeException("关闭失败");
}
}
}
}
运行结果:
——10.小结
流的基本应用小结:
•流是用来处理数据的。
•处理数据时,一定要先明确数据源,与数据目的地(数据汇)。
•数据源可以是文件,可以是键盘。
•数据目的地可以是文件、显示器或者其他设备。
•而流只是在帮助数据进行传输,并对传输的数据进行处理,比如过滤处理.转换处理等。