JAVA基础知识之FileOutputStream流

一、FileOutputStream流

         FileOutputStream流是指文件字节输出流,专用于输出原始字节流如图像数据等,其继承OutputStream类,拥有输出流的基本特性

public class FileOutputStream extends OutputStream{}

二、构造方法

1)创建FileOutputStream流以写入数据到File对象所代表的文件,同时创建一个新的FileDescriptor对象来表示与该文件的关联(源码中会new一个该对象)

public FileOutputStream(File file) throws FileNotFoundException{}

   查看底层源码发现该构造方法实际是调用了另一个构造方法

public FileOutputStream(File file) throws FileNotFoundException {
        this(file, false);
    }

若文件存在,但是是目录而不是文件,则会抛出FileNotFoundException异常

public class FileStream
{

    
    /**
     * File对象所代表的文件是目录,因此会抛异常
     * @param args
     */
    public static void main(String[] args)
    {
        //建立文件对象
        File file=new File("C:\\Users\\Administrator\\Desktop"); 
   
        try
        {
            FileOutputStream out=new FileOutputStream(file);

        }

        catch (FileNotFoundException e)
        {
          
           System.out.println("文件不存在或者文件不可读或者文件是目录");
        }
        catch (IOException e)
        {
           System.out.println("读取过程存在异常");
        } 
    }

}

    若不存在且无法创建,则会抛出FileNotFoundException异常----此处存疑,不存在可理解,无法创建无法理解,什么情况会导致无法创建?

   若因为其他原因无法打开(如设置了不可写),则会抛出FileNotFoundException异常

public class FileStream
{

    
    /**
     * 文件不可写,导致报错
     * @param args
     */
    public static void main(String[] args)
    {
        //建立文件对象
        File file=new File("C:\\Users\\Administrator\\Desktop\\2.txt"); 
        
        file.setWritable(false); //设置不可写
   
        try
        {
            FileOutputStream out=new FileOutputStream(file);
            
        }

        catch (FileNotFoundException e)
        {
          
           System.out.println("文件不存在或者文件不可读或者文件是目录");
        }
        catch (IOException e)
        {
           System.out.println("读取过程存在异常");
        } 
    }

}

2)创建FileOutputStream流以写入数据到File对象表示的文件。 如果第二个参数为true,则字节将写入文件的末尾而不是开头。 创建一个新的FileDescriptor对象来表示此文件连接。其抛异常的规则与第一个构造函数一致

public FileOutputStream(File file,boolean append) throws FileNotFoundException{}

        若第二个参数为真,则意味着会写入字节到文件的末尾,意味着追加内容,若为假,则是写入字节到文件的开头,意味着是覆盖,如现在2.txt中的内容是123456

        当参数为真时,程序运行后文本内容是123456789,所以是追加内容

public class FileStream
{

    
    /**
     *构造函数第二个参数为真,意味着追加内容到末尾
     * @param args
     */
    public static void main(String[] args)
    {
        //建立文件对象
        File file=new File("C:\\Users\\Administrator\\Desktop\\2.txt"); 
        
   
        try
        {
            String content="789";
            FileOutputStream out=new FileOutputStream(file,true);
            out.write(content.getBytes());
            
        }

        catch (FileNotFoundException e)
        {
          
           System.out.println("文件不存在或者文件不可读或者文件是目录");
        }
        catch (IOException e)
        {
           System.out.println("读取过程存在异常");
        } 
    }

}

     当第二个参数为false时,程序运行后内容是789,因此是覆盖内容

public class FileStream
{

    
    /**
     *构造函数第二个参数为假,意味着覆盖文件的原本内容
     * @param args
     */
    public static void main(String[] args)
    {
        //建立文件对象
        File file=new File("C:\\Users\\Administrator\\Desktop\\2.txt"); 
        
   
        try
        {
            String content="789";
            FileOutputStream out=new FileOutputStream(file,false);
            out.write(content.getBytes());
            
        }

        catch (FileNotFoundException e)
        {
          
           System.out.println("文件不存在或者文件不可读或者文件是目录");
        }
        catch (IOException e)
        {
           System.out.println("读取过程存在异常");
        } 
    }

}

