JAVA学习笔记(第八章 文件与I/O流)

一、文件对象

1.创建文件对象

public class TestFile {  
    public static void main(String[] args) {
        // 绝对路径
        File f1 = new File("d:/LOLFolder");
        System.out.println("f1的绝对路径:" + f1.getAbsolutePath());

        // 相对路径,相对于工作目录,如果在eclipse中,就是项目目录
        File f2 = new File("LOL.exe");
        System.out.println("f2的绝对路径:" + f2.getAbsolutePath());
  
        // 把f1作为父目录创建文件对象
        File f3 = new File(f1, "LOL.exe");  
        System.out.println("f3的绝对路径:" + f3.getAbsolutePath());
    }
}

2.文件常用方法

public class TestFile {  
    public static void main(String[] args) {  
        File f = new File("d:/LOLFolder/LOL.exe");
        System.out.println("当前文件是:" +f);
        //文件是否存在
        System.out.println("判断是否存在:"+f.exists());         
        //是否是文件夹
        System.out.println("判断是否是文件夹:"+f.isDirectory());          
        //是否是文件(非文件夹)
        System.out.println("判断是否是文件:"+f.isFile());         
        //文件长度
        System.out.println("获取文件的长度:"+f.length());          
        //文件最后修改时间
        long time = f.lastModified();
        Date d = new Date(time);
        System.out.println("获取文件的最后修改时间:"+d);
        //设置文件修改时间为1970.1.1 08:00:00
        f.setLastModified(0);         
        //文件重命名
        File f2 =new File("d:/LOLFolder/DOTA.exe");
        f.renameTo(f2);
        System.out.println("把LOL.exe改名成了DOTA.exe");         
        System.out.println("注意: 需要在D:\\LOLFolder确实存在一个LOL.exe,\r\n才可以看到对应的文件长度、修改时间等信息");
    }
}
public class TestFile {  
    public static void main(String[] args) throws IOException {  
        File f = new File("d:/LOLFolder/skin/garen.ski");  
        // 以字符串数组的形式,返回当前文件夹下的所有文件(不包含子文件及子文件夹)
        f.list();  
        // 以文件数组的形式,返回当前文件夹下的所有文件(不包含子文件及子文件夹)
        File[]fs= f.listFiles();  
        // 以字符串形式返回获取所在文件夹
        f.getParent(); 
        // 以文件形式返回获取所在文件夹
        f.getParentFile();
        // 创建文件夹,如果父文件夹skin不存在,创建就无效
        f.mkdir();  
        // 创建文件夹,如果父文件夹skin不存在,就会创建父文件夹
        f.mkdirs();  
        // 创建一个空文件,如果父文件夹skin不存在,就会抛出异常
        f.createNewFile();
        // 所以创建一个空文件之前,通常都会创建父目录
        f.getParentFile().mkdirs(); 
        // 列出所有的盘符c: d: e: 等等
        f.listRoots();  
        // 刪除文件
        f.delete();  
        // JVM结束的时候,刪除文件,常用于临时文件的删除
        f.deleteOnExit();  
    }
}

二、什么是流

        什么是流(Stream),流就是一系列的数据。

        当不同的介质之间有数据交互的时候,JAVA就使用流来实现。
        数据源可以是文件,还可以是数据库,网络甚至是其他的程序

        比如读取文件的数据到程序中,站在程序的角度来看,就叫做输入流
        输入流: InputStream
        输出流:OutputStream

1.字节流

        说到字节肯定离不开ASCII码,ASCII码表如下所示。

(1) 以字节流的形式读取文件内容

        InputStream是字节输入流,同时也是抽象类,只提供方法声明,不提供方法的具体实现。
        FileInputStream 是InputStream子类,以FileInputStream 为例进行文件读取。

public class TestStream {  
    public static void main(String[] args) {
        try {
            //准备文件lol.txt其中的内容是AB,对应的ASCII分别是65 66
            File f =new File("d:/lol.txt");
            //创建基于文件的输入流
            FileInputStream fis =new FileInputStream(f);
            //创建字节数组,其长度就是文件的长度
            byte[] all =new byte[(int) f.length()];
            //以字节流的形式读取文件所有内容
            fis.read(all);
            for (byte b : all) {
                //打印出来是65 66
                System.out.println(b);
            }             
            //每次使用完流,都应该进行关闭
            fis.close();              
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }          
    }
}

