JAVA ·

JAVA流的记录,字符流与字节流的理解

一:JAVA

  • JAVA中的流有两种,一种是字符流,另一种就是字节流
  • 字符流:专门处理字符类型的数据,如 String,Char等等。
  • 字节流:专门处理字节类型的数据,如byte[],二进制文件等等。

JAVA中流的总结图如下:

我们都知道计算机是使用二进制来表示数据。由此可以推断出字符流在最后的操作肯定也是字节操作,JAVA中这样区别个人认为是方便开发者,让我更加集中的去处理业务上面,减少这种底层转换的操作,而且底层的操作容易犯错,且不好调试!那在JAVA字节转字符流是如何操作呢?

OutputStream os = socket.getOutputStream();
PrintWriter pw = new PrintWriter(os);
pw.println("hello world");
pw.flush();

上面代码PrintWrite实例化的时候传入一个OutputStream的字节流,然后输出字符串hello world。记得一定要flush(),在我的代码中没有调用flush()会导致字符串没有写出去InputStream获取不到。JDK到底是如何处理字符流的呢?带着这个疑问和大家去进一步瞧瞧!

public PrintWriter(Writer out, boolean autoFlush) {
            super(out);
            this.out = out;
            this.autoFlush = autoFlush;
            lineSeparator = java.security.AccessController.doPrivileged(
                    new sun.security.action.GetPropertyAction("line.separator"));
        }

上面这段代码是PrintWrite的构造,其中的out就是基类Write。autoFlush默认是false。再看out具体是那个字符流,

public PrintWriter(OutputStream out, boolean autoFlush) {
        this(new BufferedWriter(new OutputStreamWriter(out)), autoFlush);
        // save print stream for error propagation
        if (out instanceof java.io.PrintStream) {
            psOut = (PrintStream) out;
        }
    }

这里就可以看到out最后是BufferedWriter,而BufferedWriter的操作最终是OutputStreamWriter。最终字符组操作是在OutputStreamWriter中

public void write(char cbuf[], int off, int len) throws IOException {
        se.write(cbuf, off, len);
    }

se是StreamEncoder.forOutputStreamWriter(out, this, (String)null);而StreamEncoder在jdk中无法开的。在网上找到这个类的代码

public static StreamEncoder forOutputStreamWriter(OutputStream out,Object lock,String charsetName)
            throws UnsupportedEncodingException {
        String csn = charsetName;
        if (csn == null)
            csn = Charset.defaultCharset().name();
        try {
            if (Charset.isSupported(csn)){
                return new StreamEncoder(out, lock, Charset.forName(csn));
            }
        } catch (IllegalCharsetNameException x) {
            throw new UnsupportedEncodingException (csn);
        }
    }

其中Charset.defaultCharset().name();这个就是后面字符转字节的编码格式,跟随你的IDE设置。最终调用的代码

private void writeBytes() throws IOException {
        bb.flip();
        int lim = bb.limit();
        int pos = bb.position();
        assert (pos <= lim);
        int rem = (pos <= lim ? lim - pos : 0);
        if (rem > 0) {
            if (ch != null) {
                if (ch.write(bb) != rem)
                    assert false : rem;
            } else {
                out.write(bb.array(), bb.arrayOffset() + pos, rem);
            }
        }
        bb.clear();
    }

这个类有两个不同的构造函数一个传入的是字节流OutPutStream另外一个是WritableByteChannel,从上面代码的分析可以看出字符流后面还是字节流,只是JDK帮我们做了一些转换的工作。因此效率上字节流比字符流更加有效率!!在写代码的过程中我对一下代码有过疑问!

byte[] buffer = new byte[4096];
    FileInputStream fis = new FileInputStream(fileToSend);
    BufferedInputStream bis = new BufferedInputStream(fis);
    // long BytesToSend = fileToSend.length();
    while(true){
        int bytesRead = bis.read(buffer, 0, buffer.length);
        if(bytesRead == -1){
            break;
        }
        os.write(buffer,0, bytesRead);
        os.flush();
    }

疑问就是:每次我们都会传入一个4096大小的byte[],假如最后一次读取数据的大小不够4096是如何处理的?是否同样的把最后整个byte[]都写过去了?这样就会导致写入的数据比实际的要大,而且如果是socket通信,客户端获取的数据长度要比实际的要长?那么到底是不是这样的,所以才有这次java流的记录。带着这个疑问去瞧瞧Unix I/O

二:Unix I/O

在linux中一切皆文件,linux也是Unix发展过来的。一个Unix文件就是一个m个字节序列,因此所有的操作都当做是读写来执行的。

打开文件。当打开一个文件时,unix内核会返回一个非负整数的描述符。用来代表这个文件,后面所有的操作都是针对这个描述符!

文件的位置。每个打开的文件,内核都会保存一个文件的位置K,初始值是0,可以设置K的位置,即从文件的哪里开始读写。

读写文件。一个读的操作就是从文件位置K读取n个字节到存储器。然后K = K + n,当K大于文件的总字节数后,就会触发一个end-of-file(EOF)表示文件结尾。写就是从存储器中拷贝n个字节,从k的位置开始。然后更新文件
关闭文件。访问完毕后,内核就会关闭这个文件,并且把这个文件描述符放回描述符池中,以备下个访问。
每个文件都有对应的读写权限,这里就不在阐述。那么Unix I/O是如何处理我上面这个疑问的呢? 下面是Unix I/O的处理不足的情况!

public static void main(String[] args) throws InterruptedException {
        try{
            File a = new File("C:\Users\ryze.liu\Desktop\a.txt");
            File b = new File("C:\Users\ryze.liu\Desktop\b.txt");
            byte[] buffer = new byte[10];
            FileInputStream fis = new FileInputStream(a);
            FileOutputStream os = new FileOutputStream(b);
            // long BytesToSend = fileToSend.length();
            while(true){
                int bytesRead = fis.read(buffer, 0, buffer.length);
                if(bytesRead == -1){
                    break;
                }
                //BytesToSend = BytesToSend - bytesRead;
                System.out.println("bytesRead:::"+bytesRead);
                for(int i = 0;i < 10;i++){
                    System.out.println("buffer:::"+buffer[i]);
                }
                //os.write(buffer,0, buffer.length);
                os.write(buffer,0, bytesRead);
                os.flush();
            }
        }catch (IOException e) {

        }
    }

我们创建一个a.txt和b.txt文件,把a内容写入b中,a文件的大小是5个byte,然后用一个10大小的byte[]去读,然后再写入!并且打印读取的大小和每个byte数据! 最后得到的结果。

bytesRead:::5
buffer:::116
buffer:::101
buffer:::115
buffer:::116
buffer:::101
buffer:::0
buffer:::0
buffer:::0
buffer:::0
buffer:::0

可以看到读取的字节数是5个,多余的全部是0,并且b文件的大小和a一样!如果最后使用os.write(buffer,0, buffer.length);就会发现b的文件大小是10个字节!0输出空白但却占一个字符位置。通过这次记录,让我更加的明白java的I/O处理,之前虽然有过接触,但是总感觉有些地方有点不明白。这次进行了一些巩固与加深,其实有的时候当你觉得你对某种技术或者其他的东西有中似懂非懂的感觉,其实就是不懂,当你真正用的时候会发现有很多的问题。

秋名山上行人希,常有高手论高低,如今道路依旧在,不见当年老司机!!

参与评论