您的位置:首页 > 职场人生

黑马程序员--IO总结

2016-03-22 13:07 465 查看

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

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  IO