Posted in Java, 技术

深入浅出: 大小端模式

Writer      :BYSocket(泥沙砖瓦浆木匠)

一、什么大小端?

大小端在计算机业界,Endian表示数据在存储器中的存放顺序。百度百科如下叙述之:

大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放;

小端模式,是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低,和我们的逻辑方法一致。

这两种模式,泥瓦匠记忆宫殿:“小端低低”。这样就知道小端的模式,反之大端的模式。

比如整形十进制数字:305419896 ,转化为十六进制表示 : 0x12345678 。其中按着十六进制的话,每两位占8个位,及一个字节。如图

iostream

二、为什么有大小端模式之分呢?

如果统一使用大端或者小端,那么何来三国演义,何来一战二战呢?还有大小端也来源于战争。所以存在即是合理。

在操作系统中,x86和一般的OS(如windows,FreeBSD,Linux)使用的是小端模式。但比如Mac OS是大端模式。

在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8bit。但是在C语言中除了8bit的char之外,还有16bit的short型,32bit的long型(要看具体的编译器)。另外,对于位数大于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如果将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。

 

知道为什么有模式的存在,下面需要了解下具有有什么应用场景

1、不同端模式的处理器进行数据传递时必须要考虑端模式的不同

2、在网络上传输数据时,由于数据传输的两端对应不同的硬件平台,采用的存储字节顺序可能不一致。所以在TCP/IP协议规定了在网络上必须采用网络字节顺序,也就是大端模式。对于char型数据只占一个字节,无所谓大端和小端。而对于非char类型数据,必须在数据发送到网络上之前将其转换成大端模式。接收网络数据时按符合接受主机的环境接收。

 

三、java中的大小端

存储量大于1字节,非char类型,如int,float等,要考虑字节的顺序问题了。java由于虚拟机的关系,屏蔽了大小端问题,需要知道的话可用 ByteOrder.nativeOrder() 查询。在操作ByteBuffer中,也可以使用 ByteBuffer.order() 进行设置:

/**
 * @author Jeff Lee
 * @since 2015-10-13 20:40:00
 * ByteBuffer中字节存储次序
 */
public class Endians {
    public static void main(String[] args) {
        // 创建12个字节的字节缓冲区
        ByteBuffer bb = ByteBuffer.wrap(new byte[12]);
        // 存入字符串
        bb.asCharBuffer().put("abdcef");
        System.out.println(Arrays.toString(bb.array()));

        // 反转缓冲区
        bb.rewind();
        // 设置字节存储次序
        bb.order(ByteOrder.BIG_ENDIAN);
        bb.asCharBuffer().put("abcdef");
        System.out.println(Arrays.toString(bb.array()));

        // 反转缓冲区
        bb.rewind();
        // 设置字节存储次序
        bb.order(ByteOrder.LITTLE_ENDIAN);
        bb.asCharBuffer().put("abcdef");
        System.out.println(Arrays.toString(bb.array()));
    }
}

run下结果如图所示:

image

前两句打印说明了,ByteBuffer存储字节次序默认为大端模式。最后一段设置了字节存储次序,然后会输出,可以看出存储次序为小端模式。

 

欢迎点击我的博客及GitHub — 博客提供RSS订阅哦!

———- http://www.bysocket.com/ ————- https://github.com/JeffLi1993 ———-

微         博:BYSocket  豆         瓣:BYSocket  FaceBook:BYSocket  Twitter    :BYSocket

Posted in Java, 技术

Java IO 之 FileInputStream & FileOutputStream源码分析

Writer      :BYSocket(泥沙砖瓦浆木匠)

微         博:BYSocket

豆         瓣:BYSocket

FaceBook:BYSocket

Twitter    :BYSocket

一、引子

文件,作为常见的数据源。关于操作文件的字节流就是 — FileInputStream & FileOutputStream。它们是Basic IO字节流中重要的实现类。

 

二、FileInputStream源码分析

FileInputStream源码如下:

/**
 * FileInputStream 从文件系统的文件中获取输入字节流。文件取决于主机系统。
 * 	比如读取图片等的原始字节流。如果读取字符流,考虑使用 FiLeReader。
 */
public class SFileInputStream extends InputStream
{
    /* 文件描述符类---此处用于打开文件的句柄 */
    private final FileDescriptor fd;

    /* 引用文件的路径 */
    private final String path;

    /* 文件通道,NIO部分 */
    private FileChannel channel = null;

    private final Object closeLock = new Object();
    private volatile boolean closed = false;

    private static final ThreadLocal<Boolean> runningFinalize =
        new ThreadLocal<>();

    private static boolean isRunningFinalize() {
        Boolean val;
        if ((val = runningFinalize.get()) != null)
            return val.booleanValue();
        return false;
    }

    /* 通过文件路径名来创建FileInputStream */
    public FileInputStream(String name) throws FileNotFoundException {
        this(name != null ? new File(name) : null);
    }

