前言
Java中使用IO(输入输出)来读取和写入,读写设备上的数据、硬盘文件、内存、网络......,根据数据的走向可分为输入流和输出流,这个走向是以内存为基准的,即往内存读外面的数据是输入流,从内存中往外写是输出流。
IO总结
字节流和字符流-1
字节流和字符流-2
根据处理的数据类型可分为字节流和字符流。
- 字节流可以处理所有数据类型的数据,在java中以Stream结尾。
- 字符流处理文本数据,在java中以Reader和Writer结尾。
在程序中所有的数据都是以流的方式进行传输或保存的,程序需要数据的时候要使用输入流读取数据,而当程序需要将一些数据保存起来的时候,就要使用输出流完成。程序中的输入输出都是以流的形式保存的,流中保存的实际上全都是字节文件。字符流处理的单元为2个字节的Unicode字符,分别操作字符、字符数组或字符串,而字节流处理单元为1个字节,操作字节和字节数组。所以字符流是由Java虚拟机将字节转化为2个字节的Unicode字符为单位的字符而成的,所以它对多国语言支持性比较好!如果是音频文件、图片、歌曲,就用字节流好点,如果是关系到中文(文本)的,用字符流好点。所有文件的储存是都是字节(byte)的储存,在磁盘上保留的并不是文件的字符而是先把字符编码成字节,再储存这些字节到磁盘。在读取文件(特别是文本文件)时,也是一个字节一个字节地读取以形成字节序列。字节流可用于任何类型的对象,包括二进制对象,而字符流只能处理字符或者字符串;字节流提供了处理任何类型的IO操作的功能,但它不能直接处理Unicode字符,而字符流就可以。字节流是最基本的,所有的InputStrem和OutputStream的子类都是,主要用在处理二进制数据,它是按字节来处理的,但实际中很多的数据是文本,又提出了字符流的概念,它是按虚拟机的encode来处理,也就是要进行字符集的转化 这两个之间通过InputStreamReader,OutputStreamWriter来关联,实际上是通过byte[]和String来关联 在实际开发中出现的汉字问题实际上都是在字符流和字节流之间转化不统一而造成的 。我们还可以看到:Reader类的read()方法返回类型为int :作为整数读取的字符(占两个字节共16位),范围在 0 到 65535 之间 (0x00-0xffff),如果已到达流的末尾,则返回 -1;InputStream的read()虽然也返回int,但由于此类是面向字节流的,一个字节占8个位,所以返回 0 到 255 范围内的 int 字节值。如果因为已经到达流末尾而没有可用的字节,则返回值 -1。因此对于不能用0-255来表示的值就得用字符流来读取!比如说汉字。
不同平台的符号
符号 | Linux | Windows |
---|---|---|
换行符,可以使用System.getProperty("line.separator")
|
\n |
\r\n |
路径分隔符,可以使用File.separator
|
/ |
\ |
多个路径分隔符,可以使用File.pathSeparator
|
: |
; |
字节流和字符流的区别
字符流使用了缓存区(内存),执行了write()
,文件中不会立刻有内容(除非缓冲区满了或者主动刷新缓冲区),需要等输出流对象关闭了,文件中才会有内容;字节流不使用缓冲区,执行了wirite()
,文件中立刻就有内容了。
import java.util.*;
import java.io.*;
public class WriterTest {
public static void main(String[] args) {
File file = new File("1.txt");
Scanner in = null;
Writer out = null;
try{
in = new Scanner(System.in);
out = new FileWriter(file);//需要定义在try代码块外面,定义在try代码块里面的话,finally中访问不了
String data = null;
while(in.hasNext()){
data = in.nextLine();
if(data == null){
data = "";
}
out.write(data);
//out.flush();刷新缓冲区,将缓冲区的内容输出
}
}catch(Exception e) {
e.printStackTrace();
}
finally{
try{
in.close();
out.close();
}catch(Exception e){
e.printStackTrace();
}
}
}
}
import java.util.*;
import java.io.*;
public class OutputStreamTest {
public static void main(String[] args) {
File file = new File("2.txt");
Scanner in = null;
FileOutputStream out = null;
try{
in = new Scanner(System.in);
//覆盖式,追加式多一个true参数
out = new FileOutputStream(file);
String data = null;
byte[] bytes = null;
while(in.hasNext()){
data = in.nextLine();
if(data != null){
bytes = data.getBytes();
}else{
bytes = "".getBytes();
}
out.write(bytes);
}
}catch(Exception e) {
e.printStackTrace();
}finally{
try{
in.close();
out.close();
}catch(Exception e){
e.printStackTrace();
}
}
}
}
字节数组和字符串的转换
- public String(byte[] bytes, String charsetName)
- String(byte[] bytes, int off, int length)
- byte[] getBytes(String charsetName)
File
File类用于操作文件或目录本身
- File类有四个静态变量
变量名 | 作用 |
---|---|
File.pathSeparator | 系统相关的多个路径的分隔符,字符串类型,windows为; Unix为:
|
File.pathSeparatorChar | pathSeparator的第一个字符,为字符型 |
File.separator | 系统相关的路径分隔符,字符串类型,Unix类为/ ,windows为\\ (转义) |
File.separatorChar | separator的第一个字符,为字符型 |
-
构造函数
- 方法
Java SE 8 API
Path
Java SE 8 API
java.nio.file包中的接口。Path描述的是一种层级结构的路径(目录和文件),一个Path是由多个被分隔符分隔的name element组成(比如c/src/1.txt
就是3个),可以和File,URI对象互相转化;Path接口的实现者是不可变的,线程安全的。官方文档描述(节选):
An object that may be used to locate a file in a file system. It will typically represent a system dependent file path. A Path represents a path that is hierarchical and composed of a sequence of directory and file name elements separated by a special separator or delimiter.Paths associated with the default
provider
are generally interoperable with thejava.io.File
class. Paths created by other providers are unlikely to be interoperable with the abstract path names represented byjava.io.File
. ThetoPath
method may be used to obtain aPath
from the abstract path name represented by ajava.io.File
object. The resultingPath
can be used to operate on the same file as thejava.io.File
object. In addition, thetoFile
method is useful to construct aFile
from theString
representation of aPath
. Implementations of this interface are immutable and safe for use by multiple concurrent threads.
Paths
java.nio.file包中的final类,作用是获取Path
另外获取Path的方法是FileSystems.getDefault().getPath(str)和file.toPath()
URI
可以和File,Path转化
Files
Java SE 8 API
Files使用
java.nio.file包中的类,该工具类非常强大,可以操作文件(File的功能全有),读写文件,读取文件属性,读取和设置文件权限,遍历单个目录和整个目录(即递归遍历),复制文件(Path-Path,Path-输出流,输入流-Path);参数是Path(不是File);获取文件大小直接有size
方法获取文件的空间使用情况要先获取FileStore对象,再通过FileStore对象来获取
Closeable接口和AutoCloneable接口
前者extends了后者,实现了后者的类都可以使用带资源的try,而且可以抛出任何类型的异常;前者只能抛出IOException
String,StringBuilder,StringBuffer
三者之所以有length方法是因为它们实现了CharSequence接口
DataInput和DataOutput接口
可以将各种类型的数据写入或读取,对于int完整的写入其4个字节,对于double写入其8个字节,其他类型也一样,占用多少字节就写入多少字节,不像OutputStream的write方法(参数为int型的那个)只写入一个字节,如果要写入的数据类型是多个字节的则会截断,只写低位的字节。DataInputStream和DataOutputStream分别实现了这两个接口,RandomAccessFile则同时实现了这两个接口
OutputStream
OutputStream是所有字节输出流的祖先,是抽象类
- 方法
Java SE 8 API
需要注意的是最后一个write(int b)
方法,参数为int型,但实际只输出低八位,高24位忽略,比如write(258)
,int型的258的二进制形式为00000000 00000000 00000001 00000010
共4个字节,但是只保存最低位置的那个字节到硬盘中,所以取出来的时候值为2
InputStream
InputStream是所有字节输入流的祖先,是抽象类,虽然是阻塞IO,但是InputStream有一个available方法用于返回流当前可以返回的字节数,如果流当前不能读,就返回0,我们可以在读取流之前使用这个方法判断,这样就可以避免造成当前线程的阻塞
- 方法
Java SE 8 API
注意特殊的mark
、reset
、skip
方法
- 读取单个字节:public int read() throws IOException;
每次使用read()操作将读取一个字节数据,此时返回的是数据(注意是int类型),如果数据已读完,则int返回-1 - 读取内容到字节数组:public int read(byte [] b) throws IOException();
将内容读取到字节数组中,返回读取的个数,如果读取完毕,则返回-1 -
读取内容到部分字节数组:public int read(byte [] b,int off,int len) throws IOException();
将指定长度的内容读取到字节数组中,返回读取个数,如果读取完毕,则返回-1
- 对于
read(byte[] b)
的一点解释
该方法的功能说明让我疑惑的是,读完了到底返回字节数还是-1?
import java.io.File;
import java.io.FileInputStream;
public class Test {
public static void main(String[] args) {
File file = new File("1.txt");
byte[] bytes = new byte[200];
int len = 0;
try(FileInputStream in = new FileInputStream(file)) {
while((len = in.read(bytes)) != -1) {
System.out.println(len);
System.out.println(new String(bytes,0,len,"utf-8"));
}
}catch(Exception e) {
e.printStackTrace();
}
}
}
对于这段代码中的循环,其实执行了两次循环,第一次循环读取完返回文件字节数,并打印出来;第二次循环返回-1,结束循环。
Writer
Writer是所有字符输出流的祖先,是抽象类
- 方法
Java SE 8 API
FileWriter不是Writer的直接子类,是Writer的直接子类OutputStreamWriter的直接子类
Reader
Reader是所有字符输入流的祖先,是抽象类
- 方法
Java SE 8 API
FileReader不是Reader的直接子类,是Reader的直接子类InputStreamReader的直接子类
字节流和字符流的转换
主要使用两个类:OutputStreamWriter、 InputStreamReader
OutputStreamWriter是Writer的子类,所以OutputStreamWriter类对象可以自动转型为Writer类实例,以OutputStream的对象作为构造器参数。
InputStreamReader是Reader的子类,所以InputStreamReader类对象可以自动转型为Reader类实例,以InputStream的对象作为构造器参数。
OutputStreamWriter
直接子类FileWriter
-
构造方法
-
方法
多数都是继承来的,下面这个方法是自己的
InputStreamReader
直接子类FileReader
-
构造方法
-
方法
多数都是继承来的,下面这个方法是自己的
PrintWriter、BufferedWriter以及BufferredReader
PrintWriter、BufferedWriter以及BufferredReader
PrintWriter是具有自动行刷新的缓冲字符输出流,这是一个高级流。所谓的自动行刷新,意思就是说:在构造函数中指定autoFlush的值为true时,则 println()、printf() 或 format() 方法将自动刷新输出缓冲区(自动调用flush()方法),但是自动行刷新无疑会增加写出次数而降低写出效率。从PrintWeiter的构造方法中我们就可以知道,该高级流很灵活,接下来我们会介绍BufferedWriter/BufferedReader这一对字符缓冲流,但是,由于BufferedWriter没有PrintWriter使用灵活,所以在实际的操作中,我们往往会使用PrinterWriter/BufferedReader这种组合。
对象和字节数组的转换
- io.netty.buffer.ByteBuf,适用于对象的数据少
- 借助Json作为中间人,缺点是会获得的字节数组会带有JSon的格式化符号
{}
- 反射,适用于对象的数据多
StringWriter和StringReader
-
StringWriter
Writer的直接子类,将流的内容写入一个StringBuffer,可以调用getBuffer()方法获取这个StringBuffer
-
StringReader
Reader的直接子类,流从一个给定的字符串获取内容
DataInputStream和DataOutputStream
用于数值型的数据
PushbackInputStream
可预览的输入流,即可以先调用read方法读出来,如果不是自己想要的,就调用unread方法退回去
组合流过滤器
不同的流具有不同的功能,如果想同时具备这些功能,就可以使用组合流过滤器的方式,将一个类型的流传给另一个类型的流的构造器,从而兼具两种流的特性。比如想要兼具有预览功能,使用缓存机制,从文件读取数值类型的流,则可以
DataInputStream din = new DataInputStream(
new PushbackInputStream (
new BufferdInputStream(
new FileInputStream(...))));
IO家族
常用的流
包装流:PrintStream/PrintWriter/Scanner
PrintStream可以封装(包装)直接与文件交互的流对象OutputStream,使得编程人员可以忽略设备底层的差异,进行一致的IO操作。因此这种流也称为处理流或者包装流。PrintWriter除了可以包装字节流OutputStream之外,还能包装字符流Writer。Scanner可以包装键盘输入(System.in),方便地将键盘输入的内容转换成我们想要的数据类型。字符串流:StringReader/StringWriter
这两个操作的是专门操作String字符串的流,其中StringReader能从String中方便地读取数据并保存到char数组,而StringWriter则将字符串类型的数据写入到StringBuffer中(因为String不可写)。转换流:InputStreamReader/OutputStreamReader
这两个类可以将字节流转换成字符流,被称为字节流与字符流之间的桥梁。我们经常在读取键盘输入(System.in)或网络通信的时候,需要使用这两个类缓冲流:BufferedReader/BufferedWriter,BufferedInputStream/BufferedOutputStream
Oracle官方的描述:
Most of the examples we've seen so far use unbuffered I/O. This means each read or write request is handled directly by the underlying OS. This can make a program much less efficient.
Buffered input streams read data from a memory area known as a buffer; the native input API is called only when the buffer is empty. Similarly, buffered output streams write data to a buffer, and the native output API is called only when the buffer is full.
即:没有经过Buffered处理的IO, 意味着每一次读和写的请求都会由OS底层直接处理,这会导致非常低效的问题。经过Buffered处理过的输入流将会从一个buffer内存区域读取数据,本地API只会在buffer空了之后才会被调用(可能一次调用会填充很多数据进buffer)。经过Buffered处理过的输出流将会把数据写入到buffer中,本地API只会在buffer满了之后才会被调用。
BufferedReader/BufferedWriter可以将字符流(Reader/Writer)包装成缓冲流,这是最常见用的做法。另外,BufferedReader提供一个readLine()可以方便地读取一行,而FileInputStream和FileReader只能读取一个字节或者一个字符,因此BufferedReader也被称为行读取器
总结上面几种流的应用场景:
- FileInputStream/FileOutputStream 需要逐个字节处理原始二进制流的时候使用,效率低下
- FileReader/FileWriter 需要逐个字符处理的时候使用
- StringReader/StringWriter 需要处理字符串的时候,可以将字符串保存为字符数组
- PrintStream/PrintWriter 用来包装FileOutputStream 对象,方便直接将String字符串写入文件 ;Scanner用来包装System.in流,很方便地将输入的String字符串转换成需要的数据类型
- InputStreamReader/OutputStreamReader , 字节和字符的转换桥梁,在网络通信或者处理键盘输入的时候用
- BufferedReader/BufferedWriter , BufferedInputStream/BufferedOutputStream , 缓冲流用来包装字节流后者字符流,提升IO性能,BufferedReader还可以方便地读取一行,简化编程。
RandomAccessFile
注意这是个流,不是File。从截图来看,这个流直接继承Object,跟字符流和字节流不是一个系列的。并且实现了DataOutput和DataInput,所以可以读,也可以写。
- 存在的意义
是JAVA I/O流体系中功能最丰富的文件内容访问类,它提供了众多方法来访问文件内容。
由于可以自由访问文件的任意位置,所以如果需要访问文件的部分内容,RandomAccessFile将是更好的选择。
可以用来访问保存数据记录的文件,文件的记录的大小不必相同,但是其大小和位置必须是可知的。
这个类在很多资料上翻译成中文都是:随机访问文件,在中文里,随机是具有不确定的含义,指一会访问这里,一会访问那里的意思。如果以这种语义来解释的话,就会感到很困惑。其实,Random在英文中不仅仅有随机,还有任意的意思。如果中文名为任意访问文件是不是就会更好的理解。任意表示我们可以指定文件中任何一个位置去操作一个文件。
Charset字符集类
对于无法表示的字符,全用\ufffd
,即显示?
utf-16BE(big-endian)遵从高位在前,utf-16LE(little-endian)遵从低位在前
方法 | 描述 |
---|---|
availableCharsets | 静态方法,返回虚拟机可用的字符集的map,键为字符集名称,值为字符集对象 |
forName | 静态方法,根据字符集名称获取字符集对象 |
name | 返回字符集名称 |
aliases | 返回该字符集的别名集合 |
ByteBuffer encode(String str) | 将字符串编码成字节序列 |
CharBuffer decode(ByteBuffer buffer) | 解码给定的字节序列,返回字符序列 |
- ByteBuffer
方法 | 描述 |
---|---|
byte[] array() | 返回该字节序列的字节数组 |
static ByteBuffer wrap(byte[] bytes) | 将字节数组包装成字节缓冲区(字节序列) |
- CharBuffer
方法 | 描述 |
---|---|
char[] array() | 返回该字符缓冲区的字符数组 |
char charAt(int index) | 返回指定索引的代码单元 |
对象序列化
要序列化的类需要实现Serializable接口,该接口没有方法,属于标记接口
ObjectInputStream和ObjectOutputStream,readObject方法和writeObject方法。序列化使用序列号机制,给每个序列化的对象分配一个唯一的序列号,所以在保存一个序列化对象到文件中时,如果之前已经保留存了,后面保存则会引用前面保存的,读取也是一样的道理
NIO
- Path路径,路径解析(resolve,resolveSibling,relativize,normalize,toAbsolutePath,getParent,getRoot,getFileName,toFile)
- Paths默认的文件系统,获取Path(get方法)
- Files文件操作(创建/删除文件/目录,复制/移动),读取文件内容(readAllBytes,readAllLines等等),创建输入输出流(newInputStream,newOutputStream,newBufferReader等等),获取文件信息,遍历目录(不包括子目录),遍历目录数(walkFileTree方法,递归遍历)
- FileStore文件存储空间情况
- FileVisitor文件访问者接口(访问前,访问,访问后,访问失败4个方法)
- SimpleFileVisitor文件访问接口的实现类
- DirectoryStream用来迭代要遍历的目录(Files的非递归遍历中使用)
- FileSystem文件系统,获取Path(getPath方法)
- FileSystems文件系统工具,可以创建新的文件系统(newFileSystem方法),获取默认的文件系统(getDefault方法)
- FileSystemProvider文件系统提供者
- Buffer缓冲区抽象类,是一种特殊的内存(0<=mark<=position<=limit<=capacity),clear,flip,mark,reset,compact,rewind,hasRemaining,remaining,position,limit,capacity,put,get...
- ByteBuffer字节缓冲区,allocate(创建一个指定大小的缓冲区),array(返回缓冲区管理的字节数组),wrap(将数组包装成字节缓冲区)
- MappedByteBuffer内存映射文件,ByteBuffer的子类,对于读取大文件很有帮助
- CharBuffer字符缓冲区
- 其他的XXBuffer(StringBuffer跟这些Buffer完全没关系)
- Channel通道,通道是线程安全的,可读可写,流是单向的,可以通过静态的open方法创建一个通道,也可以通过流的getChannel方法获取通道
- FileChannel文件通道,map方法返回MappedByteBuffer的对象
- SocketChannel 客户端TCP连接
- ServerSocketChannel 服务端TCP连接
- DatagramChannel UDP包
- CRC32(java.util.zip包中),计算循环冗余校验和,用来判断一个文件是否损坏
InputStream in = ...;
CRC32 crc = new CRC32();
int c;
while((c = in.read()) != -1) { crc.update(c)}
rerurn crc.getValue();//返回校验和
- 文件锁FileLock,锁定文件或文件的某一部分
如果多个进程(或线程)要对同一个文件进行访问,为了保证文件的安全,使用文件锁,文件锁通过channel获得,channel调用lock(阻塞式)或tryLock(非阻塞式)来获得文件锁并锁定文件,在锁对象关闭(锁对象调用close方法或release方法,close最终还是调用release方法)或者channel关闭之前,其他人无法访问该文件(或文件的某一部分)
Path path = Paths.get(str);
FileChannel channel = FileChannel.open(path);
FileLock lock = channel.lock();//or tryLock
正则表达式,\
和[
需要转义
语法 | 描述 |
---|---|
字符 | |
c | 字符c |
\unn,\xnn,\0nn | 以特定进制的形式给定代码单元的字符 |
\t,\n,\r,\f,\a,\e | 控制字符,制表、换行、回车、换页符、警告符、逃逸符 |
字符类 | |
[C1C2...Cn] | 字符集中的任何一个字符,如[0-9]表示任何数字字符 |
[^C1C2...Cn] |
^ 也可以用! 字符集的补集,如[^0-9]表示任何非数字字符 |
[C1C2...&&B1B2...] | 两个集合的交集 |
预定义字符类 | |
\d | 一个数字字符,等同于[0-9] |
\D | 一个非数字字符,等同于[^0-9] |
\w | 一个词语类字符,即[0-9a-zA-Z_],包括数字,大小写字母,下划线 |
\W | 一个非词语类字符 |
\s | 空白字符 |
\S | 非空白类字符 |
边界匹配符 | |
^ $ | 输入的开头和结尾,或者多行模式下的行的开头和结尾 |
\A | 输入的开头 |
\z | 输入的结尾 |
量词 | |
X? | 0或1个X |
X* | 0或多个X |
X+ | 1或多个X |
X{n} | n个X |
X{n,} | 至少n个X |
X{n,m} | n至m个X |
集合操作 | |
XY | 任何X中的字符串,后面紧跟Y中的字符串 |
XIY | X或Y中的字符串 |
String patternString = ...;
Pattern pattern = Pattern.compile(patternString);//获取模式对象
String input = ...;
Matcher matcher = pattern.matcher(input)//获取匹配器
if(matcher.matchs())//判断input字符串是否匹配
String patternString = ...;
Pattern pattern = Pattern.compile(patternString);//获取模式对象
String input = ...;
Matcher matcher = pattern.matcher(input)//获取匹配器
//获取输入中匹配到的字符串
while(matcher.find()) {
int start = match.start();
int end = matcher.end();
String matchString = input.subString(start, end);
}
String patternString = ...;
Pattern pattern = Pattern.compile(patternString);//获取模式对象
String input = ...;
Matcher matcher = pattern.matcher(input)//获取匹配器
//替代第一个匹配到的
String str = matcher.replaceFirst(anotherString)
//替代所有匹配到的
String str1 = matcher.replaceAll(anotherString)//
URL
String urlString = "https://www.baidu.com";
URL url = new URL(urlString);
InputStreamReader = new InputStreamReader(url.openStream());
// 方法一
URL url = new URL("http://www.sina.com.cn");
URLConnection urlcon = url.openConnection();
InputStream is = urlcon.getInputStream();
// 方法二
URL url = new URL("http://www.yhfund.com.cn");
HttpURLConnection urlcon = (HttpURLConnection)url.openConnection();
InputStream is = urlcon.getInputStream();
//方法三
URL url = new URL("http://www.yhfund.com.cn");
InputStream is = url.openStream();
long begintime = System.currentTimeMillis();
URL url = new URL("http://www.yhfund.com.cn");
HttpURLConnection urlcon = (HttpURLConnection)url.openConnection();
urlcon.connect(); //获取连接
InputStream is = urlcon.getInputStream();
BufferedReader buffer = new BufferedReader(new InputStreamReader(is));
StringBuffer bs = new StringBuffer();
String l = null;
while((l=buffer.readLine())!=null){
bs.append(l).append("/n");
}
System.out.println(bs.toString());
//System.out.println(" content-encode:"+urlcon.getContentEncoding());
//System.out.println(" content-length:"+urlcon.getContentLength());
//System.out.println(" content-type:"+urlcon.getContentType());
//System.out.println(" date:"+urlcon.getDate());
System.out.println("总共执行时间为:"+(System.currentTimeMillis()-begintime)+"毫秒");
}catch(IOException e){
System.out.println(e);
}
}
}