3)创建FileOutputStream流以写入数据到指定路径所代表的文件,同时创建一个新的FileDescriptor对象来表示与该文件的关联(源码中会new一个该对象)

public FileOutputStream(String name) throws FileNotFoundException{}

   查看源码,其本质就是调用了FileOutputStream(File file, boolean append){}方法,因此规则都和上述构造方法一致

public FileOutputStream(String name) throws FileNotFoundException {
        this(name != null ? new File(name) : null, false);
    }

4)创建FileOutputStream流以写入数据到指定路径所代表的文件,同时创建一个新的FileDescriptor对象来表示与该文件的关联(源码中会new一个该对象), 如果第二个参数为true,则字节将写入文件的末尾而不是开头

public FileOutputStream(String name,boolean append) throws FileNotFoundException

    查看源码,其本质是调用了FileOutputStream(File file, boolean append){}方法,因此规则都和上述构造方法一致

    public FileOutputStream(String name, boolean append)
        throws FileNotFoundException
    {
        this(name != null ? new File(name) : null, append);
    }

4)因此虽然有4个构造方法,但是究其本质发现都是调用了下面这个构造方法,因此后续使用FileOutputStream流时,直接使用该构造方法即可

 public FileOutputStream(File file, boolean append)
        throws FileNotFoundException
    {
    }

三、FileOutputStream流构造方法的特殊之处

        之前提到过FileIntputStream流创建时若文件不存在就会报FileNotFoundException异常,但是我们发现FileOutputStream流中的构造方法说明提到的是不存在且无法创建才会报FileNotFoundException异常。也就是意味着若不存在但可创建的情况下是不会有异常产生的

      因此FileOutputStream流的构造方法可以用于生成系统文件

  如现在桌面上没有3.txt这个文件,但是经过程序运行后,会生成一个3.txt文件,并且输入了字节内容到其中

public class FileStream
{

    
    /**
     *  构造函数可以用于生成文件
     * @param args
     */
    public static void main(String[] args)
    {
        //建立文件对象
        File file=new File("C:\\Users\\Administrator\\Desktop\\3.txt"); 
        
   
        try
        {
            String content="abcdefg";
            
            FileOutputStream out=new FileOutputStream(file,false);
            
            out.write(content.getBytes());
            
        }

        catch (FileNotFoundException e)
        {
          
           System.out.println("文件不存在或者文件不可读或者文件是目录");
        }
        catch (IOException e)
        {
           System.out.println("读取过程存在异常");
        } 
    }

}

四、FileOutputStream流的的常用API

1)将指定的一个字节写入文件的输出流中,所以是一次写入一个字节

public void write(int b) throws IOException

     此处存疑--注意参数是int型不是byte型的.这个跟输入流读取数据时读取的是字节但是返回的是int型一样存疑--涉及到字节的位数问题了

write(int n)方法实例:需要先将要写入的内容转成字节数组然后再进行循环多次写入才可以

public class FileStream
{

    
    /**
     *  write方法
     * @param args
     */
    public static void main(String[] args)
    {
        //建立文件对象
        File file=new File("C:\\Users\\Administrator\\Desktop\\4.txt"); 
        
   
        try
        {
            String content="abcdefg";
            
            FileOutputStream out=new FileOutputStream(file,false);
            
            byte[] b=content.getBytes();
            
            System.out.println("要写入的字节数据长度:"+b.length);
                    
            for (int i = 0; i < b.length; i++)
            {

                out.write(b[i]); //此处等于是进行了向上转换,即byte类型的转换成了int类型然后调用方法,向上装换不需要强转标识
                
                System.out.println("写入次数:"+(i+1)); 
            }
 
        }

        catch (FileNotFoundException e)
        {
          
           System.out.println("文件不存在或者文件不可读或者文件是目录");
        }
        catch (IOException e)
        {
           System.out.println("读取过程存在异常");
        } 
    }

}

2)将指定字节数组中的b.length个字节写入到输出流中

public void write(byte[] b) throws IOException {}

         指定字节数组是指含有要写入数据的字节数组作为参数传入,查看源码发现其本质是调用了另一个API方法

 public void write(byte b[]) throws IOException {
        writeBytes(b, 0, b.length, append);
    }