    /* 通过文件来创建FileInputStream */
    public FileInputStream(File file) throws FileNotFoundException {
        String name = (file != null ? file.getPath() : null);
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkRead(name);
        }
        if (name == null) {
            throw new NullPointerException();
        }
        if (file.isInvalid()) {
            throw new FileNotFoundException("Invalid file path");
        }
        fd = new FileDescriptor();
        fd.incrementAndGetUseCount();
        this.path = name;
        open(name);
    }

    /* 通过文件描述符类来创建FileInputStream */
    public FileInputStream(FileDescriptor fdObj) {
        SecurityManager security = System.getSecurityManager();
        if (fdObj == null) {
            throw new NullPointerException();
        }
        if (security != null) {
            security.checkRead(fdObj);
        }
        fd = fdObj;
        path = null;
        fd.incrementAndGetUseCount();
    }

    /* 打开文件,为了下一步读取文件内容。native方法 */
    private native void open(String name) throws FileNotFoundException;

    /* 从此输入流中读取一个数据字节 */
    public int read() throws IOException {
        Object traceContext = IoTrace.fileReadBegin(path);
        int b = 0;
        try {
            b = read0();
        } finally {
            IoTrace.fileReadEnd(traceContext, b == -1 ? 0 : 1);
        }
        return b;
    }

    /* 从此输入流中读取一个数据字节。native方法 */
    private native int read0() throws IOException;

    /* 从此输入流中读取多个字节到byte数组中。native方法 */
    private native int readBytes(byte b[], int off, int len) throws IOException;

    /* 从此输入流中读取多个字节到byte数组中。 */
    public int read(byte b[]) throws IOException {
        Object traceContext = IoTrace.fileReadBegin(path);
        int bytesRead = 0;
        try {
            bytesRead = readBytes(b, 0, b.length);
        } finally {
            IoTrace.fileReadEnd(traceContext, bytesRead == -1 ? 0 : bytesRead);
        }
        return bytesRead;
    }

    /* 从此输入流中读取最多len个字节到byte数组中。 */
    public int read(byte b[], int off, int len) throws IOException {
        Object traceContext = IoTrace.fileReadBegin(path);
        int bytesRead = 0;
        try {
            bytesRead = readBytes(b, off, len);
        } finally {
            IoTrace.fileReadEnd(traceContext, bytesRead == -1 ? 0 : bytesRead);
        }
        return bytesRead;
    }

    
    public native long skip(long n) throws IOException;

    /* 返回下一次对此输入流调用的方法可以不受阻塞地从此输入流读取(或跳过)的估计剩余字节数。 */
    public native int available() throws IOException;

    /* 关闭此文件输入流并释放与此流有关的所有系统资源。 */
    public void close() throws IOException {
        synchronized (closeLock) {
            if (closed) {
                return;
            }
            closed = true;
        }
        if (channel != null) {
           fd.decrementAndGetUseCount();
           channel.close();
        }

        int useCount = fd.decrementAndGetUseCount();

        if ((useCount <= 0) || !isRunningFinalize()) {
            close0();
        }
    }

    public final FileDescriptor getFD() throws IOException {
        if (fd != null) return fd;
        throw new IOException();
    }

    /* 获取此文件输入流的唯一FileChannel对象 */
    public FileChannel getChannel() {
        synchronized (this) {
            if (channel == null) {
                channel = FileChannelImpl.open(fd, path, true, false, this);
                fd.incrementAndGetUseCount();
            }
            return channel;
        }
    }

    private static native void initIDs();

    private native void close0() throws IOException;

    static {
        initIDs();
    }

    protected void finalize() throws IOException {
        if ((fd != null) &&  (fd != FileDescriptor.in)) {
            runningFinalize.set(Boolean.TRUE);
            try {
                close();
            } finally {
                runningFinalize.set(Boolean.FALSE);
            }
        }
    }
}

1. 三个核心方法

三个核心方法,也就是Override(重写)了抽象类InputStreamread方法。

int read() 方法,即

public int read() throws IOException

代码实现中很简单,一个try中调用本地nativeread0()方法,直接从文件输入流中读取一个字节。IoTrace.fileReadEnd(),字面意思是防止文件没有关闭读的通道,导致读文件失败,一直开着读的通道,会造成内存泄露。

 

int read(byte b[]) 方法,即

public int read(byte b[]) throws IOException

代码实现也是比较简单的,也是一个try中调用本地nativereadBytes()方法,直接从文件输入流中读取最多b.length个字节到byte数组b中。

 

int read(byte b[], int off, int len) 方法,即

public int read(byte b[], int off, int len) throws IOException

代码实现和 int read(byte b[])方法 一样,直接从文件输入流中读取最多len个字节到byte数组b中。

可是这里有个问答:

Q: 为什么 int read(byte b[]) 方法需要自己独立实现呢? 直接调用 int read(byte b[], int off, int len) 方法,即read(b , 0 , b.length),等价于read(b)?

A:待完善,希望路过大神回答。。。。向下兼容?? Finally??

 

2. 值得一提的native方法

上面核心方法中为什么实现简单,因为工作量都在native方法里面,即JVM里面实现了。native倒是不少一一列举吧:

native void open(String name) // 打开文件,为了下一步读取文件内容

native int read0() // 从文件输入流中读取一个字节

native int readBytes(byte b[], int off, int len) // 从文件输入流中读取,从off句柄开始的len个字节,并存储至b字节数组内。

native void close0() // 关闭该文件输入流及涉及的资源,比如说如果该文件输入流的FileChannel对被获取后,需要对FileChannel进行close。

 

其他还有值得一提的就是,在jdk1.4中,新增了NIO包,优化了一些IO处理的速度,所以在FileInputStream和FileOutputStream中新增了FileChannel getChannel()的方法。即获取与该文件输入流相关的 java.nio.channels.FileChannel对象。

 

三、FileOutputStream 源码分析

FileOutputStream 源码如下:

/**
 * 文件输入流是用于将数据写入文件或者文件描述符类
 * 	比如写入图片等的原始字节流。如果写入字符流,考虑使用 FiLeWriter。
 */
public class SFileOutputStream extends OutputStream
{
    /* 文件描述符类---此处用于打开文件的句柄 */
    private final FileDescriptor fd;

    /* 引用文件的路径 */
    private final String path;

    /* 如果为 true,则将字节写入文件末尾处,而不是写入文件开始处 */
    private final boolean append;

    /* 关联的FileChannel类,懒加载 */
    private FileChannel channel;

    private final Object closeLock = new Object();
    private volatile boolean closed = false;
    private static final ThreadLocal<Boolean> runningFinalize =
        new ThreadLocal<>();

    private static boolean isRunningFinalize() {
        Boolean val;
        if ((val = runningFinalize.get()) != null)
            return val.booleanValue();
        return false;
    }

    /* 通过文件名创建文件输入流 */
    public FileOutputStream(String name) throws FileNotFoundException {
        this(name != null ? new File(name) : null, false);
    }

