1. 程式人生 > >java IO FileInputStream與FileOutputStream

java IO FileInputStream與FileOutputStream

FileInputStream :用於讀取原始位元組流,入影象資料。如果讀取字元流,請考慮使用FileReader

public class FileInputStream extends InputStream 

繼承了靜態類 InputStream

public abstract class InputStream implements Closeable {

    private static final int MAX_SKIP_BUFFER_SIZE = 2048;

    public abstract int read() throws IOException;

    public int read(byte b[]) throws IOException {
        return read(b, 0, b.length);
    }

    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;
    }
    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) {}

    public synchronized void reset() throws IOException {
        throw new IOException("mark/reset not supported");
    }

    public boolean markSupported() {
        return false;
    }

}

這也說明了靜態類不一定只有靜態方法 (笑)

構造方法有

//輸入相關的檔名
//如果檔案不存在,是一個目錄而不是一個正常的檔案,亦或是因為某些原因檔案不能被開啟,就會丟擲異常
//return :建立一一個被開啟檔案的位元組流
public FileInputStream(String name) throws FileNotFoundException {
        this(name != null ? new File(name) : null); 
    } 
//輸入檔案
//異常同上
//return :建立一一個被開啟檔案的位元組流
 public FileInputStream(File file) throws FileNotFoundException {
        String name = (file != null ? file.getPath() : null); //獲取檔案的路徑
        SecurityManager security = System.getSecurityManager();//啟動java安全管理器
        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.attach(this); //關聯FileInputStream例項與FileDescriptor例項
        path = name;
        open(name); //開啟檔案進行讀取
    }

public FileInputStream(FileDescriptor fdObj) {
        SecurityManager security = System.getSecurityManager();
        if (fdObj == null) {
            throw new NullPointerException();
        }
        if (security != null) {
            security.checkRead(fdObj);
        }
        fd = fdObj;
        path = null;

        /*
         * FileDescriptor is being shared by streams.
         * Register this stream with FileDescriptor tracker.
         */
        fd.attach(this);
    }
//讀取b.length位元組的資料流入一個位元組陣列 b
//byte b[] 就是一個緩衝區
//return : 每一次讀入緩衝區的總位元組數,如果到達結尾後,返回-1
 public int read(byte b[]) throws IOException {
        return readBytes(b, 0, b.length);
    }
// off b的起始偏移量
// len 每一次讀取多少
// 每一次讀入緩衝區的總位元組數,如果到達結尾後,返回-1
public int read(byte b[], int off, int len) throws IOException {
        return readBytes(b, off, len);
    }

 //native  表示呼叫的方法在外部定義
 private native int readBytes(byte b[], int off, int len) throws IOException;
@Test
    public void ioTest() throws IOException {
		InputStream inputStream = new FileInputStream(new File("C:\\file\\hello.txt"));
		byte[] b = new byte[4];
		int len = 0;
		while((len = inputStream.read(b))!= -1) {
			System.out.println(len);
		}
    }

// 4 4 4 4 2 每一次讀取4個 

//如果改成這樣,則會丟擲異常
while((len = inputStream.read(b,1,4))!= -1) { 
			System.out.println(len);
		}

//如果這樣的話,每一次讀取兩個
while((len = inputStream.read(b,1,2))!= -1) {
			System.out.println(len);
		}
// 2 2 2 2 2 2 2 2 2

//返回可以被此輸入流讀取的剩餘位元組數的估計值
public native int available() throws IOException;
//跳過並丟失流中的n節位元組資料
public native long skip(long n) throws IOException;

 @Test
    public void ioTest() throws IOException {
		InputStream inputStream = new FileInputStream(new File("C:\\file\\hello.txt"));
		System.out.println(inputStream.available());
		inputStream.skip(3);
		System.out.println(inputStream.available());
    }

// 18 15

下面是關於FileOutputStream

是一個將資料寫入檔案的的輸出流

public class FileOutputStream extends OutputStream

下面是它的建構函式

//建立一個寫入指定檔案的輸出流
//如果檔案是一個目錄,或不存在但不能被建立,或者是因為某些原因不能被開啟,則丟擲異常
//如果有安全管理,那麼需要check檔案是否允許被寫入


//name : 檔案的路徑+檔案的名字+字尾
//可以清楚的看見,這個建構函式使用了File
public FileOutputStream(String name) throws FileNotFoundException {
        this(name != null ? new File(name) : null, false);
    }

//append: 如果為true,將寫入檔案的末尾而不是開頭
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();
        fd.attach(this);
        this.append = append;
        this.path = name;

        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.append = false;
        this.path = null;

        fd.attach(this);
    }

FileDescriptor 是“檔案描述符”。
FileDescriptor 可以被用來表示開放檔案、開放套接字等。
以FileDescriptor表示檔案來說:當FileDescriptor表示某檔案時,我們可以通俗的將FileDescriptor看成是該檔案。但是,我們不能直接通過FileDescriptor對該檔案進行操作;若需要通過FileDescriptor對該檔案進行操作,則需要新建立FileDescriptor對應的FileOutputStream,再對檔案進行操作。

//將一個位元組寫入輸出流中
public void write(int b) throws IOException {
        write(b, append);
    }

//從指定的b中獲取b.length個位元組寫入輸出流中
public void write(byte b[]) throws IOException {
        writeBytes(b, 0, b.length, append);
    }
//從指定的b中偏移量off快開始,獲取len個位元組寫入輸出流中
public void write(byte b[], int off, int len) throws IOException {
        writeBytes(b, off, len, append);
    }
@Test
    public void ioTest() throws IOException {
		InputStream inputStream = new FileInputStream("C:\\file\\hello.txt");
		OutputStream outputStream = new FileOutputStream("C:\\file\\hello1.txt",true);
		byte[] b = new byte[3];
		int len = 0;
		while((len = inputStream.read(b))!= -1) {
			outputStream.write(b);
		}
		outputStream.close();
		inputStream.close();
		
    }

但這樣有時候會出問題,就是當你每次讀取的位元組數不能被問檔案的總位元組數整除的時候,就會多寫入一些位元組

根據輸出的結果,我推測是因為每一次使用read的時候,並不是一次性覆蓋b。而是一個一個的覆蓋,先覆蓋第一個,然後第二個。。。等到檔案結尾的時候,就不覆蓋了。所以,容易寫入上一次沒有覆的位元組

解決方法之一,還是用這些 

byte[] b = new byte[inputStream.available()];