(2)以字节流的形式向文件写入数据

        OutputStream是字节输出流,同时也是抽象类,只提供方法声明,不提供方法的具体实现。
        FileOutputStream 是OutputStream子类,以FileOutputStream 为例向文件写出数据。

public class TestStream { 
    public static void main(String[] args) {
        try {
            // 准备文件lol2.txt其中的内容是空的
            File f = new File("d:/lol2.txt");
            // 准备长度是2的字节数组,用88,89初始化,其对应的字符分别是X,Y
            byte data[] = { 88, 89 }; 
            // 创建基于文件的输出流
            FileOutputStream fos = new FileOutputStream(f);
            // 把数据写入到输出流
            fos.write(data);
            // 关闭输出流
            fos.close();             
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } 
    }
}

2.关闭流的方式

        所有的流,无论是输入流还是输出流,使用完毕之后,都应该关闭。 如果不关闭,会产生对资源占用的浪费。 当量比较大的时候,会影响到业务的正常开展。

(1)在try中关闭

        在try的作用域里关闭文件输入流,在前面的示例中都是使用这种方式,这样做有一个弊端;
        如果文件不存在,或者读取的时候出现问题而抛出异常,那么就不会执行这一行关闭流的代码,存在巨大的资源占用隐患。 不推荐使用。

(2)在finally中关闭

        这是标准的关闭流的方式:
                ① 首先把流的引用声明在try的外面,如果声明在try里面,其作用域无法抵达finally;
                ②在finally关闭之前,要先判断该引用是否为空;
                ③关闭的时候,需要再一次进行try catch处理。

public class TestStream { 
    public static void main(String[] args) {
        File f = new File("d:/lol.txt");
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(f);
            byte[] all = new byte[(int) f.length()];
            fis.read(all);
            for (byte b : all) {
                System.out.println(b);
            } 
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 在finally 里关闭流
            if (null != fis)
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
        } 
    }
}

(3)使用try()的方式

        把流定义在try()里,try,catch或者finally结束的时候,会自动关闭。这种编写代码的方式叫做 try-with-resources, 这是从JDK7开始支持的技术。
        所有的流,都实现了一个接口叫做 AutoCloseable,任何类实现了这个接口,都可以在try()中进行实例化。 并且在try, catch, finally结束的时候自动关闭,回收相关资源。

3.字符流

        Reader字符输入流
        Writer字符输出流
        专门用于字符的形式读取和写入数据。字符流读写文件的方式和字节流相同,就是换两个关键字而已。

4.编码问题

(1)常见编码

        工作后经常接触的编码方式有如下几种:
                ISO-8859-1 ASCII 数字和西欧字母
                GBK GB2312 BIG5 中文
                UNICODE (统一码,万国码)
其中
        ISO-8859-1 包含 ASCII;
        GB2312 是简体中文,BIG5是繁体中文,GBK同时包含简体和繁体以及日文;
        UNICODE 包括了所有的文字,无论中文,英文,藏文,法文,世界所有的文字都包含其中。

        但是,如果完全按照UNICODE的方式来存储数据,就会有很大的浪费。在这种情况下,就出现了UNICODE的各种子编码, 比如UTF-8对数字和字母就使用一个字节,而对汉字就使用3个字节,这样就减少了空间的浪费。一般说来UTF-8是比较常用的方式。

(2)Java采用的是Unicode

        写在.java源代码中的汉字,在执行之后,都会变成JVM中的字符。
        而这些中文字符采用的编码方式,都是使用UNICODE. "中"字对应的UNICODE是4E2D,所以在内存中,实际保存的数据就是十六进制的0x4E2D, 也就是十进制的20013。