    /* 通过文件名创建文件输入流,并确定文件写入起始处模式 */
    public FileOutputStream(String name, boolean append)
        throws FileNotFoundException
    {
        this(name != null ? new File(name) : null, append);
    }

    /* 通过文件创建文件输入流,默认写入文件的开始处 */
    public FileOutputStream(File file) throws FileNotFoundException {
        this(file, false);
    }

    /* 通过文件创建文件输入流,并确定文件写入起始处  */
    public FileOutputStream(File file, boolean append)
        throws FileNotFoundException
    {
        String name = (file != null ? file.getPath() : null);
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkWrite(name);
        }
        if (name == null) {
            throw new NullPointerException();
        }
        if (file.isInvalid()) {
            throw new FileNotFoundException("Invalid file path");
        }
        this.fd = new FileDescriptor();
        this.append = append;
        this.path = name;
        fd.incrementAndGetUseCount();
        open(name, append);
    }

    /* 通过文件描述符类创建文件输入流 */
    public FileOutputStream(FileDescriptor fdObj) {
        SecurityManager security = System.getSecurityManager();
        if (fdObj == null) {
            throw new NullPointerException();
        }
        if (security != null) {
            security.checkWrite(fdObj);
        }
        this.fd = fdObj;
        this.path = null;
        this.append = false;

        fd.incrementAndGetUseCount();
    }

    /* 打开文件,并确定文件写入起始处模式 */
    private native void open(String name, boolean append)
        throws FileNotFoundException;

    /* 将指定的字节b写入到该文件输入流,并指定文件写入起始处模式 */
    private native void write(int b, boolean append) throws IOException;

    /* 将指定的字节b写入到该文件输入流 */
    public void write(int b) throws IOException {
        Object traceContext = IoTrace.fileWriteBegin(path);
        int bytesWritten = 0;
        try {
            write(b, append);
            bytesWritten = 1;
        } finally {
            IoTrace.fileWriteEnd(traceContext, bytesWritten);
        }
    }

    /* 将指定的字节数组写入该文件输入流,并指定文件写入起始处模式 */
    private native void writeBytes(byte b[], int off, int len, boolean append)
        throws IOException;

    /* 将指定的字节数组b写入该文件输入流 */
    public void write(byte b[]) throws IOException {
        Object traceContext = IoTrace.fileWriteBegin(path);
        int bytesWritten = 0;
        try {
            writeBytes(b, 0, b.length, append);
            bytesWritten = b.length;
        } finally {
            IoTrace.fileWriteEnd(traceContext, bytesWritten);
        }
    }

    /* 将指定len长度的字节数组b写入该文件输入流 */
    public void write(byte b[], int off, int len) throws IOException {
        Object traceContext = IoTrace.fileWriteBegin(path);
        int bytesWritten = 0;
        try {
            writeBytes(b, off, len, append);
            bytesWritten = len;
        } finally {
            IoTrace.fileWriteEnd(traceContext, bytesWritten);
        }
    }

    /* 关闭此文件输出流并释放与此流有关的所有系统资源 */
    public void close() throws IOException {
        synchronized (closeLock) {
            if (closed) {
                return;
            }
            closed = true;
        }

        if (channel != null) {
            fd.decrementAndGetUseCount();
            channel.close();
        }

        int useCount = fd.decrementAndGetUseCount();

        if ((useCount <= 0) || !isRunningFinalize()) {
            close0();
        }
    }

     public final FileDescriptor getFD()  throws IOException {
        if (fd != null) return fd;
        throw new IOException();
     }

    public FileChannel getChannel() {
        synchronized (this) {
            if (channel == null) {
                channel = FileChannelImpl.open(fd, path, false, true, append, this);

                fd.incrementAndGetUseCount();
            }
            return channel;
        }
    }

    protected void finalize() throws IOException {
        if (fd != null) {
            if (fd == FileDescriptor.out || fd == FileDescriptor.err) {
                flush();
            } else {

                runningFinalize.set(Boolean.TRUE);
                try {
                    close();
                } finally {
                    runningFinalize.set(Boolean.FALSE);
                }
            }
        }
    }

    private native void close0() throws IOException;

    private static native void initIDs();

    static {
        initIDs();
    }

}

1. 三个核心方法

三个核心方法,也就是Override(重写)了抽象类OutputStreamwrite方法。

void write(int b) 方法,即

public void write(int b) throws IOException

代码实现中很简单,一个try中调用本地nativewrite()方法,直接将指定的字节b写入文件输出流。IoTrace.fileReadEnd()的意思和上面FileInputStream意思一致。

 

void write(byte b[]) 方法,即

public void write(byte b[]) throws IOException

代码实现也是比较简单的,也是一个try中调用本地nativewriteBytes()方法,直接将指定的字节数组写入该文件输入流。

 

void write(byte b[], int off, int len) 方法,即

public void write(byte b[], int off, int len) throws IOException

代码实现和 void write(byte b[]) 方法 一样,直接将指定的字节数组写入该文件输入流。

 

2. 值得一提的native方法

上面核心方法中为什么实现简单,因为工作量都在native方法里面,即JVM里面实现了。native倒是不少一一列举吧:

native void open(String name) // 打开文件,为了下一步读取文件内容

native void write(int b, boolean append) // 直接将指定的字节b写入文件输出流

native native void writeBytes(byte b[], int off, int len, boolean append) // 直接将指定的字节数组写入该文件输入流。

native void close0() // 关闭该文件输入流及涉及的资源,比如说如果该文件输入流的FileChannel对被获取后,需要对FileChannel进行close。

 

相似之处

其实到这里,该想一想。两个源码实现很相似,而且native方法也很相似。其实不能说“相似”,应该以“对应”来概括它们。

它们是一组,是一根吸管的两个孔的关系:“一个Input一个Output”。

 

休息一下吧~ 看看小广告:

开源代码都在我的gitHub上哦 — https://github.com/JeffLi1993 作者留言“请手贱,点项目star,支持支持拜托拜托”

 

