【Java】I/O工作机制

流的理解

一个流可以理解为一个字节数据的序列。

输入流(InputStream)表示从一个源读取(read)数据。

输出流(OutputStream)表示向一个目标写(write)数据。

一个不恰当但是很形象的例子:

输入流就是从磁盘或网络读取字节数据到程序内存;

输出流就是把字节数据从程序内存写到磁盘或网络中去。

这样来看,所谓的输入或输出,参考方向都是当前程序内存而言。所以一个流不可能既是输入流也是输出流的说法。

流分类

按功能分:

低级流:有明确的数据源。

高级流:没有明确的数据源,用于处理其他低级流,高级流不能单独存在,只能与低级流配合使用。

所以区分也很好区分,也就是看有没有明确的数据源,是否依赖低级流。

按读取数据单位分:

字节流:以字节为单位进行读写,InputStream OutputStream

字符流:以字符为单位进行读写,Writer  Reader

==================================

字节(Byte):字节是通过网络传输信息(或在硬盘或内存中存储信息)的单位。字节是计算机信息技术用于计量存储容量和传输容量的一种计量单位,1个字节等于8位二进制(1 byte  = 8  bit),它是一个8位的二进制数,是一个很具体的存储空间。
字符:字符是指计算机中使用的文字和符号人们使用的记号,抽象意义上的一个符号。 
关于字节和字符的关系,就不得不说道编码方式。不同的编码方式里,字符和字节的对应关系不同。字符之间的编码不一致也是造成乱码的原因。

=======================================

以下详细说明几种流

Java 为 I/O 提供了强大的而灵活的支持,使其更广泛地应用到文件传输和网络编程中。

Java所有的流类位于java.io包中,都分别继承字以下四种抽象流类型。

继承自InputStream/OutputStream的流都是用于向程序中输入/输出数据,且数据的单位都是字节(byte=8bit)。

继承自Reader/Writer的流都是用于向程序中输入/输出数据,且数据的单位都是字符(2byte=16bit)。

Type
字节流
字符流
输入流
InputStream
Reader
输出流
OutputStream
Writer


字节流:

1、用于读写文件的输入输出流

FileInputStream:文件字节输入流

该流用于从文件读取数据,它的对象可以用关键字 new 来创建。
有多种构造方法可用来创建对象。
可以使用字符串类型的文件名来创建一个输入流对象来读取文件:
InputStream f =new FileInputStream("C:/xxxx/xxxx");
也可以使用一个文件对象来创建一个输入流对象来读取文件。我们首先得使用 File() 方法来创建一个文件对象:
File file =new File("C:/xxxx/xxxxx");
InputStream out =new FileInputStream(file);


FileOutputStream:文件字节输出流

该类用来创建一个文件并向文件中写数据。
如果该流在打开文件进行输出前,目标文件不存在,那么该流会创建该文件。
有两个构造方法可以用来创建 FileOutputStream 对象。
使用字符串类型的文件名来创建一个输出流对象:
OutputStream f =new FileOutputStream("C:/xxxx/xxxx")
也可以使用一个文件对象来创建一个输出流来写文件。我们首先得使用File()方法来创建一个文件对象:
File f = new File("C:/xxxx/xxxx");
OutputStream f =new FileOutputStream(f);

下面是一个演示 InputStream 和 OutputStream 用法的例子:

public class FileStreamsDemo {
    public static void main(String[] args) {
        try{
            byte bWrite [] = {11,21,3,40,5};
            OutputStream os = new FileOutputStream("test.txt");
            for(int x=0; x < bWrite.length ; x++){
                os.write( bWrite[x] ); // writes the bytes
            }
            os.close();

            InputStream is = new FileInputStream("test.txt");
            int size = is.available();

            for(int i=0; i< size; i++){
                System.out.print((char)is.read() + "  ");
            }
            is.close();
        }catch(IOException e){
            System.out.print("Exception");
        }
    }
}

上面的程序首先创建文件test.txt,并把给定的数字以二进制形式写进该文件,同时输出到控制台上。

以上代码由于是二进制写入,可能存在乱码,你可以使用以下代码实例来解决乱码问题:

public class FileStreamsDemo2 {
    public static void main(String[] args) throws IOException {
        File f = new File("a.txt");
        FileOutputStream fop = new FileOutputStream(f);
        // 构建FileOutputStream对象,文件不存在会自动新建

        OutputStreamWriter writer = new OutputStreamWriter(fop, "UTF-8");
        // 构建OutputStreamWriter对象,参数可以指定编码,默认为操作系统默认编码,windows上是gbk

        writer.append("中文输入");
        // 写入到缓冲区

        writer.append("\r\n");
        //换行

        writer.append("English");
        // 刷新缓存冲,写入到文件,如果下面已经没有写入的内容了,直接close也会写入

        writer.close();
        //关闭写入流,同时会把缓冲区内容写入文件,所以上面的注释掉

        fop.close();
        // 关闭输出流,释放系统资源

        FileInputStream fip = new FileInputStream(f);
        // 构建FileInputStream对象

        InputStreamReader reader = new InputStreamReader(fip, "UTF-8");
        // 构建InputStreamReader对象,编码与写入相同

        StringBuffer sb = new StringBuffer();
        while (reader.ready()) {
            sb.append((char) reader.read());
            // 转成char加到StringBuffer对象中
        }
        System.out.println(sb.toString());
        reader.close();
        // 关闭读取流

        fip.close();
        // 关闭输入流,释放系统资源
    }
}