(3)用FileInputStream 字节流正确读取中文

        为了能够正确的读取中文内容:
        ①须了解文本是以哪种编码方式保存字符的;
        ②使用字节流读取了文本后,再使用对应的编码方式去识别这些数字,得到正确的字符,如一个文件中的内容是字符中,编码方式是GBK,再使用GBK编码方式识别D6D0,就能正确的得到字符中

        使用String(all,"GBK")可以将all字符串的编码方式变为GBK方式,当然也可以变为 "BIG5", "GBK", "GB2312", "UTF-8", "UTF-16", "UTF-32"等方式。

(4)用FileReader 字符流正确读取中文

        FileReader得到的是字符,所以一定是已经把字节根据某种编码识别成了字符了。而FileReader使用的编码方式是Charset.defaultCharset()的返回值,如果是中文的操作系统,就是GBK
        FileReader是不能手动设置编码方式的,为了使用其他的编码方式,只能使用InputStreamReader来代替,像这样:

new InputStreamReader(new FileInputStream(f),Charset.forName("UTF-8")); 

         举个例子:

public class TestStream { 
    public static void main(String[] args) throws UnsupportedEncodingException, FileNotFoundException {
        File f = new File("E:\\project\\j2se\\src\\test.txt");
        System.out.println("默认编码方式:"+Charset.defaultCharset());
        //FileReader得到的是字符,所以一定是已经把字节根据某种编码识别成了字符了
        //而FileReader使用的编码方式是Charset.defaultCharset()的返回值,如果是中文的操作系统,就是GBK
        try (FileReader fr = new FileReader(f)) {
            char[] cs = new char[(int) f.length()];
            fr.read(cs);
            System.out.printf("FileReader会使用默认的编码方式%s,识别出来的字符是:%n",Charset.defaultCharset());
            System.out.println(new String(cs));
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        //FileReader是不能手动设置编码方式的,为了使用其他的编码方式,只能使用InputStreamReader来代替
        //并且使用new InputStreamReader(new FileInputStream(f),Charset.forName("UTF-8")); 这样的形式
        try (InputStreamReader isr = new InputStreamReader(new FileInputStream(f),Charset.forName("UTF-8"))) {
            char[] cs = new char[(int) f.length()];
            isr.read(cs);
            System.out.printf("InputStreamReader 指定编码方式UTF-8,识别出来的字符是:%n");
            System.out.println(new String(cs));
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }         
    }
}

5.缓冲流

        以介质是硬盘为例,字节流和字符流的弊端:
        在每一次读写的时候,都会访问硬盘。 如果读写的频率比较高的时候,其性能表现不佳。

        为了解决以上弊端,采用缓存流。
        缓存流在读取的时候,会一次性读较多的数据到缓存中,以后每一次的读取,都是在缓存中访问,直到缓存中的数据读取完毕,再到硬盘中读取。

        缓存流在写入数据的时候,会先把数据写入到缓存区,直到缓存区达到一定的量,才把这些数据,一起写入到硬盘中去。按照这种操作模式,就不会像字节流,字符流那样每写一个字节都访问硬盘,从而减少了IO操作。

(1)使用缓冲流读写数据

        BufferedReader         可以一次读取一行数据

public class TestStream {  
    public static void main(String[] args) {
        File f = new File("d:/lol.txt");
        // 创建文件字符流
        // 缓存流必须建立在一个存在的流的基础上
        try (
                FileReader fr = new FileReader(f);
                BufferedReader br = new BufferedReader(fr);
            )
        {
            while (true) {
                // 一次读一行
                String line = br.readLine();
                if (null == line)
                    break;
                System.out.println(line);
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }  
    }
}

        PrintWriter         可以一次写出一行数据

public class TestStream {   
    public static void main(String[] args) {
        // 向文件lol2.txt中写入三行语句
        File f = new File("d:/lol2.txt");          
        try (
                // 创建文件字符流
                FileWriter fw = new FileWriter(f);
                // 缓存流必须建立在一个存在的流的基础上              
                PrintWriter pw = new PrintWriter(fw);              
        ) {
            pw.println("garen kill teemo");
            pw.println("teemo revive after 1 minutes");
            pw.println("teemo try to garen, but killed again");
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }   
    }
}

(2)flush