四、使用案例

下面先看代码:

package org.javacore.io;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

/*
 * Copyright [2015] [Jeff Lee]
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @author Jeff Lee
 * @since 2015-10-8 20:06:03
 * FileInputStream&FileOutputStream使用案例
 */
public class FileIOStreamT {
	private static final String thisFilePath = 
			"src" + File.separator +
			"org" + File.separator +
			"javacore" + File.separator +
			"io" + File.separator +
			"FileIOStreamT.java";
	public static void main(String[] args) throws IOException {
		// 创建文件输入流
		FileInputStream fileInputStream = new FileInputStream(thisFilePath);
		// 创建文件输出流
		FileOutputStream fileOutputStream =  new FileOutputStream("data.txt");
		
		// 创建流的最大字节数组
		byte[] inOutBytes = new byte[fileInputStream.available()];
		// 将文件输入流读取,保存至inOutBytes数组
		fileInputStream.read(inOutBytes);
		// 将inOutBytes数组,写出到data.txt文件中
		fileOutputStream.write(inOutBytes);
		
		fileOutputStream.close();
		fileInputStream.close();
	}
}

运行后,会发现根目录中出现了一个“data.txt”文件,内容为上面的代码。

1. 简单地分析下源码:

1、创建了FileInputStream,读取该代码文件为文件输入流。

2、创建了FileOutputStream,作为文件输出流,输出至data.txt文件。

3、针对流的字节数组,一个 read ,一个write,完成读取和写入。

4、关闭流

2. 代码调用的流程如图所示:

iostream

3. 代码虽简单,但是有点小问题

FileInputStream.available() 是返回流中的估计剩余字节数。所以一般不会用此方法。

一般做法,比如创建一个 byte数组,大小1K。然后read至其返回值不为-1,一直读取即可。边读边写。

 

五、思考与小结

FileInputStream & FileOutputStream 是一对来自 InputStream和OutputStream的实现类。用于本地文件读写(二进制格式按顺序读写)

本文小结:

1、FileInputStream 源码分析

2、FileOutputStream 资源分析

3、FileInputStream & FileOutputStream 使用案例

4、其源码调用过程

欢迎点击我的博客及GitHub — 博客提供RSS订阅哦!

———- http://www.bysocket.com/ ————- https://github.com/JeffLi1993 ———-

Posted in Java, 技术

Java IO 之 OutputStream源码

Writer      :BYSocket(泥沙砖瓦浆木匠)

微         博:BYSocket

豆         瓣:BYSocket

FaceBook:BYSocket

Twitter    :BYSocket

一、前言

上一篇《Java IO 之 InputStream源码》,说了InputStream。JDK1.0中就有了这传统的IO字节流,也就是 InputStream 和 OutputStream。梳理下两者的核心:

InputStream中有几个 read() 方法和 OutputStream中有几个 write() 方法。它们是一一对应的,而核心的是read()和write()方法。它们都没实现,所有本质调用是各自实现类实现的该两个方法。

read() 和 write() ,对应着系统的Input和Output,即系统的输出输入。

 

二、OutputStream

也是一个抽象类,即表示所有字节输入流实现类的基类。它的作用就是抽象地表示所有要输出到的目标,例如常见的FileOutStream、FilterOutputStream等。它实现了java.io.Closeable和java.io.Flushable两个接口。其中空实现了flush方法,即拥有刷新缓存区字节数组作用。

那些输出目标呢?比如:

1) 字节数组(不代表String类,但可以转换)

2) 文件

3) 管道(多线程环境中的数据源)

等等

FilterOutputStream是为各种OutputStream实现类提供的“装饰器模式”的基类。将属性或者有用的接口与输出流连接起来。

 

三、细解OutputStream源码的核心

一样的,先看源码:

/**
 * 所有字节输出流实现类的基类
 */
public abstract class SOutputStream implements Closeable, Flushable {

    // 将指定的字节写入输出流
    public abstract void write(int b) throws IOException;

    // 将指定的byte数组的字节全部写入输出流
    public void write(byte b[]) throws IOException {
        write(b, 0, b.length);
    }

    // 将指定的byte数组中从偏移量off开始的len个字节写入输出流
    public void write(byte b[], int off, int len) throws IOException {
        if (b == null) {
            throw new NullPointerException();
        } else if ((off < 0) || (off > b.length) || (len < 0) ||
                   ((off + len) > b.length) || ((off + len) < 0)) {
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return;
        }
        for (int i = 0 ; i < len ; i++) {
            write(b[off + i]);
        }
    }

    // 刷新输出流,并强制写出所有缓冲的输出字节
    public void flush() throws IOException {
    }

    // 关闭输出流,并释放与该流有关的所有资源
    public void close() throws IOException {
    }

}

其中三个核心的write()方法,对应着三个InputStream的read()方法:

1. abstract void write(int b) 抽象方法

public abstract void write(int b) throws IOException;

对应着,InputStream的read()方法,此方法依旧是抽象方法。因为子类必须实现此方法的一个实现。这就是输入流的关键方法。

二者,下面两个write方法中调用了此核心方法。

可见,核心的是read()和write()方法在传统的IO是多么重要。

 

2.  void write(byte b[]) 方法

public void write(byte b[]) throws IOException

将指定的byte数组的字节全部写入输出流。该效果实际上是由下一个write方法实现的,只是调用的额时候指定了长度:

 

3.  void write(byte b[], int off, int len) 方法

public void write(byte b[], int off, int len) throws IOException

将指定的byte数组中从偏移量off开始的len个字节写入输出流。代码详细流程解读如下:

    a.  如果 bnull,则抛出 NullPointerException

b.  如果 off 为负,或 len 为负,或者 off+len 大于数组 b 的长度,则抛出 IndexOutOfBoundsException

c.  将数组 b 中的某些字节按顺序写入输出流;元素 b[off] 是此操作写入的第一个字节,b[off+len-1] 是此操作写入的最后一个字节。

四、小结

