Java-基础篇-IO流

一、概念

        "IO流"(Input/Output stream)指的是Java中用于处理输入输出(I/O)数据的机制。在Java中,所有的输入和输出都被抽象为“流”对象,并通过输入流读取数据、输出流写入数据。

        Java的I/O包提供了丰富的类和方法来支持不同类型的流,输入流和输出流之间可以自由地进行转换。它们分别主要包括字节流和字符流两种类型。其中,字节流是操作二进制数据的流,可以处理任何类型的数据,常见的例如InputStream和OutputStream;字符流则是处理字符数据的流,其主要使用Reader和Writer接口。

        Java中的IO流可以对文件、网络套接字等的数据进行读写操作,这些操作涉及到打开、关闭、查找、修改和删除文件、创建及维护网络连接等等一系列活动。因此,掌握Java中IO流的知识,有助于我们实现更加高效准确地操作文件、网络数据等各种实用应用。

        IO流的作用:就是可以对文件或者网络中的数据进行读、写的操作。

  • 把数据从磁盘、网络中读取到程序中来,用到的是输入流。
  • 把程序中的数据写入磁盘、网络中,用到的是输出流。
  • 简单记:输入流(读数据)、输出流(写数据)

1.流的分类

        可以从三个不同的维度进行分类:

  • 按照流的操作粒度划分
    • 字节流:以字节为单元,可操作任何数据【主要由InputStream和outPutStream作为基类】

    • 字符流:以字符为单元,只能 操作纯字符数据,比较方便【主要由Reader和Writer作为基类】

  • 按照流的方向划分
    • 输入流:只能从中读取数据【主要由InputStream和Reader作为基类】

    • 输出流:只能向其写入数据【主要由outputStream和Writer作为基类】

  • 按照流的角色划分
    • 节点流:可以从/向一个特定的IO设备(如磁盘,网络)读/写数据的流,也叫【低级流】

    • 处理流:用于对一个已存在的流进行连接和封装,通过封装后的流来实现数据的读/写功能,也叫【高级流】

2.理解流

         “流”是一个抽象的概念,它是对输入输出设备的一种抽象理解,在java中,对数据的输入输出操作都是以“流”的方式进行的。“流”具有方向性,输入流、输出流是相对的。当程序需要从数据源中读入数据的时候就会开启一个输入流,相反,写出数据到某个数据源目的地的时候也会开启一个输出流。数据源可以是文件、内存或者网络等。

二、Stream

1.概念

        Stream是Java 8中引入的全新API,可以极大地方便我们对集合、数组等数据源进行连续操作。它可以简化我们的代码,使代码更加易于维护和理解。Stream实际上是一种惰性计算的方式,只有需要输出结果时,才会开始计算。

        惰性计算:

        Stream只是对于原有数据的操作方式,数据本身并没有改变。因此,在对Stream进行操作时,实际上是将操作指令存储在操作流中并未计算执行,直到需要输出结果时才会被触发。这种方式可以减少计算量和开销,提高效率。

2.常用API

        常用的Stream操作包括:过滤、映射、排序、去重、计数、归约等等。

filter

        过滤方法filter用于对Stream中的元素进行筛选,只保留符合指定条件的元素。其函数式接口为Predicate<T>,其方法为boolean test(T t),接受一个T类型的对象,并返回一个boolean类型值。当该方法返回true时,说明该元素符合条件,将被保留在Stream中。

List<String> list = Arrays.asList("apple", "banana", "orange", "pear");
Stream<String> stream = list.stream().filter(s -> s.length() > 5);

map

        映射方法map用于将Stream中的元素根据指定规则进行转换。其函数式接口为Function<T, R>,其方法为R apply(T t),接受一个T类型的对象,并返回一个R类型的对象。实质上,map方法就是对Stream中各元素做一个同类型的映射。

List<String> list = Arrays.asList("apple", "banana", "orange", "pear");
Stream<Integer> stream = list.stream().map(String::length);

sorted

        排序方法sorted用于对Stream中的元素进行排序。其函数式接口为Comparator<T>,其方法为int compare(T o1, T o2),接受两个T类型的对象,并返回一个int类型值。当返回值为负数时,说明o1应排在o2前面;当返回值为正数时,说明o1应排在o2后面;当返回值为0时,说明o1与o2的顺序不确定。