        有时,需要立即把数据写入到硬盘,而不是等缓存满了才写出去。 这时候就需要用到flush。

public class TestStream {
    public static void main(String[] args) {
        //向文件lol2.txt中写入三行语句
        File f =new File("d:/lol2.txt");
        //创建文件字符流
        //缓存流必须建立在一个存在的流的基础上
        try(FileWriter fr = new FileWriter(f);PrintWriter pw = new PrintWriter(fr);) {
            pw.println("garen kill teemo");
            //强制把缓存中的数据写入硬盘,无论缓存是否已满
                pw.flush();           
            pw.println("teemo revive after 1 minutes");
                pw.flush();
            pw.println("teemo try to garen, but killed again");
                pw.flush();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

6.数据流

        DataInputStream 数据输入流
        DataOutputStream 数据输出流

        使用数据流的writeUTF()和readUTF() 可以进行数据的格式化顺序读写        

        注: 要用DataInputStream 读取一个文件,这个文件必须是由DataOutputStream 写出的,否则会出现EOFException,因为DataOutputStream 在写出的时候会做一些特殊标记,只有DataInputStream 才能成功的读取。

public class TestStream {      
    public static void main(String[] args) {
        write();
        read();
    }
 
    private static void read() {
        File f =new File("d:/lol.txt");
        try (
                FileInputStream fis  = new FileInputStream(f);
                DataInputStream dis =new DataInputStream(fis);
        ){
            boolean b= dis.readBoolean();
            int i = dis.readInt();
            String str = dis.readUTF();
             
            System.out.println("读取到布尔值:"+b);
            System.out.println("读取到整数:"+i);
            System.out.println("读取到字符串:"+str); 
        } catch (IOException e) {
            e.printStackTrace();
        }         
    }
 
    private static void write() {
        File f =new File("d:/lol.txt");
        try (
                FileOutputStream fos  = new FileOutputStream(f);
                DataOutputStream dos =new DataOutputStream(fos);
        ){
            dos.writeBoolean(true);
            dos.writeInt(300);
            dos.writeUTF("123 this is gareen");
        } catch (IOException e) {
            e.printStackTrace();
        }         
    }
}

7.对象流

        对象流指的是可以直接把一个对象以流的形式传输给其他的介质,比如硬盘。一个对象以流的形式进行传输,叫做序列化。 该对象所对应的类,必须是实现Serializable接口。

//要把Hero对象直接保存在文件上,务必让Hero类实现Serializable接口
class Hero implements Serializable {
    //表示这个类当前的版本,如果有了变化,比如新设计了属性,就应该修改这个版本号
    private static final long serialVersionUID = 1L;
    public String name;
    public float hp;
 
}

public class TestStream {    
    public static void main(String[] args) {
        Hero h = new Hero();
        h.name = "garen";
        h.hp = 616;          
        //准备一个文件用于保存该对象
        File f =new File("d:/garen.lol"); 
        try(
            //创建对象输出流
            FileOutputStream fos = new FileOutputStream(f);
            ObjectOutputStream oos =new ObjectOutputStream(fos);
            //创建对象输入流              
            FileInputStream fis = new FileInputStream(f);
            ObjectInputStream ois =new ObjectInputStream(fis);
        ) {
            oos.writeObject(h);
            Hero h2 = (Hero) ois.readObject();
            System.out.println(h2.name);
            System.out.println(h2.hp);               
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }            
    }
}

8.控制台输入输出

(1) System.out与System.in

        System.out 是常用的在控制台输出数据的
        System.in 可以从控制台输入数据

public class TestStream { 
    public static void main(String[] args) {
        // 控制台输入
        try (InputStream is = System.in;) {
            while (true) {
                // 敲入a,然后敲回车可以看到
                // 97 13 10
                // 97是a的ASCII码
                // 13 10分别对应回车换行
                int i = is.read();
                System.out.println(i);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

(2)Scanner

        使用System.in.read虽然可以读取数据,但是很不方便。使用Scanner就可以逐行读取了。

        Scanner()前面已经用过了,这里就放个例子。

public class TestStream {    
    public static void main(String[] args) {         
        Scanner s = new Scanner(System.in);             
        while(true){
        String line = s.nextLine();
        System.out.println(line);
        }         
    }
}

9.流之间的关系

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值