重要的事情说三遍:

OutputStream 解读对照着 InputStream来看!注意 一个write对应一个read

OutputStream 解读对照着 InputStream来看!注意 一个write对应一个read

OutputStream 解读对照着 InputStream来看!注意 一个write对应一个read

Posted in Java, 技术

Java IO 之 InputStream源码

 

Writer      :BYSocket(泥沙砖瓦浆木匠)

微         博:BYSocket

豆         瓣:BYSocket

FaceBook:BYSocket

Twitter    :BYSocket

一、InputStream

InputStream是一个抽象类,即表示所有字节输入流实现类的基类。它的作用就是抽象地表示所有从不同数据源产生输入的类,例如常见的FileInputStream、FilterInputStream等。那些数据源呢?比如:

1) 字节数组(不代表String类,但可以转换)

2) String对象

3) 文件

4) 一个其他种类的流组成的序列化 (在分布式系统中常见)

5) 管道(多线程环境中的数据源)

等等

二者,注意它是属于字节流部分,而不是字符流(java.io中Reader\Writer,下面会讲到)。

FilterInputStream是为各种InputStream实现类提供的“装饰器模式”的基类。因此,可以分为原始的字节流和“装饰”过的功能封装字节流。

 

二、细解InputStream源码的核心

源码如下:

/**
 * 所有字节输入流实现类的基类
 */
public abstract class SInputStream {

    // 缓存区字节数组最大值
    private static final int MAX_SKIP_BUFFER_SIZE = 2048;

    // 从输入流中读取数据的下一个字节,以int返回
    public abstract int read() throws IOException;

    // 从输入流中读取数据的一定数量字节,并存储在缓存数组b
    public int read(byte b[]) throws IOException {
        return read(b, 0, b.length);
    }

    // 从输入流中读取数据最多len个字节,并存储在缓存数组b
    public int read(byte b[], int off, int len) throws IOException {
        if (b == null) {
            throw new NullPointerException();
        } else if (off < 0 || len < 0 || len > b.length - off) {
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return 0;
        }

        int c = read();
        if (c == -1) {
            return -1;
        }
        b[off] = (byte)c;

        int i = 1;
        try {
            for (; i < len ; i++) {
                c = read();
                if (c == -1) {
                    break;
                }
                b[off + i] = (byte)c;
            }
        } catch (IOException ee) {
        }
        return i;
    }

    // 跳过输入流中数据的n个字节
    public long skip(long n) throws IOException {

        long remaining = n;
        int nr;

        if (n <= 0) {
            return 0;
        }

        int size = (int)Math.min(MAX_SKIP_BUFFER_SIZE, remaining);
        byte[] skipBuffer = new byte[size];
        while (remaining > 0) {
            nr = read(skipBuffer, 0, (int)Math.min(size, remaining));
            if (nr < 0) {
                break;
            }
            remaining -= nr;
        }

        return n - remaining;
    }

    // 返回下一个方法调用能不受阻塞地从此读取(或者跳过)的估计字节数
    public int available() throws IOException {
        return 0;
    }

    // 关闭此输入流,并释放与其关联的所有资源

    public void close() throws IOException {}

    // 在此输出流中标记当前位置
    public synchronized void mark(int readlimit) {}

    // 将此流重新定位到最后一次对此输入流调用 mark 方法时的位置。
    public synchronized void reset() throws IOException {
        throw new IOException("mark/reset not supported");
    }

    // 测试此输入流是否支持 mark 和 reset 方法
    public boolean markSupported() {
        return false;
    }

}

其中,InputStream下面三个read方法才是核心方法:

public abstract int read()

抽象方法,没有具体实现。因为子类必须实现此方法的一个实现。这就是输入流的关键方法。

二者,可见下面两个read()方法都调用了这个方法子类的实现来完成功能的。

 

public int read(byte b[])

该方法是表示从输入流中读取数据的一定数量字节,并存储在缓存字节数组b。其效果等同于调用了下面方法的实现:

 read(b, 0, b.length)

如果b的长度为 0,则不读取任何字节并返回 0;否则,尝试读取至少 1 字节。如果因为流位于文件末尾而没有可用的字节,则返回值 -1;否则,至少读取一个字节并将其存储在 b 中。

思考:这时候,怪不得很多时候, b != –1 或者 b != EOF

 

public int read(byte b[], int off, int len)


在输入数据可用、检测到流末尾或者抛出异常前,此方法一直阻塞。

该方法先进行校验,然后校验下个字节是否为空。如果校验通过后,
如下代码:

int i = 1;
try {
    for (; i < len ; i++) {
        c = read();
        if (c == -1) {
            break;
        }
        b[off + i] = (byte)c;
    }
} catch (IOException ee) {
}

将读取的第一个字节存储在元素 b[off] 中,下一个存储在 b[off+1] 中,依次类推。读取的字节数最多等于 len。设 k 为实际读取的字节数;这些字节将存储在 b[off]b[off+k-1] 的元素中,不影响 b[off+k]b[off+len-1] 的元素。

因为有上面两个read的实现,所以这里InputStream设计为抽象类。

三、小结

1. InputSream 对应着 OutputStream

2. 看源码是享受人家写代码中流露的How

3. 泥瓦匠学习的代码都在github上(同步osc git),欢迎大家点star,提意见,一起进步。地址:https://github.com/JeffLi1993

 

Posted in 清文

生来仿徨

两排路灯

穿梭的车流

一条小道

喧嚣的街道

匆匆一别

亦有两日

吾病渐安

思念成疾

无尽的车道

那个转角转弯

人生苦短

无处寻安

笑,自嘲

生来仿徨,死去何安

静,浪过度

才知心里想过的生活

那就去,罢了

– 2015-9-21

Posted in Java, 技术

图解 & 深入浅出Java初始化与清理:构造器必知必会

Writer      :BYSocket(泥沙砖瓦浆木匠)

微         博:BYSocket

豆         瓣:BYSocket

FaceBook:BYSocket