List<String> list = Arrays.asList("apple", "banana", "orange", "pear");
Stream<String> stream = list.stream().sorted();

distinct

        去重方法distinct用于将Stream中的重复元素去除,只保留一个。其使用equals方法进行比较,因此需要保证数据源中的元素正确实现了equals方法。

List<String> list = Arrays.asList("apple", "banana", "orange", "banana");
Stream<String> stream = list.stream().distinct();

count

        计数方法count用于返回Stream中元素的数量,返回值为long类型。

List<String> list = Arrays.asList("apple", "banana", "orange", "pear");
long count = list.stream().count();

reduce

        归约方法reduce用于将Stream中的元素归约成一个值。其函数式接口为BinaryOperator<T>,其方法为apply(T t1, T t2),用于对两个T类型值进行归约,返回一个T类型值。reduce方法接受两个参数:第一个参数表示归约操作的初始值,可以为任意类型的对象;第二个参数为一个BinaryOperator类型的对象,用于对Stream中所有元素递归地进行归约操作。

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
int sum = list.stream().reduce(0, Integer::sum);

forEach

        forEach方法用于对Stream中的每个元素执行指定的操作,其函数式接口为Consumer<T>,其方法为void accept(T t)forEach是一个终端操作,对于同一个Stream只能进行一次,一旦执行了终端操作,该Stream就不能再重复使用了。

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
list.stream().forEach(System.out::println);

三、File

1.概念

        File,即文件、文件夹,一个File对象表示了磁盘上的某个文件或文件夹。在java中,File类实质是以文件或者文件夹的路径来操作的。File 类是 java.io 包中唯一代表磁盘文件本身的对象。File类的类图如下 :

         File类实现了Serializable和Comparable两个接口:

  • Serializable接口的实现,使File类的对象可以串行化,串行化后,对象可以进行网络传输,也可以保存到文件。
  • Comparable接口的实现使得File对象可以进行比较。

   File类以文件或文件夹为单位进行操作,因此不能直接访问文件的内容,若想访问文件内容,需要配合java中的IO流来完成。

        特点:

  1. java.io.File类:文件和文件目录路径的抽象表示形式,与平台无关。
  2. File类中涉及到关于文件或文件目录的创建、删除、重命名、修改时间、文件大小等方法,并未涉及到写入或读取文件内容的操作。如果需要读取或写入文件内容,必须使用IO流来完成。
  3. 想要在Java程序中表示一个真实存在的文件或目录,那么必须有一个File对象,但是Java程序中的一个File对象,可能没有一个真实存在的文件或目录。
  4. File对象可以作为参数传递给流的构造器,指明读取或写入的"终点"。

2.常用API

注意事项:

1.绝对路径和相对路径

绝对路径:  指的是从顶层目录(windows下为盘)开始定位,形成的路径。

相对路径:  指的是从当前目录(IDEA默认从项目开始)开始定位,形成的路径。

2.路径分隔符

a.路径中的每级目录之间用一个路径分隔符隔开。

b.路径分隔符和系统有关:

  • windowsDOS系统默认使用"\"来表示
  • UNIX和URL使用"/"来表示

c.Java程序支持跨平台运行,因此路径分隔符要慎用。

File类提供了一个常量:public static final String separator:根据操作系统,动态的提供分隔符。

a.构造方法

  • File (String pathname)

    • 需要传入文件路径的String类型。

    • 需要用File类型作接收。

  • File(String parent, String child)

    • 将文件路径分为父路径和子路径两部分,分开传上去。

  • File(File parent, String child)

    • 将文件路径劈开后,又先将父路径封装成了File类型,然后再分别将File类型的父路径和String类型的子路径传上去。

        代码示例:

package org.exam.FileTest;
 
import java.io.File;
import java.io.IOException;
 
