IO流的做用:用来处理设备之间的数据传输。
流的分类:
流按操作数据分为两种:字节流与字符流。
按流分向分为:输入流,输出流。
字节流的由来:
因为后期编码表的不断出现,识别某一文字的码表不唯一,比如中文,GBK,unicode都可以识别,就出现了编码问题。如:中文字节数据gbk -à 流处理unicode 来处理 -à数据错误,所以就有了字符流的出现。(虽然字节流也能处理问题,但比较麻烦)
字符流其实就是:字节流+编码表的一个封装体。
Io流常用的基类:
字节流的基类:
InputStream ,OutputStream
字符流的基类:
Reader , Writer
学习io流体系:看顶层(父类的共性功能),用底层(底层的具体对象)。
该体系的一个好处是:每个子类的后缀名都是所属体系的父类的名称,很容易区分所属体系,而前缀是流的功能体现。
需求:将一段文字数据写到硬盘上。
FileWriter中没有方法,方法都是继承父类Writer的。
Writer中的方法:
write(char[] buf):写入字符数组。
Write(int c):写入单个字符。
Write(String c):写入字符串。
Write(String str , int off ,int len):写入字符串的某一部分。
Write(char[] buf ,int off ,int len):写入字符数组的某一部分。
Flush():刷新流。
Close():关闭流。
Reader中的方法:
Int Read():一次读取一个字符,返回作为整数读取的字符,如果读到末尾则返回-1。
Int Read(char[] buf):将字符读入数组,,并返回读取到的字符个数。
Int Read(char[] buf ,int off, int len):将字符读入数组的某一部分。
Close():关闭流。
注意read()方法是一次读取一个字符进内存,read(char[] buf)方法一次读取一堆数据进字符数组中,所以使用read(char[] buf)方法来读取比较高效。
字节流读取代码演示:
需求:将c盘下的文本文件复制到d盘中。
复制方式一:
public class CopyTest {
public static void main(String[] args) throws IOException {
//1、创建字符读取流对象和源相关联。
FileReader fr = new FileReader("c:\\demo.text");
//2、创建字符输出流对象,明确要存储数据的目的。
FileWriter fw= new FileWriter("copy_demo.txt");
//3、进行读写操作,读一个,就写一个。
int ch = 0;
while((ch=fr.read())!=-1){
fw.write(ch);
}
//4、关闭资源。
fw.close();
fr.close();
}
}
复制方式二:使用缓冲区数组,使用的就是可以操作数组的读写方法。
public class CopyTest2 {
public static void main(String[] args) throws IOException {
//1、创建字符输入流和字符输出流。
FileReader fr = fr = new FileReader("demo.txt");
FileWriter fw = fw = new FileWriter("copy_demo2.txt");
//3,定义一个数组缓冲区。用于缓冲读取到的数据。
char[] buf = new char[1024];
//4,读写操作。
int len = 0;
while((len = fr.read(buf))!=-1){
fw.write(buf,0,len);
}
fw.close();
fr.close();
}
}
IO中的异常处理规范示例:
public class FileWriterDemo2 {
public static void main(String[] args){
//为了close()方法的引用有效。
FileWriter fw = null;
try {
fw = new FileWriter("d:\\demo.txt");
fw.write("abcde");
fw.flush();
} catch (IOException e) {
System.out.println(e.toString());
}finally{
// if(fw!=null) //这里一定要判断下流是否创建成功,不然出现两个异常提示。
//这里也要try,因为close()方法也会抛异常。
try{
fw.close();
}catch(IOException e){
//相关的代码处理,比如说,将关闭失败的异常信息记录到日志文件中。
throw new RuntimeException("关闭失败");
}
}
}
}
Flush()方法和close()方法的区别?
Flush:仅仅是将缓冲中的数据刷新到目的,流可以继续使用,可以刷新多次。
Close:将缓冲区中的数据刷新到目的,流关闭,流不能继续运行,只能使用一次。
字符流的缓冲区:增强高效读写。
BufferedReader
BufferedWriter
缓冲区给流的操作动作(读写)提高效率,所以缓冲区的对象建立必须要有流对象。同时在构造时可以指定缓冲区大小,或者使用默认大小,一般默认就足够。
缓冲区中的read()方法和流中的read()的区别?
缓冲区中的read()方法的读取原理:是使用Reader中的read(char[] buf)方法读取一批数据到缓冲数组中,然后再使用read()方法从缓冲区中一次取一个,而且每次都是从缓冲区中取,所以效率比较高。
readLine():一次读取一行。
readLine()方法可以读取一行的原理,使用buf.read()方法从缓冲区中取出字符存储到readLine()方法的容器中(也就是容器中还有容器),当取出的字符是回车符时,就将存储的数据作为字符串返回,返回的字符串中是不带回车符的。
装饰设计模式:
解决问题:给已有的对象提供增强额外功能,还不用对原有对象进行修改。
这种设计方式比继承更灵活,避免了继承的臃肿,IO中频繁的用到了装饰设置模式。
装饰类和被装饰类都属于同一个体系。
装饰类往往会提供构造方法用于接收被装饰对象,比如:BufferedReader(Reader in);
装饰类LineNumberReader:增强的是行号功能,提供了设置行号和获取行号功能。
setLineNumber(int lineNumber):设置当前的行号。
getLineNumber():获取当前的行号。
装饰设计模式代码演示:
//被装饰类。
class Person{
public void eat(){
System.out.println("吃饭");
}
}
//装饰类。
class SuperPerson {
private Person p;
//提供构造方法用于接收被装饰的类。
SuperPerson(Person p){
this.p = p;
}
public void eat(){
p.eat(); //调用原有功能。
System.out.println("开胃酒");//增加功能。
System.out.println("甜品");//增加功能。
}
}
字节流:
字符流和字节流的操作方式基本上一样,只是字节流操作的单位是字节,字符流操作的单位是字节。
字节流的读取方法:
Read():一次读取一个字节,返回下一个数据字节,如果没有返回-1。
Read(byte[] b):一次读取一个字节数组,返回读入缓冲区的字节总数,没有则返回-1。
字节流的写方法:
Write(int b):一次写入一个字节。
Write(byte[] b):一次写入一个字节数组的数据。
字节输出流为什么不需要用flash()方法刷新?
字符流的写入会先将这些数据进行临时存储,并查指定的编码表,再按照指定的编码表中的内容写入到目的地中。例如:"你好"FileWriter-->编码表GBK-->数字字节->目的地。而字节流处理的数据不一定都是文字数据,所以不需要指定查表的。直接在操作文件数据时,就将具体的字节数据写入到了目的地。
字符流可以复制图片吗?
不能,就算复制,复制完的图片就跟原本的图片不一样了,因为字符流操作的都是字符数据,而且字符流操作的数据都会多做了一步动作,都会去查相应的表,所以字符流只能操作纯文本的数据。
转换流:
字节流--à字符流的桥梁。InputStreamReader
字符流--à字节流的桥梁。OutputStreamWriter
转换流使用代码:
需求:读取键盘录入的数据,将这些数据转成大写打印在屏幕上,如果录入的是over,程序结束。
分析为什么要使用转换流:
分析发现如果使用readLine方法的方式来读取,会更容易,但是readLine()读取一行的方式,是字符流BufferedReader对象中过的方法,而System.in键盘录入是字节流。想要readLine中的方法,可以使用转换流将字节流转换成字符流,这样就可以使用readLine中的方法了。
public class TransStreamDemo {
public static void main(String[] args) throws IOException {
//使用转换流将System.in转换成字符流,再和BufferedReader,就可以使用readLine方法读取一行。
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(newFileOutputStream("tempfile\\out.txt")));
String line = null;
//readLine()方法一次读取一行。
while((line=bufr.readLine())!=null){
if("over".equals(line))
break;
// System.out.println(line.toUpperCase());
bufw.write(line.toUpperCase());
bufw.newLine();
bufw.flush();
}
//可以不用关流。
// osw.close();
}
}
File类:
专门用于描述系统中文件或文件夹的对象,可以用于操作文件或文件夹的属性信息。
学习File类具体方法使用,还是按照面向对象的原则:
分析如果将一个文件或文件夹封装成对象,那么这个对象具备什么功能方便对文件或文件夹的操作呢?
File类中的成员:
1、构造函数。
File(File parent,String child);
File(String parent,String child);根据父parent和child子路径名创建File实例。
File(String pathname);通过将制定字符串路径名称创建File实例。
2、字段:
分隔符:
路径名称分隔符 \\ / static String separator;
路径分隔符 ;
3、方法:
获取信息:
String name = file.getName();获取名称。
String absPath = file.getAbsolutePath();获取绝对路径。
String path = file.getPath();获取路径。
String dir = file.getParent();获取父目录。
long len = file.length();获取字节大小。
long len2 = file.getFreeSpace();获取指定盘的总容量。
long time = file.lastModified();获取文件最新修改的时间。
获取时间:1、将毫秒值转成Date时间对象。2、对Date对象进行格式化。
判断:is开头的方法。
boolean isFile();是否是文件。
boolean isDirectory();是否是文件夹。
要判断是文件还是目录的前提,必须要存在,所以要用exists()判断文件是否存在。
创建:
boolean createNewFile();创建文件,如果文件不存在就创建,如果存在就不创建。
boolean mkdir();创建一个文件夹。
boolean mkdirs();创建多级目录。删除的
删除:
boolean delete();删除就为true。
boolean exists();判断文件是否存在。
boolean renameTo(File dest);重命名路文件,有剪切的作用。
static File[] listRoots();获取系统中有效的盘符存进数组。需要遍历数组。
String[] list();将指定目录下的文件或文件夹的名称存储到字符数组中,包含隐藏文件。
String[] list(FilenameFilter filter);返回经过过滤器过滤的文件或文件夹名。
FilenameFilter接口,过滤器。
boolean accept(File dir,String name);参数为文件路径和要过滤的文件名。
如果名称包含在指定列表中则返回true,否则返回false。
boolean endsWith(".java");后缀名是.java的就返回真。
File[] listFiles();将路径下的所有文件和文件夹封装成对象,存到数组中。
过滤器的原理:
1、首先获取到该目录所有内容,用list();
2、对这写内容进行遍历。
在遍历中加入条件,条件:accept(dir,name);
3、凡是符合条件,accept方法返回true,就将这些符合条件的进行存储。
4、将存储的内容返回,这样就得到了过滤后的内容。
递归:
递归就是函数自身调用自身(直接或间接)。
递归使用注意:
1、 一定要定义条件,否则会发生StackOverflowError异常。
2、 一定要注意递归的次数。
什么时候使用递归?
当一个功能被复用,每次这个功能需要的参数都是根据上一个功能的得出的。
下面代码可以可以分析递归的使用:
public class DiGuiDemo {
public static void main(String[] args) {
show(3);
show2(3);
int sum = getSum(5);
System.out.println("sum="+sum);
}
//求和运算。分析结果。
public static int getSum(int num){
if(num==1)
return 1;
return num+getSum(num-1);
}
//分析结果。
public static void show(int num){
if(num==0)
return ;
show(num-1);
System.out.println("num="+num);//打印的结果是1,2,3,为什么?
}
//分析结果。
public static void show2(int num){
if(num==0)
return ;
System.out.println("num="+num);//打印的结果是3,2,1为什么?
show(num-1);
}
public static void method(){
// method();
}
}
练习:获取指定目录中的所有内容(包含子目录中的内容)。用递归方式。
public class FileTest {
public static void main(String[] args) {
File dir = new File("e:\\");
showDir(dir,0);
}
//递归。
public static void showDir(File dir,int count){
System.out.println(getSpace(count)+dir.getName());
count++;
File[] files = dir.listFiles();
// if(files!=null)//健壮性判断。
for(File f : files){
if(f.isDirectory()){
showDir(f,count);
}
else
System.out.println(getSpace(count)+f.getName());
}
}
//定义一个,用来获取目录缩进的量。
private static String getSpace(int count) {
StringBuilder sb = new StringBuilder();
for(int x=0; x<count; x++){
sb.append("|--");
}
return sb.toString();
}
}
练习:删除一个带内容的文件夹。(递归)
思路:
1,删除一个带内容的目录,必须按照window的规则,从里往外删。
2,要删除最里面,如果做到的呢?可以使用递归。
public class FileTest2 {
public static void main(String[] args) {
File dir = new File("e:\\demodir");
removeDir(dir);
}
public static void removeDir(File dir){
File[] files = dir.listFiles();
for(File file : files){
if(file.isDirectory()){
removeDir(file);
}else{
System.out.println(file+":"+file.delete());
}
}
System.out.println(dir+":"+dir.delete());
}
}
Properties:该类表示了一个持久的属性集。
1、 Properties是Map接口中Hashtable的子类。
2、 该类上没有定义泛型,因为它的键值都是固定的字符串类型。
3、 因为存储的都是字符串数据,通常都作为属性信息存在。
4、 该集合最大的特点就是可以和IO技术相结合。也就是说该集合中的数据可以来自流,也可以将集合中的数据写入流中。
Properties类的基本方法:
setProperty(String key,String value):调用Hashtable的put方法,为集合添加键和值。
Set<String> StringPropertyNames():反回此属性列表中的键集。
getProperty(String key):通过键取值。
Properties方法中和流相关联的方法:
List(PrintStream):将集合中的数据打印到控制台上,一般用于程序调试。
Load(Reader):将流中的数据存储到集合中。
Store(Writer writer ,String comments):将集合中的数据写入到输出流中关联的目的。
想要对硬盘中的文本数据进行修改,只能使用load方法将数据读取到集合中,然后,对集合数据通过相同键设值,然后再用store()方法将集合中修改过的数据写入文本中。
Properties集合的应用:可以用于简单配置文件信息。
标准化配置信息一般用xml的方式来完成。
练习:定义一个功能用于记录住软件运行的次数,如果运行次数大于5次。不要在运行并给出提示:试用次数已到,请注册!给钱!
思路:
1、计数器。而且这个计数器必须软件运行结束后,持久化存储。
2、每次软件启动都要读取这个记录了计数器的数据的文件。并将计数器的值取出自增后,在重新存储。
3、数值需要名称标记,就有了键值对。这就是map集合,还要操作硬盘设备上的数据,就使用到了IO流。map和io结合的对象正好有Properties.
代码演示:
public class PropertiesTest {
public static void main(String[] args) throws IOException {
if(countDemo()){
//运行程序代码。
System.out.println("运行程序");
}else{
System.out.println("试用次数已到,请注册!给钱!");
}
}
public static boolean countDemo() throws IOException{
Properties prop = new Properties();
int count = 0;
//配置文件。
File confile = new File("config.txt");
if(!confile.exists())
confile.createNewFile();
FileInputStream fis = new FileInputStream(confile);
//将流中的数据加载到prop中。
prop.load(fis);
//获取配置文件中的次数。
String value = prop.getProperty("count");
if(value!=null){
count = Integer.parseInt(value);
if(count>=5){
// System.out.println("试用次数已到,请注册!给钱!");
return false;
}
}
count++;
System.out.println("运行"+count+"次");
//将具体的键和次数存储到集合中。
prop.setProperty("count", String.valueOf(count));//count+"";
//将集合中的数据写入到文件中持久化。
FileOutputStream fos = new FileOutputStream(confile);
prop.store(fos, "");
fos.close();
fis.close();
return true;
}
}
IO中的其他功能流:
打印流:
PrintWriter与PrintStream。
特点
1、 打印流为其他输出流添加了功能,使他们能够方便的打印各种数据值表示形式。
2、 提供了一系列的打印功能,可以打印任何数据。
3、 它的特有方法不抛出异常。(打印方法)
构造方法:该流是一个处理目的的流对象。
目的设备:
1、 File对象。
2、 字符串路径。
3、 字节输出流。
打印流PrintStream中的write和print方法的区别?
Write():一次只写一个字节。Read()方法一次读取一个字节,读取到内存中提升为整数4个字节,所以write()写方法,只会将参数的最后一个字节写入目的。
Print方法,可以将参数的数据表现形式打印到目的中,原理是print方法内部将传入参数转成字符串,再write到目的,所以可以保证数据的原有表现形式。(write(String.valueOf(i))。
PrintWriter字符打印流:
PrintStream打印的所有字符都使用平台默认的字符编码转换为字节,在需要写入字符而不是写入字节的情况下,应该使用PrintWriter类。所以PrintWriter类使用的频率比较高。
构造函数:接收的参数跟字节打印流的参数差不多,只是多了个字符输出流。
目的设备:
1、 字符输出流。
还能指定编码表,一般不用PritWriter定义的编码表,因为要操作的编码表有专用的对象,转换流,转换流,不仅能设置写的编码表,还可以设置读的编码表。
自动刷新:
打印流写的方法都会先进缓冲区中。想要自动刷新,在构造时将autoFlush设为true,则println、printf或format方法将刷新输出缓冲区。
SequenceInputStream序列流:可以将多个流进行逻辑串联(进行合并,变成一个流,操作起来更方便,因为将多个员变成了一个源)。
构造方法:
SequenceInputStream(Eunmeration <? Extends InputStream >e):接收一个枚举。
SequenceInputStream(InputStream s1 ,InputStream s2):接收两个流。
操作流的四个明确:
1,明确源和目的。
源:InputStream Reader 一定是被读取的。
目的:OutputStream Writer 一定是被写入的。
2,处理的数据是否是纯文本的数据?
是:使用字符流。Reader Writer
否:使用字节流。 InputStream OutputStream
如果是源并且是纯文本,Reader
如果是目的并且是纯文本,Writer
到这里,两个明确确定完,就可以确定出要使用哪个体系。
接下来,就应该明确具体这个体系要使用哪个具体的对象。
3,明确数据所在的设备:
源设备:
键盘(System.in)
硬盘(FileXXX)FileReader FileInputStream
内存(数组)ByteArrayInputStream CharArrayReader StringReader
网络(Socket)
目的设备:
显示器(控制台System.out)
硬盘(FileXXX)FileWriter FileOutputStream
内存(数组)ByteArrayOutputStream CharArrayWriter StringWriter
网络(Socket)
4,明确是否需要额外功能?
1,是否需要高效?缓冲区Buffered 四个。
2,是否需要转换?转换流 InputStreamReader OutputStreamWriter
3,是否操作基本数据类型? DataInputStream DataOutputStream
4,是否操作对象(对象序列化)? ObjectInputStream ObjectOutputStream
5,需要对多个源合并吗? SequenceInputStream
6,需要保证数据的表现形式到目的地吗? PrintWriter