Twitter    :BYSocket

面向对象编程中,编程人员应该在意“资源”。比如

String hello = "hello";

在代码中,我们很在意在内存String类型hello,它是有一个生命周期的。在它生命周期中,初始化(initialization)清理(cleanup)是两个重要的环节。尤其是C程序中,很多bug会出现在对象初始化清理的环节。这样会造成一些程序安全问题。

《Think in Java》中说道:

随着计算机革命的发展,”不安全“的编程方式已逐渐成为编程代价高昂的主因之一。

一、栈与堆

在Java开发中,针对对象会在乎内存的两个区域:对象的生存空间是堆(heap) 方法调用及局部变量(也称变量)的生存空间栈(stack)。如图:

绘图1

 

二、什么是构造器,默认(无参)构造器

为了保证对象成功初始化,Java像C++一样引入了构造器(Constructor),即在创建对象的时候被自动调用的特殊方法。自然,Java额外提供了GC(垃圾回收器),对于不再使用的资源,垃圾回收机制会自动释放资源。Java通过类构造器创建新对象的时候,在使用此对象前,会调用其构造器进行初始化。如:

public class VoidConstructor {
    
    VoidConstructor(){
        // 构造器
    }
    
    void VoidConstructor() {
        // void 方法,不是构造器
    }
}

1、默认构造器

这里,VoidConstructor是一个类,如果需要创建一个VoidConstructor对象只要

VoidConstructor constr = new VoidConstructor();

调用上面的代码就可以通过这个默认构造器(即无参构造器),就生成一个VoidConstuctor对象。代码图解如下

绘图2

2、注意,构造器特殊方法,采取与类名同名。它没有返回值,这与返回值void方法不同。这里,void方法名命名也是不规范,应该是“每个方法首字母小写”,也一般不与类名相同。 

另外,就算你类没有写构造器的话,编译器会生成一个默认构造器。

三、带参构造器

下面跑个带参构造器的例子:

/**
 * @author Jeff Lee
 * @since 2015-9-7 16:54:19
 * 带参数简单构造器的展示
 */
public class SimpleConstructor2 {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Child2(i);
        }
    }
}

// Child类
class Child2 {
    Child2(int i) {// 带参数的Child类构造器
        System.out.print("Child init " + i + " ");
    }
}

Eclipse中,右键Run asjava application

image

上面Child2(int)是Child类的唯一构造器,此时你先在编译器中通过 new Child2()来创建对象是不行的。

所以,构造器有助于避免开发中出现代码错误

二者,构造器可以用来初始化资源。其中,对象的创建与资源的初始化是捆绑在一起的。

休息一下吧~ 看看小广告:

开源代码都在我的gitHub上哦 — https://github.com/JeffLi1993 作者留言“请手贱,点项目star,支持支持拜托拜托

四、继承中的构造器

在创建一个对象时,所以继承下来的对象都会被调用各构造器。比如说下面这个例子:

/**
 * @author Jeff Lee
 * @since 2015-9-10 08:56:18
 * 继承中的构造器的案例
 */
public class ChildConstructor extends PersonConstructor{
	ChildConstructor() {
 //super(); 
		System.out.println("Making a Child Modle...");
	}
	
	@SuppressWarnings("unused")
	public static void main(String[] args) {
		ChildConstructor childConstructor = new ChildConstructor();
	}
}

class PersonConstructor {
	PersonConstructor() {
		System.out.println("Making a Person Modle...");
	}
}

main函数运行结果如下:

image

 

1. 先分析下类UML,ChildConstructor 继承 PersonConstructor,PersonConstructor 继承 Object:

 

绘图3

2. super关键字,super用来调用父类的构造器。如果子类中没有super关键字,编译器会自动添加默认super()方法。因此,子类通过super()方法调用父类构造器。

3. 然后main函数中,通过new指令,启动了ChildConstructor对象的创建。子类 ChildConstructor 构造器在执行时,第一件事通过super()调用父类,这又会连锁反应到Objerct类。所以,打印控制台先输出 “父类先初始化”。

这过程就是构造器链Constructor Chaining),即Child对象  is-a Person对象同时也 is-a Object对象。如果创建Child对象,也同时创建了Person对象和Object对象的部分。

4. 过程流程图详解(图中是各对象构造器在堆栈块中调用流程 图中引用两字有歧义):

绘图4

五、总结

 

构造器虽小,关键还是非常关键的。

本文小结:

1、默认构造器

2、有参构造器

3、继承中的构造器

还有第一点的知识基础补充

 

欢迎点击我的博客及GitHub — 博客提供RSS订阅哦!

———- http://www.bysocket.com/ ————- https://github.com/JeffLi1993 ———-

Posted in Java, 技术

[好文要转要收藏] UML类图几种关系的总结

在UML类图中,常见的有以下几种关系:泛化(Generalization),  实现(Realization),关联(Association),聚合(Aggregation),组合(Composition),依赖(Dependency)

1.泛化(Generalization)

【泛化关系】:是一种继承关系,它指定了子类如何特化父类的所有特征和行为例如:老虎是动物的一种.

【箭头指向】:带三角箭头的实线,箭头指向父类

2.实现(Realization)

【实现关系】:是一种类与接口的关系,表示类是接口所有特征和行为的实现

【箭头指向】:带三角箭头的虚线,箭头指向接口

3.关联(Association)

关联关系】:是一种拥有的关系,它使一个类知道另一个类的属性和方法;如:老师与学生,丈夫与妻子

关联可以是双向的,也可以是单向的。双向的关联可以有两个箭头或者没有箭头,单向的关联有一个箭头。

【代码体现】:成员变量

【箭头及指向】:带普通箭头的实心线,指向被拥有者

上图中,老师与学生是双向关联,老师有多名学生,学生也可能有多名老师。但学生与某课程间的关系为单向关联,一名学生可能要上多门课程,课程是个抽象的东西他不拥有学生。

上图为自身关联:

4. 聚合(Aggregation)