write(byte[] b)方法实例:本质是调用其他方法执行的 ,注意参数append是构造函数中的第二个参数,默认为false

public class FileStream
{

    
    /**
     *  write(byte【】 b)方法
     * @param args
     */
    public static void main(String[] args)
    {
        //建立文件对象
        File file=new File("C:\\Users\\Administrator\\Desktop\\4.txt"); 
        
   
        try
        {
            String content="abcdefg";
            
            FileOutputStream out=new FileOutputStream(file,false);
            
            out.write(content.getBytes()); 
         
        }

        catch (FileNotFoundException e)
        {
          
           System.out.println("文件不存在或者文件不可读或者文件是目录");
        }
        catch (IOException e)
        {
           System.out.println("读取过程存在异常");
        } 
    }

}

3)将从偏移量off开始的指定字节数组中的len个字节写入输出流中

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

   参数b代表着含有要写入数据的字节数组,参数off代表着从数组下标off开始,参数len表示最终写入的字节个数

   如write(bytes,0,5)则意味着从字节数组bytes中下标0开始读5个字节到输出流中

  查看源码发现其调用的是另外一个native方法,前面提过native方法是调用的其它语言的方法,无法查看实现

    public void write(byte b[], int off, int len) throws IOException {
        writeBytes(b, off, len, append);
    }
    
    private native void writeBytes(byte b[], int off, int len, boolean append)
        throws IOException;

但是我们根据其父类OutputStream类中参考下其类似的方法来理解

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

  可以看出最终的本质还是write(byte b[], int off, int len)方法等于多次调用write(int n)方法

  而根据上面的write(byte b[])方法本质是调用writeBytes(b, 0, b.length, append)方法的

 
write(byte b[], int off, int len)方法实例 : 相比write(byte b[])方法等于是灵活控制了要输入的内容

public class FileStream
{

    
    /**
     *  write(byte b[], int off, int len)方法
     * @param args
     */
    public static void main(String[] args)
    {
        //建立文件对象
        File file=new File("C:\\Users\\Administrator\\Desktop\\4.txt"); 
        
   
        try
        {
            String content="abcdefg";
            
            FileOutputStream out=new FileOutputStream(file,false);
            
            byte[] bytes=content.getBytes(); //得到装有内容的字节数组
            
            out.write(bytes,1,4);  //代表我只想要从下标1开始的4个字节,即bcde
         
        }

        catch (FileNotFoundException e)
        {
          
           System.out.println("文件不存在或者文件不可读或者文件是目录");
        }
        catch (IOException e)
        {
           System.out.println("读取过程存在异常");
        } 
    }

}

4)关闭输出流并释放与此流关联的所有系统资源。输出流可能不再用于写入字节。

public void close() throws IOException{}

     查看源码如下:其中有同步锁关键词,个人理解为若写完资源后不进行关闭,则不会释放锁,那么其他地方也无法对该文件资源进行其他的读写操作

  public void close() throws IOException {
        synchronized (closeLock) {
            if (closed) {
                return;
            }
            closed = true;
        }

        if (channel != null) {
            /*
             * Decrement FD use count associated with the channel
             * The use count is incremented whenever a new channel
             * is obtained from this stream.
             */
            fd.decrementAndGetUseCount();
            channel.close();
        }

        /*
         * Decrement FD use count associated with this stream
         */
        int useCount = fd.decrementAndGetUseCount();

        /*
         * If FileDescriptor is still in use by another stream, the finalizer
         * will not close it.
         */
        if ((useCount <= 0) || !isRunningFinalize()) {
            close0();
        }
    }

五、三种write方法对比

         1、通过上述实例代码其实可以发现使用write(int n)是需要传递单字节作为参数,但是一般情况我们都是接收的字节数组,与其使用字节数组进行循环调用还不如使用write(byte[] b)方法,直接把内容转化成字节数组作为参数调用即可

         2、 三者之间写数据的效率,根据源代码可以看出,虽然具体的实现方法是native修饰无法查看,但是根据父类方法对比发现三者实质都是通过for循环进行的单字节写入,所以认定三者写数据的效率差不多一样

         3、因此使用FileOutputStream流写数据时一般使用第二种和第三种write方法即可

 

你可能感兴趣的:(JAVA基础知识之FileOutputStream流)