public class FileConstructors {
    public static void main (String[] args) throws IOException {//建议直接抛出父类异常
    //演示File类的三种构造方法:(1.txt之后会在创建功能的演示中创建)
        System.out.println("---------------------------------");
    //1.File (String pathname):
        File file1 = new File("D:\\JAVA\\IDEA\\1.txt");
        System.out.println("看看file1长啥样:\n" + file1);
        System.out.println("---------------------------------");
 
    //2.File(String parent, String child):
        File file2 = new File("D:/JAVA/IDEA/", "2.txt");
        System.out.println("看看file2长啥样:\n" + file2);
        System.out.println("---------------------------------");
 
    //3.File(File parent, String child):
        File parent = new File("D:/JAVA/IDEA");
        File file3 = new File(parent, "3.txt");
        System.out.println("看看file3长啥样:\n" + file3);
    }
}

b.成员方法

判断功能:

        代码示例:先对这个不存在的路径做一个判断,再用file对象重新创建一个1.txt文本文件,然后再对它进行判断。

package cn.exam.ioTest;
 
import java.io.File;
import java.io.IOException;
 
public class Judge {
    public static void main(String[] args) throws IOException {
    /*
        演示三种判断方法:
        注意:
        文件只有在被创建后,才能被判断为是文件!
    */
    //1.isDirectory():
        //先来封装一个目录的file对象
        File file1 = new File("D:\\JAVA\\IDEA\\fileEX\\我是目录");
        boolean bool1 = file1.isDirectory();
 
        System.out.println("file1对象对应的路径是不是目录:" + bool1);
        System.out.println("---------------------------------------------");
    //2.isFile():
        boolean bool2 = file1.isFile();
 
        System.out.println("file1对象对应的路径是不是文件:" + bool2);
        System.out.println("---------------------------------------------");
    //3.exists():
        File file2 = new File("D:\\JAVA\\IDEA\\fileEX\\1.txt");
        //注意这时候这个文件还没有被创建
        boolean bool3 = file2.exists();
        boolean bool3ex = file2.isFile();
        System.out.println("file2对应的1.txt这个文件现在存在吗:" + bool3);
        System.out.println("1.txt现在还没创建,那它是文件吗:" + bool3ex);
        System.out.println("---------------------------------------------");
 
        file2.createNewFile();
        boolean bool4 = file2.exists();
        boolean bool4ex = file2.isFile();
        System.out.println("已经创建的1.txt这个文件现在存在了吗:" + bool4);
        System.out.println("已经创建的1.txt是文件吗:" + bool4ex);
    }
}

        假如在1.txt文件没有创建时,判断它是不是文件,结果是false,在创建后,它既存在,也是文件。因此我们发现:The file can be judged as file only when it has been created. 即文件只有在被创建后才能被判断为是文件。

获取功能:

        代码示例:

package cn.exam.ioTest;
 
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
 
public class Obtain {
    public static void main(String[] args) throws IOException {
//演示File类的五种常用获取方法:
    //1.getAbsolutePath():获取file对象对应的绝对路径
        File file = new File("D:\\JAVA\\IDEA\\fileEX\\");
        String absolutePath = file.getAbsolutePath();
        System.out.println("输出file对象的绝对路径:" + absolutePath);
        /*
            Notice:
            ①因为up这里在创建File对象时,使用的是绝对路径,
             所以我们这里输出的相对路径和绝对路径是一样的。
            ②只有创建的File对象是相对路径,输出的相对路径与绝对路径才会不同。
         */
        System.out.println("=================================");
 
    //2.getPath(): 获取file对象对应的相对路径
        String path = file.getPath();
        System.out.println("输出file对象的相对路径:" + path);
        System.out.println("=================================");
 
    //3.getName(): 获取file的文件名
        String fileName = file.getName();
        System.out.println("输出file对象的文件名:" + fileName);
        System.out.println("=================================");
 
    //4.getName(): 获取file的文件名
        String parentName = file.getParent();
        System.out.println("输出file对象的父级目录:" + parentName);
        System.out.println("=================================");
 
    //5.length(): 获取file文件的大小(以字节为单位进行计算)
        File file1 = new File("D:\\JAVA\\IDEA\\fileEX\\1.txt");
        long size = file1.length();
        System.out.println("file1文件的大小 = " + size + " byte");
        System.out.println("=================================");
 
    //6.list(): 获取指定目录下所有文件(夹)的名称数组
        String[] fileNames = file.list();
        /*
            获取到的名称数组我们使用增强for来遍历。
            之后的File数组同理。
         */
        System.out.println("使用增强for遍历名称数组如下:");
        for (String fName : fileNames) {
            System.out.println(fName);
        }
        //此处也可以使用Arrays.toString() 方法来打印,如下:
        System.out.println();
        System.out.println("我们再用一下Arrays.toString()方法试试:");
        System.out.println(Arrays.toString(fileNames));
        System.out.println("=================================");
 
    //7.listFiles():  获取指定目录下所有文件(夹)的File类型数组
        File[] files = file.listFiles();
        System.out.println("使用增强for遍历File数组如下:");
        for (File filePlus : files) {
            System.out.println(filePlus);
        }
 
        System.out.println();
        System.out.println("我们再用一下Arrays.toString()方法试试:");
        System.out.println(Arrays.toString(files));
        System.out.println("=================================");
    }
}
创建功能:

        常见三种创建方式:

