一、Java流
- Java.io 包几乎包含了所有操作输入、输出需要的类。所有这些流类代表了输入源和输出目标。
- Java.io 包中的流支持很多种格式,比如:基本类型、对象、本地化字符集等等。
- 一个流可以理解为一个数据的序列。输入流表示从一个源读取数据,输出流表示向一个目标写数据。
- Java 为 I/O 提供了强大的而灵活的支持,使其更广泛地应用到文件传输和网络编程中。
1、读取控制台输入多字符串
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class ReaderTest {
public static void main(String[] args) {
try {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
char c;
do {
c = (char)br.read();
System.out.println(c);
}while(c!='q');
} catch (IOException e) {
e.printStackTrace();
}
}
}
2、读取控制台输入字符串
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class ReaderTest {
public static void main(String[] args) throws IOException {
// 使用 System.in 创建 BufferedReader
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String str;
System.out.println("Enter lines of text.");
System.out.println("Enter 'end' to quit.");
do {
str = br.readLine();
System.out.println(str);
} while (!str.equals("end"));
}
}
3、控制台输出
常用的控制台输出语句有print()和println(),这些方法都是由PrintStream类定义的。System.out是该类的一种引用。PrintStream继承了FilterOutputStream类,并实现write()方法,通过write()也能实现控制台的输出。
String s = "菜小白向你问好!";
System.out.write(s.getBytes());
二、File类
Java文件类以抽象的方式代表文件名和目录路径名。该类主要用于文件和目录的创建、文件的查找和文件的删除等。File对象代表磁盘中实际存在的文件和目录。
1、字段
1)path:这个抽象路径名是规范化的路径名字符串。 一个规范化 ,pathname字符串使用默认的名称分隔符,不使用包含任何重复或多余的分隔符。
private final String path;
2)separatorChar:依赖于系统的默认名称分隔符。 这个字段是初始化为包含系统值的第一个字符属性file.separator 。在UNIX系统上,这个字段是/
; 在Microsoft Windows系统上,它是\
。
public static final char separatorChar = fs.getSeparator();
3)separator :依赖于系统的默认名称分隔符,表示为字符串方便。 该字符串只包含一个字符,即separatorChar
//表示separator 是字符串形式的separatorChar
public static final String separator = "" + separatorChar;
4)pathSeparatorChar :系统相关的路径分隔符。 这个字段是初始化为包含系统值的第一个字符属性path.separator 这个字符被用来在给定的文件序列中分隔文件名,作为路径列表。在UNIX系统上,这个字符是:
,在微软的Windows系统中;
。
public static final char pathSeparatorChar = fs.getPathSeparator();
5)pathSeparator:系统相关的路径分隔符,表示为字符串,为了方便。 该字符串只包含一个字符,即pathSeparatorChar
public static final String pathSeparator = "" + pathSeparatorChar;
2、构造方法
1)通过将给定的路径名字符串转换为抽象路径名来创建新的File实例。 如果给定的字符串是空字符串,则结果是空的抽象路径名。
- public File(String pathname)
//如果路径为null,则报NullPointerException异常。
File f1 = new File("D:/test/test.txt");
2)根据 parent 路径名字符串和 child 路径名字符串创建一个新 File 实例。
- public File(String parent,String child)
//当parent为null时,
String parent = null;
File f1 = new File(parent,"D:/test/test.txt");
System.out.println(f1.getPath());//D:/test/test.txt
【源码】
public File(String parent, String child) {
//如果child为null,报空指针异常
if (child == null) {
throw new NullPointerException();
}
if (parent != null) {
//如果parent是空字符串,使用child进行file文件创建
if (parent.equals("")) {
this.path = fs.resolve(fs.getDefaultParent(),
fs.normalize(child));
} else {//parent和child都不为null,且parent不为空。
this.path = fs.resolve(fs.normalize(parent),
fs.normalize(child));
}
} else {//parent为null,相当于使用child调用单参数的构造函数创建file
this.path = fs.normalize(child);
}
this.prefixLength = fs.prefixLength(this.path);
}
3)通过给定的父抽象路径名和子路径名字符串创建一个新的File实例。
- public File(File parent,String child)
简单理解为:将File parent文件的路径作为路径名,便等同与第二个构造方法。
File f = new File("D:/test");
File f1 = new File(f,"test.txt");
System.out.println(f1.getPath());//D:\test\test.txt
4)通过将给定的 file: URI 转换成一个抽象路径名来创建一个新的 File 实例。
- public File(URI uri)
3、常用方法
1)public boolean canExecute()
测试应用程序是否可以执行此抽象路径名表示的文件。
2)public boolean canRead()
测试应用程序是否可以读取此抽象路径名表示的文件。
3)public boolean canWrite()
测试应用程序是否可以修改此抽象路径名表示的文件。
File f = new File("D:/test/hello.txt");
System.out.println(f.canExecute());//true;电脑的该路径下存在这个文件
System.out.println(f.canRead());//true;文件权限是否可读
System.out.println(f.canWrite());//true;文件权限是否可写
4)public boolean createNewFile() throws IOException
当且仅当具有此名称的文件尚不存在时,以原子方式创建由此抽象路径名命名的新空文件。
true如果指定的文件不存在且已成功创建; false如果指定的文件已存在
//执行前提,test路径下的文件不存在
File f1 = new File("D:/test/test.txt");
boolean b1 = f1.createNewFile();
System.out.println(b1);//true
5)public static File createTempFile(String prefix, String suffix, File directory) throws IOException
在指定目录中创建一个新的空文件,使用给定的前缀和后缀字符串生成其名称。
由该方法创建的文件可能对此方法创建的文件具有更严格的访问权限,因此可能更适合安全敏感的应用程序。
File f3 = new File("D:/test");
File f4 = File.createTempFile("arrays", ".txt", f3);
System.out.println(f4);//D:\test\arrays2689259757626239534.txt
/*
* C:\XXX\Temp\arr4762018469261811129.txt
* 其中XXX自己电脑的默认路径。
* 参数:
* prefix - 用于生成文件名的前缀字符串; 必须至少三个字符长
* suffix - 用于生成文件名的后缀字符串; 可以是null ,在这种情况下将使用后缀".tmp"
* directory - 要在其中创建文件的目录,如果要使用默认临时文件目录, null
* 结果:
* 表示新创建的空文件的抽象路径名
* 异常:
* IllegalArgumentException - 如果 prefix参数包含少于三个字符
* IOException - 如果无法创建文件
* SecurityException - 如果存在安全管理器且其 SecurityManager.checkWrite(java.lang.String)方法不允许创建文件
*/
6)public static File createTempFile(String prefix, String suffix) throws IOException
在默认临时文件目录中创建一个空文件,使用给定的前缀和后缀生成其名称。 调用此方法相当于调用createTempFile(prefix, suffix, null) .
File f2 = File.createTempFile("arr", ".txt");
System.out.println(f2);
7)public boolean delete()
删除此抽象路径名表示的文件或目录。 如果此路径名表示目录,则该目录必须为空才能被删除。
File f5 = new File("D:/test/hello.txt");
boolean b = f5.delete();
System.out.println(b);//true,文件存在删除成功
8)public boolean exists()
测试此抽象路径名表示的文件或目录是否存在。
boolean b1 = f5.exists();
System.out.println(b1);//false,
/*
* 由于上一个方法删除了该文件,因此文件不存在,返回false
*/
9)public String getName()
返回此抽象路径名表示的文件或目录的名称。 如果路径名的名称序列为空,则返回空字符串。
10)public boolean mkdir()
创建此抽象路径名指定的目录。
11)public Path toPath()
返回从此抽象路径构造的java.nio.file.Path对象。
12)public String toString()
返回此抽象路径名的路径名字符串。 这只是getPath()方法返回的字符串。
File类API
三、读写文件(字节流)
输入流和输出流的类层次图:
1、FileOutputStream
该类是创建一个类,并向文件中写入内容。
1)构造方法
使用字符串类型的文件名来创建一个输入流对象来读取文件
FileOutputStream out = null;
try {
out = new FileOutputStream("D:\\test\\output.text");
} catch (IOException e) {
e.printStackTrace();
}
使用一个文件对象来创建一个输入流对象来读取文件。首先得使用 File() 方法来创建一个文件对象
FileOutputStream out = null;
File f = new File("D:\\test\\output.text");
try {
out = new FileOutputStream(f);
} catch (IOException e) {
e.printStackTrace();
}
2)基本方法
返回值类型 | 方法名 | 描述 |
---|---|---|
void | close() | 关闭此文件输出流并释放与此流关联的所有系统资源 |
void | write(byte[] b) | 将指定字节数组中的 b.length字节写入此文件输出流 |
void | write(byte[] b, int off, int len) | 将从偏移量 off开始的指定字节数组中的 len字节写入此文件输出流 |
void | write(int b) | 将指定的字节写入此文件输出流 |
3)实例
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileOutputStreamTest {
public static void main(String[] args) {
FileOutputStream out = null;
File f = new File("D:\\test\\output.text");
try {
//文件夹不存在会报错,确保文件在存在的目录下面
out = new FileOutputStream(f);
//定义写入文件的内容
byte[] b = {11,22,33,44,55,66,77,88,99};
//进行写入操作
for (int i = 0; i < b.length; i++) {
out.write(b[i]);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
//及时关闭流资源
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
2、ByteArrayOutputStream
JDK文档介绍:此类实现一个输出流,其中数据被写入字节数组。 缓冲区会在数据写入时自动增长。 可以使用toByteArray()和toString()检索数据。关闭ByteArrayOutputStream无效。 在关闭流之后可以调用此类中的方法,而不生成IOException 。
1)构造方法
//创建一个32字节(默认大小)的缓冲区:
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
//创建一个指定大小的缓冲区:
ByteArrayOutputStream byteOut = new ByteArrayOutputStream(32);
2)字段
变量和类型 | 字段名 | 描述 |
---|---|---|
protected byte[] | buf | 存储数据的缓冲区 |
protected int | count | 缓冲区中的有效字节数 |
3)基本方法
返回值 | 方法名 | 描述 |
---|---|---|
void | close() | 关闭 ByteArrayOutputStream无效 |
void | reset() | 将 count字段重置为零,丢弃输出流中当前累积的所有输出 |
int | size() | 返回缓冲区的当前大小 |
byte[] | toByteArray() | 创建一个新分配的字节数组,数组的大小和当前输出流的大小,内容是当前输出流的拷贝。 |
String | toString() | 使用平台的默认字符集将缓冲区的内容转换为字符串解码字节 |
String | toString(String charsetName) | 通过使用名为charset的字节解码将缓冲区的内容转换为字符串 |
String | toString(Charset charset) | 通过使用指定的charset解码字节,将缓冲区的内容转换为字符串。 |
void | write(byte[] b, int off, int len) | 从偏移量为 off的指定字节数组写入 len字节到流中 |
void | write(int b) | 将指定的字节写入流中 |
void | writeBytes(byte[] b) | 将指定字节数组的完整内容写入流中 |
void | writeTo(OutputStream out) | 将流的完整内容写入指定的输出流参数 |
4)实例
import java.io.ByteArrayOutputStream;
import java.io.IOException;
public class ByteArrayInputStreamTest {
public static void main(String[] args) throws IOException {
byte[] b = new byte[32];
ByteArrayOutputStream byteOut = new ByteArrayOutputStream(32);
while(byteOut.size()!=10) {
//接收控制台的输入,写入流中
byteOut.write(System.in.read());
}
b = byteOut.toByteArray();//将流中数据转化为字符数组
for (int i = 0; i < b.length; i++) {
System.out.print((char)b[i]+",");//输出数据
}
}
}
3、FileInputStream
FileInputStream作用是从文件系统中的文件获取输入字节。 可用的文件取决于主机环境,用于读取诸如图像数据的原始字节流
1)构造方法
可以使用字符串类型的文件名来创建一个输入流对象来读取文件:
InputStream f = new FileInputStream("C:/java/hello");
也可以使用一个文件对象来创建一个输入流对象来读取文件。我们首先得使用 File() 方法来创建一个文件对象:
File f = new File("C:/java/hello");
InputStream in = new FileInputStream(f);
2)基本方法
返回值类型 | 方法名 | 描述 |
---|---|---|
void | close() | 返回可以从此输入流中读取(或跳过)的剩余字节数的估计值,而不会被下一次调用此输入流的方法阻塞。 |
int | available() | 返回可以从此输入流中读取(或跳过)的剩余字节数的估计值,而不会被下一次调用此输入流的方法阻塞。 |
int | read() | 从此输入流中读取一个字节的数据 |
int | read(byte[] b) | 从输入流读取b.length长度的字节,存放在b数组缓冲区中 |
int | read(byte[] b, int off, int len) | 从此输入流读len个字节的数据从下标off开始读入到b数组中 |
3)实例:
结合FileInputStream和FileOutputStream完成文件的读入和输出操作:
由于是二进制方式的写入,以下方法会产生乱码问题,可以使用字符流操作来避免乱码问题
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileOutputStreamTest {
public static void main(String[] args) {
FileOutputStream out = null;
FileInputStream input = null;
int size;//读取文件时,文件的大小
File f = new File("D:\\test\\output.text");
try {
//文件夹不存在会报错,确保文件在存在的目录下面
out = new FileOutputStream(f);
//定义写入文件的内容
byte[] b = {11,22,33,44,55,66,77,88,99};
//进行写入操作
for (int i = 0; i < b.length; i++) {
out.write(b[i]);
}
input = new FileInputStream(f);
size = input.available();//获取文件的大小
for (int i = 0; i < size; i++) {
System.out.println((char)input.read()+" ");
}
} catch (IOException e) {
e.printStackTrace();
}finally {
//及时关闭流资源
try {
input.close();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
4、ByteArrayInputStream类
JDK文档介绍:ByteArrayInputStream包含一个内部缓冲区,其中包含可从流中读取的字节。 内部计数器跟踪read方法提供的下一个字节。
关闭ByteArrayInputStream无效。 在关闭流之后可以调用此类中的方法,而不生成IOException 。
1)字段
变量和类型 | 字段 | 描述 |
---|---|---|
protectedbyte[] | buf | 由流的创建者提供的字节数组 |
protected int | count | 索引1大于输入流缓冲区中的最后一个有效字符 |
protected int | mark | 流中当前标记的位置 |
protected int | pos | 从输入流缓冲区读取的下一个字符的索引 |
2)构造方法
- ByteArrayInputStream(byte[] buf)
接收字节数组作为参数创建 - ByteArrayInputStream(byte[] buf, int offset, int length)
接收一个字节数组,和两个整形变量 off、len,off表示第一个读取的字节,len表示读取字节的长度。
3)基本方法
返回值 | 方法名 | 描述 |
---|---|---|
int | available() | 返回可从此输入流中读取(或跳过)的剩余字节数。 |
void | close() | 关闭 ByteArrayInputStream无效。 |
void | mark(int readAheadLimit) | 设置流中当前标记的位置。 |
boolean | markSupported() | 测试此 InputStream支持标记/重置。 |
int | read() | 从此输入流中读取下一个数据字节。 |
int | read(byte[] b, int off, int len) | 从该输入流将最多 len字节的数据读入一个字节数组。 |
void | reset() | 将缓冲区重置为标记位置。 |
long | skip(long n) | 从此输入流中跳过 n个字节的输入。 |
4)实例
结合ByteArrayInputStream 和 ByteArrayOutputStream的使用:
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
public class ByteArrayIOStreamTest {
public static void main(String[] args) throws IOException {
byte[] b = new byte[32];
ByteArrayOutputStream byteOut = new ByteArrayOutputStream(32);
while(byteOut.size()!=10) {
//接收控制台的输入,写入流中
byteOut.write(System.in.read());
}
b = byteOut.toByteArray();
for (int i = 0; i < b.length; i++) {
System.out.print((char)b[i]+",");
}
//输出
int c;
System.out.println("\n Converting characters to Upper case :");
//创建输入流
ByteArrayInputStream byteinput = new ByteArrayInputStream(b);
while(( c= byteinput.read())!= -1) {
//输出流中内容的大写形式
System.out.println(Character.toUpperCase((char)c));
}
byteinput.reset();
}
}
四、读写文件(字符流)
1、Reader类
用于读取字符流的抽象类。 子类必须实现的唯一方法是read(char [],int,int)和close()。 但是,大多数子类将覆盖此处定义的一些方法,以提供更高的效率,附加功能或两者兼而有之。
1)构造函数
protected Reader()
创建一个新的字符流阅读器,其关键部分将在阅读器本身上同步。
2)常用方法
方法 | 描述 |
---|---|
public int read() throws IOException | 读一个字符。 |
public int read(char[] cbuf) throws IOException | 将字符读入数组。 |
public abstract int read(char[] cbuf, int off, int len) throws IOException | 将字符读入数组的一部分。 |
public boolean ready() throws IOException | 判断此流是否可以读取。 |
public abstract void close() throws IOException | 关闭流并释放与其关联的所有系统资源。 |
2、BufferedReader类
从字符输入流中读取文本,缓冲字符,以便有效地读取字符,数组和行。
由Reader构成的每个读取请求都会导致相应的读取请求由基础字符或字节流构成。 因此,建议将BufferedReader包装在任何read()操作可能代价高昂的Reader上,例如FileReaders和InputStreamReaders。 例如,
BufferedReader in = new BufferedReader(new FileReader(“foo.in”));
将缓冲指定文件的输入。
如果没有缓冲,read()或readLine()的每次调用都可能导致从文件中读取字节,转换为字符,然后返回,这可能是非常低效的。
1)构造方法
① BufferedReader(Reader in)
创建使用默认大小的输入缓冲区的缓冲字符输入流。
② BufferedReader(Reader in, int sz)
创建使用指定大小(size = sz)的输入缓冲区的缓冲字符输入流。
2)基本方法
返回值 | 方法名/参数 | 描述 |
---|---|---|
void | mark(int readAheadLimit) | 标记流中的当前位置。 |
boolean | markSupported() | 判断此流是否支持mark()操作。 |
int | read() | 读一个字符。 |
int | read(char[] cbuf, int off, int len) | 将字符读入数组的一部分。 |
String | readLine() | 读一行文字。 |
boolean | ready() | 判断此流是否可以读取。 |
void | reset() | 将流重置为最新标记。 |
long | skip(long n) | 跳过字符。 |
3)源码分析
package java.io;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
public class BufferedReader extends Reader {
private Reader in;
//字符的缓冲区cb[]
private char cb[];
// nChars 是cb缓冲区中字符的总的个数
// nextChar 是下一个要读取的字符在cb缓冲区中的位置
private int nChars, nextChar;
// 表示“标记无效”。它与UNMARKED的区别是:
// (1) UNMARKED 是压根就没有设置过标记。
// (2) 而INVALIDATED是设置了标记,但是被标记位置太长,导致标记无效!
private static final int INVALIDATED = -2;
//表示没有设置“标记”
private static final int UNMARKED = -1;
// “标记”
private int markedChar = UNMARKED;
// “标记”能标记位置的最大长度,仅当markedChar>0有效
private int readAheadLimit = 0;
/** 如果下一个字符是换行符,是否跳过它 */
private boolean skipLF = false;
/** 设置标记时的skipLF标志 */
private boolean markedSkipLF = false;
// 默认的缓冲区大小
private static int defaultCharBufferSize = 8192;
//默认读取一行的长度
private static int defaultExpectedLineLength = 80;
/**
*创建一个指定缓冲区大小的BufferedReader对象,size=sz
*当sz小于0时,IllegalArgumentException异常
*/
public BufferedReader(Reader in, int sz) {
super(in);
if (sz <= 0)
throw new IllegalArgumentException("Buffer size <= 0");
this.in = in;
cb = new char[sz];
nextChar = nChars = 0;
}
/**
* 创建默认缓冲区大小的对象
*/
public BufferedReader(Reader in) {
this(in, defaultCharBufferSize);
}
/**检查以确保流没有被关闭 */
private void ensureOpen() throws IOException {
if (in == null)
throw new IOException("Stream closed");
}
// 填充缓冲区函数。有以下两种情况被调用:
// (01) 缓冲区没有数据时,通过fill()可以向缓冲区填充数据。
// (02) 缓冲区数据被读完,需更新时,通过fill()可以更新缓冲区的数据。
private void fill() throws IOException {
//dst表示“cb中填充数据的起始位置”。
int dst;
if (markedChar <= UNMARKED) {
/*没有标记,则设dst为0 */
dst = 0;
} else {
//delta表示“当前标记的长度”,它等于“下一个被读取字符的位置”减去“标记的位置”的差值;
int delta = nextChar - markedChar;
if (delta >= readAheadLimit) {
//如果标记的长度大于标记上限
// 则丢弃标记!
markedChar = INVALIDATED;
readAheadLimit = 0;
dst = 0;
} else {
if (readAheadLimit <= cb.length) {
// 若“当前标记的长度”没有超过了“标记上限(readAheadLimit)”,
// 并且“标记上限(readAheadLimit)”小于/等于“缓冲的长度”;
// 则先将“下一个要被读取的位置,距离我们标记的置符的距离”间的字符保存到cb中。
System.arraycopy(cb, markedChar, cb, 0, delta);
markedChar = 0;
dst = delta;
} else {
// 若“当前标记的长度”没有超过了“标记上限(readAheadLimit)”,
// 并且“标记上限(readAheadLimit)”大于“缓冲的长度”;
// 则重新设置缓冲区大小,并将“下一个要被读取的位置,距离我们标记的置符的距离”间的字符保存到cb中。
char ncb[] = new char[readAheadLimit];
System.arraycopy(cb, markedChar, ncb, 0, delta);
cb = ncb;
markedChar = 0;
dst = delta;
}
nextChar = nChars = delta;
}
}
int n;
do {
n = in.read(cb, dst, cb.length - dst);
} while (n == 0);
if (n > 0) {
nChars = dst + n;
nextChar = dst;
}
}
/** 读取单个字符*/
public int read() throws IOException {
synchronized (lock) {
ensureOpen();
for (;;) {
if (nextChar >= nChars) {
fill();
if (nextChar >= nChars)
return -1;
}
if (skipLF) {
skipLF = false;
if (cb[nextChar] == '\n') {
nextChar++;
continue;
}
}
return cb[nextChar++];
}
}
}
/**
* 将缓冲区中的数据写入到数组cbuf中。off是数组cbuf中的写入起始位置,len是写入长度
*/
private int read1(char[] cbuf, int off, int len) throws IOException {
if (nextChar >= nChars) {
if (len >= cb.length && markedChar <= UNMARKED && !skipLF) {
return in.read(cbuf, off, len);
}
fill();
}
if (nextChar >= nChars) return -1;
if (skipLF) {
skipLF = false;
if (cb[nextChar] == '\n') {
nextChar++;
if (nextChar >= nChars)
fill();
if (nextChar >= nChars)
return -1;
}
}
int n = Math.min(len, nChars - nextChar);
System.arraycopy(cb, nextChar, cbuf, off, n);
nextChar += n;
return n;
}
// 对read1()的封装,添加了“同步处理”和“阻塞式读取”等功能
public int read(char cbuf[], int off, int len) throws IOException {
synchronized (lock) {
ensureOpen();
if ((off < 0) || (off > cbuf.length) || (len < 0) ||
((off + len) > cbuf.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}
int n = read1(cbuf, off, len);
if (n <= 0) return n;
while ((n < len) && in.ready()) {
int n1 = read1(cbuf, off + n, len - n);
if (n1 <= 0) break;
n += n1;
}
return n;
}
}
// 读取一行数据。ignoreLF是“是否忽略换行符”
String readLine(boolean ignoreLF) throws IOException {
StringBuffer s = null;
int startChar;
synchronized (lock) {
ensureOpen();
boolean omitLF = ignoreLF || skipLF;
bufferLoop:
for (;;) {
if (nextChar >= nChars)
fill();
if (nextChar >= nChars) { /* EOF */
if (s != null && s.length() > 0)
return s.toString();
else
return null;
}
boolean eol = false;
char c = 0;
int i;
/* Skip a leftover '\n', if necessary */
if (omitLF && (cb[nextChar] == '\n'))
nextChar++;
skipLF = false;
omitLF = false;
charLoop:
for (i = nextChar; i < nChars; i++) {
c = cb[i];
if ((c == '\n') || (c == '\r')) {
eol = true;
break charLoop;
}
}
startChar = nextChar;
nextChar = i;
if (eol) {
String str;
if (s == null) {
str = new String(cb, startChar, i - startChar);
} else {
s.append(cb, startChar, i - startChar);
str = s.toString();
}
nextChar++;
if (c == '\r') {
skipLF = true;
}
return str;
}
if (s == null)
s = new StringBuffer(defaultExpectedLineLength);
s.append(cb, startChar, i - startChar);
}
}
}
// 读取一行数据。不忽略换行符
public String readLine() throws IOException {
return readLine(false);
}
// 跳过n个字符
public long skip(long n) throws IOException {
if (n < 0L) {
throw new IllegalArgumentException("skip value is negative");
}
synchronized (lock) {
ensureOpen();
long r = n;
while (r > 0) {
if (nextChar >= nChars)
fill();
if (nextChar >= nChars) /* EOF */
break;
if (skipLF) {
skipLF = false;
if (cb[nextChar] == '\n') {
nextChar++;
}
}
long d = nChars - nextChar;
if (r <= d) {
nextChar += r;
r = 0;
break;
}
else {
r -= d;
nextChar = nChars;
}
}
return n - r;
}
}
// “下一个字符”是否可读
public boolean ready() throws IOException {
synchronized (lock) {
ensureOpen();
/*
* 如果需要跳过换行符并读取下一个字符是换行符,然后直接跳过它。
*/
if (skipLF) {
if (nextChar >= nChars && in.ready()) {
fill();
}
if (nextChar < nChars) {
if (cb[nextChar] == '\n')
nextChar++;
skipLF = false;
}
}
return (nextChar < nChars) || in.ready();
}
}
// 始终返回true。因为BufferedReader支持mark(), reset()
public boolean markSupported() {
return true;
}
// 标记当前BufferedReader的下一个要读取位置。
public void mark(int readAheadLimit) throws IOException {
if (readAheadLimit < 0) {
throw new IllegalArgumentException("Read-ahead limit < 0");
}
synchronized (lock) {
ensureOpen();
this.readAheadLimit = readAheadLimit;
markedChar = nextChar;
markedSkipLF = skipLF;
}
}
// 重置BufferedReader的下一个要读取位置,
// 将其还原到mark()中所保存的位置。
public void reset() throws IOException {
synchronized (lock) {
ensureOpen();
if (markedChar < 0)
throw new IOException((markedChar == INVALIDATED)
? "Mark invalid"
: "Stream not marked");
nextChar = markedChar;
skipLF = markedSkipLF;
}
}
public void close() throws IOException {
synchronized (lock) {
if (in == null)
return;
try {
in.close();
} finally {
in = null;
cb = null;
}
}
}
3、InputStreamReader
InputStreamReader是从字节流到字符流的桥接器:它使用指定的charset读取字节并将其解码为字符。 它使用的字符集可以通过名称指定,也可以明确指定,或者可以接受平台的默认字符集。
每次调用一个InputStreamReader的read()方法都可能导致从底层字节输入流中读取一个或多个字节。 为了实现字节到字符的有效转换,可以从基础流中提取比满足当前读取操作所需的更多字节。
1)构造方法
构造器 | 描述 |
---|---|
InputStreamReader(InputStream in) | 创建一个使用默认字符集的InputStreamReader。 |
InputStreamReader(InputStream in, String charsetName) | 创建一个使用指定charset的InputStreamReader。 |
InputStreamReader(InputStream in, Charset cs) | 创建一个使用给定charset的InputStreamReader |
InputStreamReader(InputStream in, CharsetDecoder dec) | 创建一个使用给定charset解码器的InputStreamReader。 |
2)常用方法
变量和类型 | 方法 | 描述 |
---|---|---|
String | getEncoding() | 返回此流使用的字符编码的名称。 |
int | read() | 读一个字符。 |
int | read(char[] cbuf, int offset, int length) | 将字符读入数组的一部分。 |
boolean | ready() | 判断此流是否可以读取。 |
其余方法都是其继承Reader类和Object类的方法。 | ||
继承Reader类的方法:close, mark, markSupported, nullReader, read, read, reset, skip, transferTo |
3)、FileReader和InputStreamReader的区别
FileReader类是InputStreamReader类的直接子类,所有方法(read ()等)都从父类 InputStreamReader 中继承来,它俩的主要区别就在于构造函数的不同,InputStreamReader类可以指定charset字符集。当字符文件编码与默认编码相同时,FileReader比InputStreamReader更加便利。
4、Writer
用于写入字符流的抽象类。 子类必须实现的唯一方法是write(char [],int,int),flush()和close()。 但是,大多数子类将覆盖此处定义的一些方法,以提供更高的效率,附加功能或两者兼而有之。
方法列表:
变量和类型 | 方法 | 描述 |
---|---|---|
Writer | append(char c) | 将指定的字符追加到此writer。 |
Writer | append(CharSequence csq) | 将指定的字符序列追加到此writer。 |
Writer | append(CharSequence csq, int start, int end) | 将指定字符序列的子序列追加到此writer。 |
abstract void | close() | 关闭流,先冲洗它。 |
abstract void | flush() | 刷新流。 |
static Writer | nullWriter() | 返回一个新的 Writer ,它丢弃所有字符。 |
void | write(char[] cbuf) | 写一个字符数组。 |
abstract void | write(char[] cbuf, int off, int len) | 写一个字符数组的一部分。 |
void | write(int c) | 写一个字符。 |
void | write(String str) | 写一个字符串。 |
void | write(String str, int off, int len) | 写一个字符串的一部分。 |
5、BufferedWriter
将文本写入字符输出流,缓冲字符,以便有效地写入单个字符,数组和字符串。
可以指定缓冲区大小,或者可以接受默认大小。 对于大多数用途,默认值足够大。
提供了一个newLine()方法,它使用平台自己的行分隔符概念,由系统属性line.separator定义。 并非所有平台都使用换行符(‘\ n’)来终止行。 因此,调用此方法终止每个输出行比直接编写换行符更为可取。
通常,Writer会立即将其输出发送到基础字符或字节流。 除非需要提示输出,否则建议将BufferedWriter包装在任何write()操作可能代价高昂的Writer周围,例如FileWriters和OutputStreamWriters。 例如,
PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(“xxx.txt”)));
将PrintWriter的输出缓冲到文件。 如果没有缓冲,每次调用print()方法都会导致字符转换为字节,然后立即写入文件,这可能效率很低。
1)构造方法
1)FileWriter构造方法
构造器 | 描述 |
---|---|
FileWriter(File file) | 给 File写一个 FileWriter ,使用平台的 default charset |
FileWriter(FileDescriptor fd) | 构造一个 FileWriter给出的文件描述符,使用该平台的 default charset 。 |
FileWriter(File file, boolean append) | 在给出要写入的 FileWriter下构造 File ,并使用平台的 default charset构造一个布尔值,指示是否附加写入的数据。 |
FileWriter(File file, Charset charset) | 构造一个FileWriter给予File编写和charset 。 |
FileWriter(File file, Charset charset, boolean append) | 构造FileWriter给出File写入, charset和一个布尔值,指示是否附加写入的数据。 |
FileWriter(String fileName) | 构造一个 FileWriter给出文件名,使用平台的 default charset |
FileWriter(String fileName, boolean append) | 使用平台的 default charset构造一个 FileWriter给定一个文件名和一个布尔值,指示是否附加写入的数据。 |
FileWriter(String fileName, Charset charset) | 构造一个FileWriter给出文件名和charset 。 |
FileWriter(String fileName, Charset charset, boolean append) | 构造一个FileWriter给定一个文件名, charset和一个布尔值,指示是否附加写入的数据。 |
6、OutputStreamWriter
OutputStreamWriter是从字符流到字节流的桥接器:使用指定的charset将写入其中的字符编码为字节。 它使用的字符集可以通过名称指定,也可以明确指定,或者可以接受平台的默认字符集。
每次调用write()方法都会导致在给定字符上调用编码转换器。 生成的字节在写入底层输出流之前在缓冲区中累积。 请注意,传递给write()方法的字符不会被缓冲。
为了获得最高效率,请考虑在BufferedWriter中包装OutputStreamWriter,以避免频繁的转换器调用。 例如:
Writer out
= new BufferedWriter(new OutputStreamWriter(System.out));
1)构造函数
构造器 | 描述 |
---|---|
OutputStreamWriter(OutputStream out) | 创建使用默认字符编码的OutputStreamWriter。 |
OutputStreamWriter(OutputStream out, String charsetName) | 创建使用指定charset的OutputStreamWriter。 |
OutputStreamWriter(OutputStream out, Charset cs) | 创建使用给定charset的OutputStreamWriter。 |
OutputStreamWriter(OutputStream out, CharsetEncoder enc) | 创建使用给定charset编码器的OutputStreamWriter。 |
2)方法
变量和类型 | 方法 | 描述 |
---|---|---|
void | flush() | 刷新流。 |
String | getEncoding() | 返回此流使用的字符编码的名称。 |
void | write(char[] cbuf, int off, int len) | 写一个字符数组的一部分。 |
void | write(int c) | 写一个字符。 |
void | write(String str, int off, int len) | 写一个字符串的一部分。 |