【聚合关系】:是整体与部分的关系.如车和轮胎是整体和部分的关系.

聚合关系是关联关系的一种,是强的关联关系;关联和聚合在语法上无法区分,必须考察具体的逻辑关系。

【代码体现】:成员变量

【箭头及指向】:带空心菱形的实心线,菱形指向整体

5. 组合(Composition)

【组合关系】:是整体与部分的关系.,没有公司就不存在部门 组合关系是关联关系的一种,是比聚合关系还要强的关系,它要求普通的聚合关系中代表整体的对象负责代表部分的对象的生命周期

【代码体现】:成员变量

【箭头及指向】:带实心菱形的实线,菱形指向整体

6. 依赖(Dependency)

【依赖关系】:是一种使用的关系,所以要尽量不使用双向的互相依赖。

【代码表现】:局部变量、方法的参数或者对静态方法的调用

【箭头及指向】:带箭头的虚线,指向被使用者

各种关系的强弱顺序:

泛化= 实现> 组合> 聚合> 关联> 依赖

下面这张UML图,比较形象地展示了各种类图关系:

原文地址:http://blog.csdn.net/tianhai110/article/details/6339565

 

思考

在Java 各框架源码中,会涉及到各种

继承 = 实现> 组合> 聚合> 关联> 依赖

黑体字为常用

Posted in Java, 技术

图解 & 深入浅出 JavaWeb:Servlet 再说几句

Writer      :BYSocket(泥沙砖瓦浆木匠)

微         博:BYSocket

豆         瓣:BYSocket

FaceBook:BYSocket

Twitter    :BYSocket

上一篇的《 Servlet必会必知 》受到大家一致好评 — (感谢 读者 及 OSC 推荐 每日一’搏’) image

后来觉得还有些东西没点到,这边补充补充。

一、回到 HttpServlet 的 service方法

Servlet 基础接口定义了用于客户端请求处理的service方法。 当请求到达Servlet容器,由Servlet容器路由到一个Servlet实例

比如说 javax.servlet.http.HttpServlet 类 ,其中有一个 protected void service 方法如下:

private static final String METHOD_DELETE = "DELETE";
private static final String METHOD_HEAD = "HEAD";
private static final String METHOD_GET = "GET";
private static final String METHOD_OPTIONS = "OPTIONS";
private static final String METHOD_POST = "POST";
private static final String METHOD_PUT = "PUT";
private static final String METHOD_TRACE = "TRACE";

private static final String HEADER_IFMODSINCE = "If-Modified-Since";

private static final String LSTRING_FILE =
        "javax.servlet.http.LocalStrings";
private static ResourceBundle lStrings =
        ResourceBundle.getBundle(LSTRING_FILE);
/**
 * HTTP状态码304
 */
public static final int SC_NOT_MODIFIED = 304;

/**
 * 接收来自 public service方法的标准HTTP请求,
 * 并将它们分发给此类中定义的doXXX方法。
 */
protected void service(HttpServletRequest req, HttpServletResponse resp) 
		throws ServletException, IOException {
	// 获取请求方法名
	String method = req.getMethod();
	// 如果是GET请求
	if (method.equals(METHOD_GET)) {
		// 上一次修改HttpServletRequest对象的时间
		long lastModified = getLastModified(req);
		// 没有改变
		if (lastModified == -1) {
			doGet(req, resp);
		} else {
			long ifModifiedSince;
			try {
				// 获取请求头中服务器修改时间
				ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
			} catch (IllegalArgumentException iae) {
				// 获取无效
				ifModifiedSince = -1;
			}
			// 如果请求头服务器修改时间迟
			if (ifModifiedSince < (lastModified / 1000 * 1000)) {
				// 设置修改HttpServletResponse对象的时间,重新设置浏览器的参数
                              //maybeSetLastModified(resp, lastModified);
				// 调用doGet方法
                                doGet(req, resp);
			} else {
				// 304 HTTP状态码
				resp.setStatus(SC_NOT_MODIFIED);
			}
		}
	} else if (method.equals(METHOD_HEAD)) {
        long lastModified = getLastModified(req);
      //maybeSetLastModified(resp, lastModified);
        doHead(req, resp);
    } else if (method.equals(METHOD_POST)) {
        doPost(req, resp);
    } else if (method.equals(METHOD_PUT)) {
        doPut(req, resp);
    } else if (method.equals(METHOD_DELETE)) {
        doDelete(req, resp);
    } else if (method.equals(METHOD_OPTIONS)) {
        doOptions(req,resp);
    } else if (method.equals(METHOD_TRACE)) {
        doTrace(req,resp);
    } else {
        // 如果没有被请求到的话
        String errMsg = lStrings.getString("http.method_not_implemented");
        Object[] errArgs = new Object[1];
        errArgs[0] = method;
        errMsg = MessageFormat.format(errMsg, errArgs);
        // 501 HTTP状态码
        resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
    }
}

代码逻辑详解如下:

1、HttpServletprotected void service方法 用于接受 public service接收的标准HTTP请求

也就是说,HttpServlet 重写了父类 GenericServletservice方法。如图显示的是该方法,将从容器获取的 ServletRequestServletResponse 对象强制转化成 用于HTTP处理的 HttpServletRequestHttpServletResponse 对象。然后将两个对象路由给了 HttpServletprotected void service方法(图中代码选中处)

image

2、然后根据请求的方法名,分发到此类定义的doXXX方法。如果没有被请求到的话,则返回501 HTTP 状态码。

这样子仿佛明白了什么,也就是说,如果你在 HelloServlet重写doGet方法,这里分发到就是HttpServlet的子类HelloServlet的doGet方法。

哦~ 还有,501 HTTP 状态码未实现(Not implemented)表示服务器不支持实现请求所需要的功能。例如,客户发出了一个服务器不支持的PUT请求。原来如此,所谓死记硬背这些HTTP 状态码有什么用?这样的记忆才是最有效的。

休息休息,小广告插一下 :(维持生计,O(∩_∩)O~)