返回值(出参)方法名(形参列表)描述
booleancreateNewFile()创建一个新文件。(由路径创建File对象对应的文件)
booleanmkdir()创建目录(即文件夹),但只能是单级目录。
booleanmkdirs()创建目录(即文件夹),但既可以是单级目录,又可以是多级目录。

注意:

        对于创建功能,如果合法路径对应的文件不存在,就创建该文件并返回true;如果该文件已经存在,就不创建,返回false其实对于删除功能和判断功能的方法,返回值类型也为boolean类型。

        当我们创建一个File类对象时,仅仅是在内存中有了一个File对象,还未与磁盘发生实际的联系,只有当我们通过createNewFile()方法成功创建文件后,磁盘的相应位置才会创建实际存在着的文件。 

         代码示例:

package cn.exam.ioTest;
 
import java.io.File;
import java.io.IOException;
 
public class Create {
    public static void main(String[] args) throws IOException{//建议直接抛出父类异常IOException
    //演示三种创建方式:
    //1.createNewFile():
        /*
            注意1.txt这个文件是不存在的,但是我们可以通过file1.createNewFile()方法,
            创建file1对象对应的文件,即1.txt文件
         */
        File file1 = new File("D:\\JAVA\\IDEA\\fileEX\\1.txt");
        boolean bool1 = file1.createNewFile();
        boolean bool1ex = file1.createNewFile();//相同的文件对象只能成功创建一次。除非你删了它。
 
        System.out.println("创建1.txt文件成功了吗:" + bool1);//第一次创建成功,是true。
        System.out.println("文件已经存在,创建1.txt文件成功了吗:" + bool1ex);//第二次输出就是false了,因为已经存在。
        System.out.println();
 
    //2.mkdir:
        File file2 = new File("D:\\JAVA\\IDEA\\fileEX\\1.txt但目录");
        boolean bool2 = file2.mkdir();
 
        System.out.println("创建1.txt目录情况如何:" + bool2);
        System.out.println();
 
    //3.mkdirs:
        File file3 = new File("D:\\JAVA\\IDEA\\fileEX\\我是目录\\a\\b");
        boolean bool3 = file3.mkdirs();
 
        System.out.println("创建多级目录成功了没有:" + bool3);
    }
}
删除功能:

        代码示例:删除刚刚创建的文件

package cn.exam.ioTest;
 
import java.io.File;
import java.io.IOException;
 
public class Delete {
    public static void main(String[] args) throws IOException{
    //演示删除功能:
        File file1 = new File("D:\\JAVA\\IDEA\\fileEX\\1.txt");
        boolean bool = file1.delete();
 
        System.out.println("1.txt到底删掉了没有:" + bool);
    }
}

3.应用场景

a.打印文件及文件夹内容

        要求:按照windows系统目录中的存储结构打印文件夹的目录结构(包含子文件夹和文件)

        代码示例:

public static void showDir(File srcDir,int count) {
    System.out.println("|-" + srcDir.getName());
    File[] files = srcDir.listFiles();
    count++;
    for (File file : files) {
        System.out.print(printTab(count));
        if (file.isFile()) {
            System.out.println("|-" + file.getName());
        } else {
            showDir(file,count);
        }
    }
}	

