一、概念
"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流来完成。
特点:
java.io.File
类:文件和文件目录路径的抽象表示形式,与平台无关。File
类中涉及到关于文件或文件目录的创建、删除、重命名、修改时间、文件大小等方法,并未涉及到写入或读取文件内容的操作。如果需要读取或写入文件内容,必须使用IO
流来完成。- 想要在Java程序中表示一个真实存在的文件或目录,那么必须有一个
File
对象,但是Java程序中的一个File
对象,可能没有一个真实存在的文件或目录。 File
对象可以作为参数传递给流的构造器,指明读取或写入的"终点"。
2.常用API
注意事项:
1.绝对路径和相对路径
绝对路径: 指的是从顶层目录(windows下为盘)开始定位,形成的路径。
相对路径: 指的是从当前目录(IDEA默认从项目开始)开始定位,形成的路径。
2.路径分隔符
a.路径中的每级目录之间用一个路径分隔符隔开。
b.路径分隔符和系统有关:
windows
和DOS
系统默认使用"\"
来表示- 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("=================================");
}
}
创建功能:
常见三种创建方式:
返回值(出参) | 方法名(形参列表) | 描述 |
---|---|---|
boolean | createNewFile() | 创建一个新文件。(由路径创建File对象对应的文件) |
boolean | mkdir() | 创建目录(即文件夹),但只能是单级目录。 |
boolean | mkdirs() | 创建目录(即文件夹),但既可以是单级目录,又可以是多级目录。 |
注意:
对于创建功能,如果合法路径对应的文件不存在,就创建该文件并返回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类
四大顶级抽象类:
输入流 | 输出流 | |
字节流 | InputStream | OutputStream |
字符流 | Reader | Writer |
1.字节流
a.字节输入流(InputStream)
java.io.InputStream抽象类是表示字节输入流的所有类的超类(父类),可以读取字节信息到内存中。它定义了字节输入流的基本共性功能方法。
常用API
返回值(出参) | 方法名(形参列表) | 描述 |
---|---|---|
void | close() | 关闭此输出流并释放与此流相关联的任何系统资源 |
int | read() | 从输入流读取数据的下一个字节 |
int | read(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:
返回值 | 方法名 | 作用 |
---|---|---|
int | read() | 读取一个字符,返回值为该字符ASCII码;读到文件末尾返回-1 |
int | read(char[] c) | 读c数组长度的字节到c数组中,返回值为读到的字符个数;读到文件末尾返回-1 |
String | readLine() | 读取文件一行 |
long | skip(long n) | 跳过n个字符 |
void | close() | 关闭文件输入流 |
代码示例:
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:
返回值 | 方法名 | 作用 |
---|---|---|
void | write(int c) | 将指定字符写入文件中 |
void | write(char[] c, int off, int len) | 将c素组off位置开始,len长度的字符写入文件中 |
void | write(String str, int off, int len) | 从字符串off位置开始截取len长度的字符串写入文件 |
void | flush() | 刷新此输出流并强制写出所有缓冲的输出字符 |
void | close() | 关闭文件输出流 |
代码示例:
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.序列化条件:
一个对象要想序列化,必须满足两个条件:
- 该类必须实现java.io.Serializable 接口,Serializable 是一个标记接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出NotSerializableException 。
- 该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用transient 关键字修饰。
ObjectOutputStream
java.io.ObjectOutputStream 类,将Java对象的原始数据类型写出到文件,实现对象的持久存储。
构造方法:
ObjectInputStream
ObjectInputStream反序列化流,将之前使用ObjectOutputStream序列化的原始数据恢复为对象。
构造方法:
4.打印流
a.什么是打印流:
平时我们在控制台打印输出,是调用print方法和println方法完成的,这两个方法都来自于java.io.PrintStream类。该类能够方便地打印各种数据类型的值,是一种便捷的输出方式。
打印流分为:字节打印流PrintStream,字符打印流PrintWriter。
特点:
- 只操作目的地,不操作数据源
- 可以操作任意类型的数据
- 如果启用了自动刷新,在调用println()方法的时候,能够换行并刷新
- 可以直接操作文件
简单理解:如果该流的构造方法能够同时接收File和String类型的参数,一般都是可以直接操作文件的。