涉及到的代码都会在开源项目 servlet-core-learning简介 — Servlet/JSP学习积累的例子,是Java EE初学者及Servlet/JSP核心技术巩固的最佳实践

大致就是这两步骤。这就是service的工作流程

1、接受 public service接收的标准HTTP请求。

2、分发到定义的doXXX方法

二、GET 请求的处理详解

上面对于GET请求代码处理如下:

// 如果是GET请求
if (method.equals(METHOD_GET)) {
	// 上一次修改HttpServletRequest对象的时间
	long lastModified = getLastModified(req);
	// 没有改变
	if (lastModified == -1) {
		doGet(req, resp);
	} else {
		long ifModifiedSince;
		try {
			// 获取请求头中服务器修改时间
			ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
		} catch (IllegalArgumentException iae) {
			// 获取无效
			ifModifiedSince = -1;
		}
		// 如果请求头服务器修改时间迟
		if (ifModifiedSince < (lastModified / 1000 * 1000)) {
			// 设置修改HttpServletResponse对象的时间,重新设置浏览器的参数
          //maybeSetLastModified(resp, lastModified);
			// 调用doGet方法
            doGet(req, resp);
		} else {
			// 304 HTTP状态码
			resp.setStatus(SC_NOT_MODIFIED);
		}
	}
} 

这里,

1、定义了 getLastModified(req) 方法。用于获取上一次修改HttpServletRequest对象的时间。如果lastModified为默认–1L,则总是刷新

这个getLastModified,是HttpServlet定义了用于支持有条件GET操作。即当客户端通过GET请求获取资源时,当资源自第一次获取那个实际点发生更改后才再次发生数据,否则将使用客户端缓存的数据。

在一些适当的场合,实现此方法可以更有效的利用网络资源,减少不必要的数据发送。

2、如果getLastModified方法的返回值是一个正数,那就要分以下两种情况考虑:

    (1)如果请求头没有包含If-Modified-Since头字段(应该是第一次访问资源时候) 或者 其getLastModified返回值比If-Modified-Since头字段指定时间,则调用doGet返回生成 response 设置Last-Modified 消息头

    (2)如果其getLastModified返回值比If-Modified-Since头字段指定时间,则返回一个304状态给客户端,表示让客户端继续使用以前缓存的页面

比如说 304 这个场景我在《 JavaEE 要懂的小事:一、图解Http协议 》文章中提到,第一次访问 百度 首页时,有些资源会成功获取 返回200再次F5,有些资源或直接调用客户端的缓存数据,则返回304

image1_thumb

三、Servlet线程问题

Servlet容器可以并发路由多个请求到 Servlet 的 service方法。为了处理这些请求,Servlet必须在并发及线程安全问题做好处理。上一篇的《 Servlet必会必知 》提到定义全局变量会造成线程安全问题。在开发Servlet时,考虑线程安全问题提出了一下解决

1、实现 SingleThreadModel 接口

    Servlet2.4 已经提出不提倡使用。实现此接口,Servlet容器为每个新的请求创建一个单独的Servlet实例。这会有严重性能问题

2、同步锁

    使用synchronized关键字,虽然可以保证只有一个线程可以访问被保护区段,已达到保证线程安全。但是系统性能及并发量大大降低。不可取~

3、避免使用实例变量,即Servlet中全局变量。使用局部变量 (推荐)

    方法中的局部变量分配在空间,每个线程有私有的栈空间。因此访问是线程安全的。

我想到了以下一个问题:

既然Sevlet的全局变量是线程不安全的,那SpringMVC Controller 也一样。那我们在Controller定义个 XXXService 变量会不会造成线程安全呢?
答:因为这是Spring的一个Service Bean,是线程安全的,所以可以作为单例使用,不会造成线程安全。

四、总结(别忘了点赞哦)

补充文章内容要点:

HttpServlet service 方法详解

深入理解 代码 对HTTP状态码的运用

Servlet的线程安全问题

欢迎点击我的博客及GitHub — 博客提供RSS订阅哦

———- http://www.bysocket.com/ ————- https://github.com/JeffLi1993 ———-

Posted in Working Skills, 技术

Eclipse我常用快捷键

ctrl+shift+r:打开资源

这可能是所有快捷键组合中最省时间的了。节约查找文件的时间,比如你看到报错是 某DAO层报错了,这样就方便。

Control-Shift-T: 打开类型(Open type)。如果你不是有意磨洋工,还是忘记通过源码树(source tree)打开的方式吧。用eclipse很容易打开接口的实现类的,按ctrl+t会列出接口的实现类列表

ctrl+o:快速outline

如果想要查看当前类的方法或某个特定方法,但又不想把代码拉上拉下,也不想使用查找功能的话,就用ctrl+o吧。它可以列出当前类中的所有方法及属性,你只需输入你想要查询的方法名,点击enter就能够直接跳转至你想去的位置。

ctrl+e:快速转换编辑器

这组快捷键将帮助你在打开的编辑器之间浏览。使用ctrl+page downctrl+page up可以浏览前后的选项卡,但是在很多文件打开的状态下,ctrl+e会更加有效率。

shift+enter及ctrl+shift+enter

Shift+enter当前行之下创建一个空白行,与光标是否在行末无关。ctrl+shift+enter则在当前行之前插入空白行。

Alt+方向键

这也是个节省时间的法宝。这个组合将当前行的内容往上或下移动。在try/catch部分,这个快捷方式尤其好使。

ctrl+m

大显示屏幕能够提高工作效率是大家都知道的。Ctrl+m是编辑器窗口最大化的快捷键。

ctrl+.及ctrl+1:下一个错误及快速修改

ctrl+.将光标移动至当前文件中的下一个报错处或警告处。这组快捷键我一般与ctrl+1一并使用,即修改建议的快捷键。新版Eclipse的 修改建议做的很不错,可以帮你解决很多问题,如方法中的缺失参数,throw/catch exception,未执行的方法等等。