b.统计文件夹的内存大小

        要求:通过键盘录入的文件夹路径,计算文件夹的大小

        代码示例:

public static long calculateDirSize(File srcDir) {
    long size = 0;
    File[] files = srcDir.listFiles();
    for (File file : files) {
        if (file.isDirectory()) {
            size += calculateDirSize(file);
        } else {
            size += file.length();
        }
    }
    return size;
}

c.复制文件夹

        要求:键盘输入两个文件夹路径,将其中一个文件夹(包含内容)拷贝到另一个文件夹中

        代码示例:

	//使用 commons-io-2.11.0包的工具类
    public static void copyDirUtil(File srcDir, File desDir) throws IOException {
        FileUtils.copyDirectory(srcDir, desDir); //底层也用的递归
    }
	
	//自己递归实现
    public static void copyDir(File srcDir, File desDir) throws IOException {
        if (!desDir.exists()) {
            desDir.mkdir();
        }
        File newDir = new File(desDir, srcDir.getName());
        newDir.mkdir();

        File[] files = srcDir.listFiles();
        if (files == null || files.length == 0) {
            return;
        }
        for (File file : files) {
            if (file.isDirectory()) {
                copyDir(file, newDir);
            } else {
                FileInputStream fis = new FileInputStream(file);
                FileOutputStream fos = new FileOutputStream(new File(newDir, file.getName()));
                byte[] buffer = new byte[1024];
                int len = 0;
                while ((len = fis.read(buffer)) != -1) {
                    fos.write(buffer, 0, len);
                }
                fis.close();
                fos.close();
            }
        }
    }

d.剪切文件夹

要求:键盘输入两个文件夹路径,将其中一个文件夹(包含内容)剪切到另一个文件夹中

代码示例:

	//使用 commons-io-2.11.0包的工具类,工具类忒好了
    public static void cutDirUtil(File srcDir, File desDir) throws IOException {
        FileUtils.moveDirectory(srcDir,desDir);
        /*源码
			先复制,再删除
		*/
    }
    //自己实现,在复制后紧接着删除源文件
    public static void cutDir(File srcDir, File desDir) throws IOException {
        if (!desDir.exists()) {
            desDir.mkdir();//删除源文件夹
        }
        File newDir = new File(desDir, srcDir.getName());
        newDir.mkdir();

        File[] files = srcDir.listFiles();
        if (files == null || files.length == 0) {
            srcDir.delete();
            return;
        }
       for (File file : files) {
            if (file.isDirectory()) {
                cutDir(file, newDir);
            } else {
                FileInputStream fis = new FileInputStream(file);
                FileOutputStream fos = new FileOutputStream(new File(newDir, file.getName()));
                byte[] buffer = new byte[1024];
                int len = 0;
                while ((len = fis.read(buffer)) != -1) {
                    fos.write(buffer, 0, len);
                }
                fis.close();
                fos.close();
                //读写完后,删除源文件
                file.delete();
            }
        }
        srcDir.delete();
    }

e.删除文件夹

要求:删除指定文件夹以及内部所有的文件

代码示例:

public static boolean deleteDir(File srcDir) {
    File[] files = srcDir.listFiles();
    if (files == null || files.length == 0) {
        return srcDir.delete();
    }

    for (File file : files) {
        if (file.isDirectory()) {
            deleteDir(file);
        } else {
            file.delete();
        }
    }
    return srcDir.delete();
}

f.复制指定类型文件

要求:复制源文件夹中所有指定类型的文件到目标文件夹中