2、BufferedInputStream和BufferedOutputStream 缓冲字节输入输出流
相比文件字节输入输出流而言,缓冲流的内部维护了一个缓冲区(字节数组),可以减少读写次数,从而提高读写效率。缓冲流输入高级流,依赖低级流,所以在最后关闭流的时候,是先关闭低级流,再关闭高级流。
使用缓冲流一定注意一个问题。缓冲流默认是读写满了缓冲区,才会真正的读写出去。所以,没有读写满时,一定要记得flush()方法,不然会丢数据。这个有点坑人!!

3、DataInputStream和DataOutputStream 基于基本类型数据读写输入输出高级流
这种流不同于FileInputStream、FileOutputStream、BufferedInputStream和BufferedOutputStream只有简单的按字节读写的write和read方法。DataOutputStream和DataInputStream都是writeInt,ReadInt方法。相率上没有BufferedInputStream和BufferedOutputStream高。但是高级流是可以组合使用的。一般这种流用于组合其他高级流使用,从而提高读写效率。

4、ObjectInputStream和ObjectOutputStream  基于Java对象的高级流
ObjectInputStream:用于将一个文件先序列化为字节数据读取后转换为Java对象
ObjectOutputStream:用于将一个Java对象转换为响应的字节并写入文件

这两个高级流很容易出现的错误就是NotSerializableException未序列化异常。所以使用这两个高级流进行Java对象的转换就需要Java对象能够进行序列化,实现了Serivalizable接口。

随便提及一下关于序列化的问题:
序列化:将基本类型数据(或对象bean)转化为对应的字节序列。
反序列化:将字节序列转化为对应的基本类型数据(或对象bean)。

把一个对象序列化以后,再反序列化回来,并不是同一个对象,只是内容相同而已。
一个对象序列化以后,能否反序列化回来,还得看序列化的版本号(long serialVersionUID=1L)是否一致,若不一致就不能反序列化回来。会引发InvalidClassException异常。

还有一个关键字:transient。被transient关键字修饰的属性,在序列化的时候,会被忽略。

字符流(都是高级流):

字符输入输出流使用时有一定的局限性,只能读取文本文件,不适用于读取其他数据类型,比如图片,视频,音频等。要读取图片,视频,音频这些只能通过字节输入输出流进行读取。
字节 到 字符 之间的相互转换,中间就会涉及到 字符集编码转换的问题。

1、InputStreamReader 和OutputStreamWriter 字符输入输出流

以字符为单位读写数据。可修改字符集

2、BufferedReader 和 BufferedWriter  缓冲字符输入输出流
以行为单位读写字符串。

3、FileReader 和 FileWriter 读取文本文件的字符输入输出流  
不能修改字符集,只能按照当前系统默认的字符集进行读取

4、PrintWriter  一种高级的缓冲字符输出流。工作中常用。
 
web开发中常用,输出到指定页面渲染。

((HttpServletResponse) response).setStatus(HttpStatus.SC_INTERNAL_SERVER_ERROR);
response.setContentType("application/json;charset=UTF-8");
Map<String, Object> resultMap = "";
String respString = JSONObject.toJSONString(resultMap);
PrintWriter out = response.getWriter();
out.print(respString);
out.close();


Java中的目录


File类用于描述文件系统中的一个文件或目录。
功能:通过File类可以获取文件或目录的名称,大小,修改时间等属性信息,可以创建,删除文件,但是不能修改编辑文件内容。

创建目录:

File类中有两个方法可以用来创建文件夹:
mkdir( )方法创建一个文件夹,成功则返回true,失败则返回false。失败表明File对象指定的路径已经存在,或者由于整个路径还不存在,该文件夹不能被创建。
mkdirs()方法创建一个文件夹和它的所有父文件夹。
注意: Java 在 UNIX 和 Windows 自动按约定分辨文件路径分隔符。

读取目录:

一个目录其实就是一个 File 对象,它包含其他文件和文件夹。
如果创建一个 File 对象并且它是一个目录,那么调用 isDirectory() 方法会返回 true。
可以通过调用该对象上的 list() 方法,来提取它包含的文件和文件夹的列表。
下面展示的例子说明如何使用 list() 方法来检查一个文件夹中包含的内容:
public class ReadDirListDemo {
    public static void main(String[] args) {
        String dirname = "/tmp";
        File f1 = new File(dirname);
        if (f1.isDirectory()) {
            System.out.println( "目录 " + dirname);
            String s[] = f1.list();
            for (int i=0; i < s.length; i++) {
                File f = new File(dirname + "/" + s[i]);
                if (f.isDirectory()) {
                    System.out.println(s[i] + " 是一个目录");
                } else {
                    System.out.println(s[i] + " 是一个文件");
                }
            }
        } else {
            System.out.println(dirname + " 不是一个目录");
        }
    }
}

删除目录或文件:

删除文件可以使用 java.io.File.delete() 方法。
以下代码会删除目录/tmp/java/,即便目录不为空。
测试目录结构:
public class DeleteFileDemo {
    public static void main(String args[]) {
        // 这里修改为自己的测试目录
        File folder = new File("/tmp/java/");
        deleteFolder(folder);
    }

    //删除文件及目录
    public static void deleteFolder(File folder) {
        File[] files = folder.listFiles();
        if(files!=null) {
            for(File f: files) {
                if(f.isDirectory()) {
                    deleteFolder(f);
                } else {
                    f.delete();
                }
            }
        }
        folder.delete();
    }
}


RandomAccessFile类:随机读写文件内容

读写数据都是基于字节形式的。读写完毕后要关闭,释放对文件的操作权限以及资源。
RandomAccessFile是基于游标进行读写操作的,总是读取或写入游标置顶的位置,所以当连续读写完成后,游标只想文件末尾,如果此时再试图读取,就会抛异常了。所以每次读取之前,先确保游标的位置。游标详细操作参考jdk。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章