代码示例:

	//将符合条件的 文件路径-文件名 存到map集合中
    public static Map<File, String> serchTypeFile(File srcDir, String desFileSuffix) {
        Map<File, String> map = new HashMap<>();
        File[] files = srcDir.listFiles();
        if (files == null) {
            return map;
        }
        for (File file : files) {
            if (file.isDirectory()) {
                map.putAll(serchTypeFile(file, desFileSuffix));
            } else {
                if (file.getName().toLowerCase().endsWith(desFileSuffix)) {
                    map.put(file.getAbsoluteFile(), file.getName());
                }
            }
        }
        return map;
    }

    public static void copyTypeDir(File srcDir, File desDir, String desFileSuffix) throws IOException {
        Map<File, String> map = serchTypeFile(srcDir, desFileSuffix);
        Set<Map.Entry<File, String>> entries = map.entrySet();
        for (Map.Entry<File, String> entry : entries) {
            File key = entry.getKey();
            String value = entry.getValue();
            System.out.println(key + "---" + value);
            /*挑选代码
                ...
            */
            //给文件加上时间戳,防止文件重名. 
            //这里有点问题,时间戳到毫秒值,如果电脑够快,可能两个同名文件加的时间戳一样,会造成文件的覆盖,唉~~ 
            //用(1)(2)的话,可以解决,但每次都要遍历新文件夹,好麻烦啊。
            String[] names = key.getName().split("\\."); //正则表达式的关系
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS");
            String format = sdf.format(new Date());
            String s = names[0] + "(" + format + ")." + names[1];

            File file = new File(desDir, s);
            FileUtils.copyFile(key,file);

        }
    }

四、常用IO类

四大顶级抽象类:

输入流输出流
字节流InputStreamOutputStream
字符流ReaderWriter

1.字节流

a.字节输入流(InputStream)

        java.io.InputStream抽象类是表示字节输入流的所有类的超类(父类),可以读取字节信息到内存中。它定义了字节输入流的基本共性功能方法。

常用API

返回值(出参)方法名(形参列表)描述

void

close()关闭此输出流并释放与此流相关联的任何系统资源
intread()从输入流读取数据的下一个字节
intread(byte[] b)该方法返回的int值代表的是读取了多少个字节,读到几个返回几个,读取不到返回-1
FileInputStream类

构造方法:

b.字节输出流(OutputStream)

        java.io.OutputStream抽象类是表示字节输出流的所有类的超类(父类),将指定的字节信息写出到目的地。它定义了字节输出流的基本共性功能方法。

        常用API:

FileOutputStream类

构造方法:

注意:

        创建输出流对象的时候,系统会自动去对应位置创建对应文件,而创建输出流对象的时候,文件不存在则会报FileNotFoundException异常,也就是系统找不到指定的文件异常。

        当你创建一个流对象时,必须直接或者间接传入一个文件路径。比如现在我们创建一个FileOutputStream流对象,在该路径下,如果没有这个文件,会创建该文件。如果有这个文件,会清空这个文件的数据

2.字符流

        因为数据编码的不同,因而有了对字符进行高效操作的流对象,字符流本质其实就是基于字节流读取时,去查了指定的码表,而字节流直接读取数据会有乱码的问题。

a.Reader

        java.io.Reader抽象类是字符输入流的所有类的超类(父类),可以读取字符信息到内存中。它定义了字符输入流的基本共性功能方法。

        常用API

FileReader类

        java.io.FileReader类是读取字符文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区。

        构造方法:

b.Writer

        java.io.Writer抽象类是字符输出流的所有类的超类(父类),将指定的字符信息写出到目的地。它同样定义了字符输出流的基本共性功能方法。

        常用API:

        

FileWriter类

        java.io.FileWriter类是写出字符到文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区。

注意:

        关闭资源时,与FileOutputStream不同。 如果不关闭,数据只是保存到缓冲区,并未保存到文件。

关闭close和刷新flush:
因为内置缓冲区的原因,如果不关闭输出流,无法写出字符到文件中。但是关闭的流对象,是无法继续写出数据的。如果我们既想写出数据,又想继续使用流,就需要flush 方法了。

flush :刷新缓冲区,流对象可以继续使用。
close:先刷新缓冲区,然后通知系统释放资源。流对象不可以再被使用了。

        构造方法:

五、包装IO流

1.缓存流

        缓冲流,也叫高效流,是对4个FileXxx 流的“增强流”。

        缓冲流作用:IO 操作是很消耗性能的,缓冲流将数据加载至缓冲区,一次性读取/写入多个字节,从而避免频繁的 IO 操作,提高流的传输效率。

BufferedInputStream(字节缓冲输入流)

        BufferedInputStream从源头(通常是文件)读取数据(字节信息)到内存的过程中不会一个字节一个字节的读取,而是会先将读取到的字节存放在缓存区(字节数组),并从内部缓冲区中单独读取字节

        BufferedReader带有缓冲区的字符输入流。使用这个流的时候不需要自定义char数组,或者说不需要自定义byte数组。自带缓冲。

BufferedOutputStream(字节缓冲输出流)

        BufferedOutputStream将数据(字节信息)写入到目的地(通常是文件)的过程中不会一个字节一个字节的写入,而是会先将要写入的字节存放在缓存区,并从内部缓冲区中单独写入字节

BufferedReader(字符缓冲输入流)

        BufferedReader从源头(通常是文件)读取数据(字符信息)到内存的过程中不会一个字符一个字符的读取,而是会先将读取到的字符存放在缓存区(字符数组),并从内部缓冲区中单独读取字符。

构造方法:

方法名备注
BufferedReader(Reader in)in为reader对象(可以是reader的实现类)

常用API:

返回值方法名作用
intread()读取一个字符,返回值为该字符ASCII码;读到文件末尾返回-1
intread(char[] c)读c数组长度的字节到c数组中,返回值为读到的字符个数;读到文件末尾返回-1
StringreadLine()读取文件一行
longskip(long n)跳过n个字符
voidclose()关闭文件输入流

代码示例:

public class BufferedReaderTest01 {
    public static void main(String[] args) throws Exception{

        FileReader reader = new FileReader("Copy02.java");
        // 当一个流的构造方法中需要一个流的时候,这个被传进来的流叫做:节点流。
        // 外部负责包装的这个流,叫做:包装流,还有一个名字叫做:处理流。
        // 像当前这个程序来说:FileReader就是一个节点流。BufferedReader就是包装流/处理流。
        BufferedReader br = new BufferedReader(reader);

        // br.readLine()方法读取一个文本行,但不带换行符。
        String s = null;
        while((s = br.readLine()) != null){
            System.out.print(s);
        }

        // 关闭流
        // 对于包装流来说,只需要关闭最外层流就行,里面的节点流会自动关闭。(可以看源代码。)
        br.close();
    }
}

BufferedWriter(字符缓冲输出流)

        BufferedWriter将数据(字符信息)写入到目的地(通常是文件)的过程中不会一个字符一个字符的写入,而是会先将要写入的字符存放在缓存区,并从内部缓冲区中单独写入字符。

构造方法:

方法名备注
BufferedWriter(Writer out)out为Writer对象(可以是reader的实现类)

常用API:

返回值方法名作用
voidwrite(int c)将指定字符写入文件中
voidwrite(char[] c, int off, int len)将c素组off位置开始,len长度的字符写入文件中
voidwrite(String str, int off, int len)从字符串off位置开始截取len长度的字符串写入文件
voidflush()刷新此输出流并强制写出所有缓冲的输出字符
voidclose()关闭文件输出流

代码示例:

    public static void main(String[] args) throws Exception{
        // 带有缓冲区的字符输出流
        BufferedWriter out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("copy", true)));
        // 开始写。
        out.write("hello world!");
        out.write("\n");
        out.write("hello kitty!");
        // 刷新
        out.flush();
        // 关闭最外层
        out.close();
        }
    }

2.转换流

        字符编码与解码:

        计算机中储存的信息都是用二进制数表示的,而我们在屏幕上看到的数字、英文、标点符号、汉字等字符是二进制数转换之后的结果。按照某种规则,将字符存储到计算机中,称为编码 。反之,将存储在计算机中的二进制数按照某种规则解析显示出来,称为解码 。比如说,按照A规则存储,同样按照A规则解析,那么就能显示正确的文本符号。反之,按照A规则存储,再按照B规则解析,就会导致乱码现象。简而言之:

  • 按照某种规则,将字符存储到计算机中,称为编码;

  • 将存储在计算机中的二进制数按照某种规则解析显示出来,称为解码。

        字符集:

        字符集 Charset:也叫编码表。是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等。
        计算机要准确的存储和识别各种字符集符号,需要进行字符编码,一套字符集必然至少有一套字符编码。常见字符集有ASCII字符集、GBK字符集、Unicode字符集等。

        Reader和Writer最重要的子类是InputStreamReader和OutputStreamWriter类:

  • InputStreamReader类包含了一个底层输入流,可以从中读取原始字节。它根据指定的编码方式,将这些字节转换为Unicode字符 --- 字节转字符;

  • OutputStreamWriter从运行的程序中接收Unicode字符,然后使用指定的编码方式将这些字符转换为字节,再将这些字节写入底层输出流中 --- 字符转字节。

InputStreamReader(字节流到字符流的桥梁)

        转换流java.io.InputStreamReader,是Reader的子类,从字面意思可以看出它是从字节流到字符流的桥梁。它读取字节,并使用指定的字符集将其解码为字符。它的字符集可以由名称指定,也可以接受平台的默认字符集。

构造方法:

代码示例:

public class BufferedReaderTest02 {
    public static void main(String[] args) throws Exception{

        /*// 字节流
        FileInputStream in = new FileInputStream("Copy02.java");

        // 通过转换流转换(InputStreamReader将字节流转换成字符流。)
        // in是节点流。reader是包装流。
        InputStreamReader reader = new InputStreamReader(in);

        // 这个构造方法只能传一个字符流。不能传字节流。
        // reader是节点流。br是包装流。
        BufferedReader br = new BufferedReader(reader);*/

        // 合并
        BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("Copy02.java")));

        String line = null;
        while((line = br.readLine()) != null){
            System.out.println(line);
        }

        // 关闭最外层
        br.close();
    }
}

OutputStreamWriter类(字符流到字节流的桥梁)

        转换流java.io.OutputStreamWriter ,是Writer的子类,字面看容易混淆会误以为是转为字符流,其实不然,OutputStreamWriter为从字符流到字节流的桥梁。使用指定的字符集将字符编码为字节。它的字符集可以由名称指定,也可以接受平台的默认字符集。

构造方法:

代码示例:

public class OutputDemo {
    public static void main(String[] args) throws IOException {
      	// 定义文件路径
        String FileName = "C:\\s.txt";
      	// 创建流对象,默认UTF8编码
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(FileName));
        // 写出数据
      	osw.write("哥敢"); // 保存为6个字节
        osw.close();
      	
		// 定义文件路径
		String FileName2 = "D:\\A.txt";
     	// 创建流对象,指定GBK编码
        OutputStreamWriter osw2 = new OutputStreamWriter(new FileOutputStream(FileName2),"GBK");
        // 写出数据
      	osw2.write("吹牛逼);// 保存为4个字节
        osw2.close();
    }
}

3.序列化流

a.什么是序列化:

        Java 提供了一种对象序列化的机制。用一个字节序列可以表示一个对象,该字节序列包含该对象的数据、对象的类型和对象中存储的属性等信息。字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息。

        反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化。对象的数据、对象的类型和对象中存储的数据信息,都可以用来在内存中创建对象。

b.序列化条件:

一个对象要想序列化,必须满足两个条件:

  1. 该类必须实现java.io.Serializable 接口,Serializable 是一个标记接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出NotSerializableException 。
  2. 该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用transient 关键字修饰。

ObjectOutputStream

        java.io.ObjectOutputStream 类,将Java对象的原始数据类型写出到文件,实现对象的持久存储。

构造方法:

ObjectInputStream

        ObjectInputStream反序列化流,将之前使用ObjectOutputStream序列化的原始数据恢复为对象。

构造方法:

4.打印流

a.什么是打印流:

        平时我们在控制台打印输出,是调用print方法和println方法完成的,这两个方法都来自于java.io.PrintStream类。该类能够方便地打印各种数据类型的值,是一种便捷的输出方式。

        打印流分为:字节打印流PrintStream,字符打印流PrintWriter。

        特点:

  • 只操作目的地,不操作数据源
  • 可以操作任意类型的数据
  • 如果启用了自动刷新,在调用println()方法的时候,能够换行并刷新
  • 可以直接操作文件

        简单理解:如果该流的构造方法能够同时接收File和String类型的参数,一般都是可以直接操作文件的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值