JAVA I/O 流
基本概念就不介绍了,都知道输入/输出流,高级流都是继承于OutputStream/InputSream,
很多类的中方法都是一样的,使用都是一样的
1:ByteArrayInputStream /ByteArrayOutputStream
字节数组流,顾名思义
byte buf[]= {61,62,63,64,65,66,67,68,69};
//使用的buf为缓冲区数组。
ByteArrayInputStream arrayInputStream=new ByteArrayInputStream(buf);
System.out.println(arrayInputStream.read()); //61
//在这里添加标记
arrayInputStream.mark(0);
//从输入流读入指定长度的数据到指定数组中
byte buf1[]=new byte[2];
arrayInputStream.read(buf1, 0, 2);//62, 63
//available剩余可读数,上面总共读取3个后剩余6个,
System.out.println(arrayInputStream.available());//6
//上面读取了2个字段后,再继续读取输出64
System.out.println(arrayInputStream.read());//64
//流跳过2个字节
arrayInputStream.skip(2); //65 66
System.out.println(arrayInputStream.read());//67
//这里rest重置流到上面标记的位置
arrayInputStream.reset();
System.out.println(arrayInputStream.read());//62
System.out.println(arrayInputStream.available());//7
//mark和reset必须配套使用
byte buf[] = { 'a', 'b', 'c', 'd', 'e'};
ByteArrayOutputStream arrayOutputStream = new ByteArrayOutputStream();
// 指定数组写入到ByteArrayOutputStream输出流中
arrayOutputStream.write(buf, 0, 5);
// 打印buf数组
System.out.println(arrayOutputStream.toString());//abcde
//重新接受一个字符
arrayOutputStream.write(78);
System.out.println(arrayOutputStream.toString());//abcdeN
//如上我们第二次再接受一个字符时,因为流中已经有数据了,所以输出结果是跟在上一个数据后面
//使用rest就是清空输出流中的数据
arrayOutputStream.reset();
arrayOutputStream.write(78);
System.out.println(arrayOutputStream.toString());//N
FileOutputStream fileOutputStream=new FileOutputStream(new File("path"));
//writeTo:就是把ByteArrayOutputStream流的全部内容写入到指定的输出流参数
arrayOutputStream.writeTo(fileOutputStream);
mark和reset必须配套使用,另外.mark(0)重点值大于0即可,他只是用来标记位置,比如你传递10 和传递0,
效果是一样的,这里的值并不代表流的偏移位置,知识记录当前位置,比如你读了5个字节后,在这里mark下
那么rest时读取数据的位置是在5字节这个位置开始读取
2:CharArrayReader/CharArrayWriter
字符数组流
方法和使用都和ByteArrayInputStream /ByteArrayOutputStream一模一样
区别:一个是字节流,一个是字符流,接受参数注意一个是byte数组一个是char数组
byte和char使用上一般人看起来没有区别,但是byte是有符号类型占一个字节表示-128-127,
char是无符号类型占两个字节表示0-65535
3:File文件和目录路径名的抽象表示
某些常规方法略
- 创建文件目录 mkdirs和mkdir
File file=new File("D:\\test\\test1\\test2");
if(!file.mkdir()) {
file.mkdir();
}
文件目录创建mkdir和mkdirs的区别,
mkdir只能创建一集目录不能再多了,mkdirs可以创建多级目录
D:\test\test1\test2 如果这个文件夹不存在则只能mkdirs创建,
如果D:\test\test1这两级目录存在,要在test1里面再创建test2,那么此时用mkdir或者mkdirs没有区别,
如果D:\test目录存在,要在他下面创建test\test1两级目录,也只能用mkdirs
- 创建临时文件对象createTempFile
//prefix文件名称前缀,suffix文件名称后缀
static File createTempFile(String prefix, String suffix)
//directory在这个文件目录中创建这个临时文件
static File createTempFile(String prefix, String suffix, File directory)
如上方法创建临时文件,注意只能创建文件对象,不能创建文件目录哦,
给出文件的前缀和后缀,系统使用随机生成一个名称,如果不指定文件生成的目录,
则文件位置系统给定,给出了文件生成的目录则文件在该目录下,如果是多级目录,则文件位置在最后一级目录里面。
- 获取磁盘大小
long getTotalSpace() //获取当前文件所处位置(分区)的总大小(单位字节)
long getUsableSpace()//获取当前文件所处位置(分区)的剩余大小(单位字节)
- 权限
//这样只允许读操作的文件或目录
boolean setReadOnly()
//true允许写操作 false 不允许写
boolean setWritable(boolean writable)
//true 允许读插座, false不允许读
boolean setReadable(boolean readable)
- 文件过滤Filter
```
//查找当前目录下的所有文件或文件夹名,列出名称,注意不包含子目录哦
File file=new File("D:\\");
String list[]=file.list();
for(String str:list) {
System.out.println(str);
}
----------------------------
//同上,只是返回对象
File[] listFiles()
----------------------------
//获取当前系统的根目录,电脑上来说就是系统分的几个区()
File file=new File("");
File list[]=file.listRoots();
for(File str:list) {
System.out.println(str.getAbsolutePath());//C:\,D:\,E:\,F:\
}
----------------------------------
//过滤指定文件或者目录,通过FilenameFilter或者FileFilter 指定规则,只返回符合指定规则的文件和目录
//FilenameFilter 和FileFilter用法一样,只是一个方法中返回对象,一个返回文件名称
//String[] list(FilenameFilter filter)
//File[] listFiles(FileFilter filter)
//File[] listFiles(FilenameFilter filter)
// FilenameFilter filenameFilter=new FilenameFilter() {
//
// @Override
// public boolean accept(File dir, String name) {
// // TODO Auto-generated method stub
// return false;
// }
// };
FileFilter fileFilter=new FileFilter() {
//accept方法返回文件对象或者路径,然后我们自定义规则,如果满足规则则return true
//执行完成后满足规则的文件就会保存到数组中
@Override
public boolean accept(File pathname) {
if(pathname.getName().startsWith("test")) {
return true;
}
return false;
}
};
File file=new File("D:\\");
File list[]=file.listFiles(fileFilter);
for(File str:list) {
System.out.println(str.getAbsolutePath());
}
- 文件流操作相关FileInputStream/FileOutputStream/FileReader/FileWriter
//FileInputStream/FileOutputStream用法
FileOutputStream fileOutputStream = null;
FileInputStream fileInputStream = null;
try {
String str = "helloWorld";
File file = new File("D:\\test.txt");
// 如果文件不存在,则新建一个文件
if (!file.exists()) {
file.createNewFile();
}
//写入数据
fileOutputStream = new FileOutputStream(file);
fileOutputStream.write(str.getBytes());
// ----------------------------
//创建字节数组
byte buf[] = new byte[1024];
fileInputStream = new FileInputStream(file);
//循环读取数据写入buf数组,当数据为-1时表示数据读完
while ((fileInputStream.read(buf, 0, buf.length)) != -1) {
System.out.println(new String(buf));
} catch (Exception e) {
} finally {
try {
//关闭流
if (fileOutputStream != null) {
fileOutputStream.close();
}
if(fileInputStream!=null) {
fileInputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
//FileReader/FileWriter
FileWriter fileWriter=null;
FileReader fileReader=null;
try {
String str = "helloWorld";
File file = new File("D:\\test.txt");
// 如果文件不存在,则新建一个文件
if (!file.exists()) {
file.createNewFile();
}
fileWriter=new FileWriter(file);
fileWriter.write(str);
//刷新下 这里注意下,我们是先写入数据马上再去读取,如果不执行flush,你会发现下面的代码读不到数据
//这是因为fileWriter的数据输出时还在缓存中,没有实际写到文件中,这里必须调用flush强制把内容写出
//如果我们下面不马上读取数据,正常来说我们会调用close,调用close会自动调用flush,
//最终也会把内容输出到文件中
fileWriter.flush();
//------------------------------
//这里注意接收的是char字符数组哦。
char buf[] = new char[1024];
fileReader=new FileReader(file);
//循环读取数据写入buf数组,当数据为-1时表示数据读完
while ((fileReader.read(buf, 0, buf.length)) != -1) {
System.out.println(new String(buf));
}
} catch (Exception e) {
System.out.println(e.getMessage());
}finally {
try {
if(fileWriter!=null) {
fileWriter.close();
}
if(fileReader!=null) {
fileReader.close();
}
} catch (Exception e2) {
}
}
4:RandomAccessFile读取和写入随机访问文件。
FileInputStream/FileOutputStream/FileReader/FileWriter:
这四个是用来输入、输出文件流,但是这几个方法有个问题是不能随机访问流,意思就是一个4G文件你要在中间插入一段文字,或者你只需要读取文件内容2G-4G的内容,他们是不好处理的,如果硬要实现也可以实现,就是把流全部读取出来,然后取或者处理相关,但是这种后果是什么,内存撑爆
RandomAccessFile
RandomAccessFile支持随机访问流的位置,我们可以轻易的从指定位置开始获取我们要的内容
应用场景:常用的文件断点续传,在指定文件出插入、修改内容
RandomAccessFile(String name, String mode)
RandomAccessFile(File file, String mode)
mode:模式有4个值
r 代表以只读方式打开指定文件 。
rw 以读写方式打开指定文件 。
rws 读写方式打开,并对内容或元数据都同步写入底层存储设备 。
rwd 读写方式打开,对文件内容的更新同步更新至底层存储设备 。rws 和rwd 区别不是很明白,但是有一点rws会把文件元数据更新同步到设备
元数据:简单点理解就是描述数据的数据,比如文件中的时间、大小等其他信息?或者是java代码中辅助描述方法类信息的数据
//如果test.txt这个文件内容是空的,我们指向accessFile.seek(5),然后再去获取他的偏移结果还是5
//这时候accessFile.writeBytes("adsads");写入数据,则文件内容前面会用5个空格占位
//如果test.txt内容是123456,设置内容偏移5,然后写入数据adsads,结果是12345adsads
//偏移下标是0开始,写入数据后会把从原有内容5偏移开始覆盖替换成adsads新写入的数据
RandomAccessFile accessFile=new RandomAccessFile("D:\\test.txt", "rw");
//getFilePointer()获取当前记录指向当前文件内容偏移位置,初始是0
System.out.println( accessFile.getFilePointer());//0
//seek()移动指向未见内容位置
accessFile.seek(5);
System.out.println( accessFile.getFilePointer());//5
accessFile.writeBytes("adsads");
//如下代码test.txt内容是123456
//调用accessFile.writeBytes("a");写入数据a,此时getFilePointer内容偏移就变成了1
//接着我们去accessFile.readLine()读取数据为23456,但是本地内容是a23456
//这是因为当前程序中在文件头部添加数据后,文件内容偏移指针从0自动变成了添加数据长度
//后面读取数据就相当于使用了seek()方法,
RandomAccessFile accessFile=new RandomAccessFile("D:\\test.txt", "rws");//123456
accessFile.writeBytes("a");
System.out.println( accessFile.getFilePointer());//1
System.out.println(accessFile.readLine());//23456
上面两段代码很好的展示了,我们可以在任意位置添加或者修改文件内容
RandomAccessFile中有seek(..)和skipBytes(int n) 两个方法,用法上没有多少区别
skipBytes方法内部最终还是调用的seek方法,找了很多文章也没发现他们本质区别,
有一点肯定的是skipBytes是相对定位,而seek是绝对定位,
意思skipBytes跳过的字节开始位置是相对于流上次所处的位置,而seek你0就是定位到文件开始,你20就是20的位置。
//test.txt内容是123456789
RandomAccessFile raf = new RandomAccessFile("D:\\test.txt", "rw");
//先读取一次内容,读取后相当于文件指针偏移到文件末尾了
System.out.println(raf.readLine());//123456789
System.out.println(raf.getFilePointer());//9
//此时我们是有skipBytes跳过2个字节,实际上他是在我文件末尾即9这里再跳过2个字节,
//明显我们数据只有9个字节长度,这里再去读取数据肯定是null了
raf.skipBytes(2);
System.out.println(raf.readLine());//null
System.out.println(raf.getFilePointer());//9
//----------------------
System.out.println(raf.getFilePointer());//9
//seek(2)让文件指针偏移到2的位置,他是绝对定位,设置后文件指针偏移位置就是在2
raf.seek(2);
System.out.println(raf.getFilePointer());//2
//所以这里打印的时候值就是3456789
System.out.println(raf.readLine());//3456789
//以上能清楚看到skipBytes的跳过是相对于上次位置再进行偏移
//seek是绝对位置
5:FilterInputSream/FilterOutputSream/FilterWriter/FilterReader
这个流本身是不能直接使用,他提供的构造方法不是怕public的,他要基于他的子类来使用
BufferedInputStream/DataInputStream/LineNumberInputStream/PushbackInputStream
6:缓冲流BufferedInputStream/BufferedOutputStream/BufferedWriter/BufferedReader
使用很简单,BufferedOutputStream(OutputStream out)里面套一个流
重点说下缓冲流和其他流的区别与好处
- 我们拿文件流和缓冲流来比较,在表面上看,使用方式和方法都一样,以为一样?
源码:
FileOutputStream fileOutputStream=new FileOutputStream(new File(""));
fileOutputStream.write(b, off, len);
//查看write的源码,直接调用native 方法进行文件读写,
private native void writeBytes(byte b[], int off, int len, boolean append)
throws IOException;
//---------------------------------
BufferedOutputStream bufferedOutputStream=new BufferedOutputStream(fileOutputStream);
bufferedOutputStream.write(b, off, len);
//先查看BufferedOutputStream的构造函数
//在BufferedOutputStream对象创建时内部会创建一个byte数组,
//如果不指定数组大小,内部默认大小为8192
public BufferedOutputStream(OutputStream out, int size) {
super(out);
if (size <= 0) {
throw new IllegalArgumentException("Buffer size <= 0");
}
buf = new byte[size];
}
//再查看BufferedOutputStream 的write方法源码,注意flushBuffer();方法
public synchronized void write(byte b[], int off, int len) throws IOException {
if (len >= buf.length) {
/* If the request length exceeds the size of the output buffer,
flush the output buffer and then write the data directly.
In this way buffered streams will cascade harmlessly. */
flushBuffer();
out.write(b, off, len);
return;
}
if (len > buf.length - count) {
flushBuffer();
}
System.arraycopy(b, off, buf, count, len);
count += len;
}
//查看flushBuffer方法,这里明显看到,是把我们的流输入到buf刚才创建的数组中
private void flushBuffer() throws IOException {
if (count > 0) {
out.write(buf, 0, count);
count = 0;
}
}
通过上面的源码片段可以看出,尽管文件流和缓存流2个在方法上和使用上一模一样,
但是源码中可以清楚看到缓存流在构建对象时会自动生成一个buf数组的缓存区,
我们每次读写流时先写入到那个缓存区中去,等缓冲区满了再写入到磁盘
这样的好处就是可以减少磁盘IO,提高效率,对磁盘有好处,这样的缺点就是占内存
至于处理的文件的效率不是说用了缓冲流就绝对的快,这个还是看具体应用场景吧
- BufferedWriter.newLine()在输出流中加入一个换行
StringWriter sw = null;
BufferedWriter bw = null;
String s = "Hello World!!";
try {
sw = new StringWriter();
bw = new BufferedWriter(sw);
bw.write(s, 0, 5);// Hello
bw.newLine();// 换一行
bw.write(s, 6, s.length() - 6);
bw.flush();
System.out.print(sw.getBuffer());
} catch (Exception e) {
e.printStackTrace();
} finally {
}
//结果Hello World!!分两行显示
//hello
//World
7:DataOutputStream/DataInputStream数据输入输出流
数据流:见名知意思,这个流主要是 从来输入输出数据流,
File file=new File("D:\\test.txt");
DataOutputStream dataOutputStream=new DataOutputStream(new FileOutputStream(file));
//往文件中输入11.52f,不要试图去打开这个文件夹,因为现实不出来
dataOutputStream.writeFloat(11.52f);
//再读取这个数据结果11.52
DataInputStream dataInputStream=new DataInputStream(new FileInputStream(file));
System.out.println(dataInputStream.readFloat());
- 数据流的应用举例,把如下图片的数据按照指定格式把数据存入文本中,然后读取出来
DataOutputStream dos = null ; // 声明数据输出流对象
File f = new File("d:" + File.separator + "order.txt") ; // 文件的保存路径
dos = new DataOutputStream(new FileOutputStream(f)) ; // 实例化数据输出流对象
String names[] = {"衬衣","手套","围巾"} ; // 商品名称
float prices[] = {98.3f,30.3f,50.5f} ; // 商品价格
int nums[] = {3,2,1} ; // 商品数量
for(int i=0;i<names.length;i++){ // 循环输出
dos.writeChars(names[i]) ; // 写入字符串
dos.writeChar('\t') ; // 写入分隔符
dos.writeFloat(prices[i]) ; // 写入价格
dos.writeChar('\t') ; // 写入分隔符
dos.writeInt(nums[i]) ; // 写入数量
dos.writeChar('\n') ; // 换行
}
dos.close() ; // 关闭输出流
//-----------------------------------------------
DataInputStream dis = null ; // 声明数据输入流对象
File f = new File("d:" + File.separator + "order.txt") ; // 文件的保存路径
dis = new DataInputStream(new FileInputStream(f)) ; // 实例化数据输入流对象
String name = null ; // 接收名称
float price = 0.0f ; // 接收价格
int num = 0 ; // 接收数量
char temp[] = null ; // 接收商品名称
int len = 0 ; // 保存读取数据的个数
char c = 0 ; // '\u0000'
try{
while(true){
temp = new char[200] ; // 开辟空间
len = 0 ;
while((c=dis.readChar())!='\t'){ // 接收内容
temp[len] = c ;
len ++ ; // 读取长度加1
}
name = new String(temp,0,len) ; // 将字符数组变为String
price = dis.readFloat() ; // 读取价格
dis.readChar() ; // 读取\t
num = dis.readInt() ; // 读取int
dis.readChar() ; // 读取\n
System.out.printf("名称:%s;价格:%5.2f;数量:%d\n",name,price,num) ;
}
}catch(Exception e){}
dis.close() ;
8:PrintStream/PrintWriter/Scanner
Scanner不属于流中,空用于控制台进行输入操作
PrintStream/PrintWriter/:忽略设备底层的差异,进行一致的IO操作。因此这种流也称为处理流或者包装流。
//控制台输入一个数
Scanner scanner=new Scanner(System.in);
String str=scanner.nextLine();
//打印出来
PrintWriter printWriter=new PrintWriter(System.out);
printWriter.print(str);
//记住使用flush刷新
printWriter.flush();
System.in/System.out标准的输入输出设备
//使用PrintWriter 给文件中输入数据
FileWriter fileWriter= new FileWriter(new File("D:\\test.txt"));
PrintWriter printWriter=new PrintWriter(fileWriter);
printWriter.write("123123");
printWriter.flush();
FileOutputStream fileOutputStream=new FileOutputStream(new File("D:\\test.txt"));
PrintStream printStream=new PrintStream(fileOutputStream);
printStream.println("dsadsa");
//记住使用flush刷新
printWriter.flush();
9:ObjectOutputStream/ObjectInputStream对象输入输出流
用于java对象的保存和读取,
注意对象必须要序列化
//创建一个user对象,注意user必须序列化,否则读取时会报错
User user=new User();
user.setUserName("chenxin");
user.setUserPhone(123456789);
//把这个对象写入文件中
File file=new File("D:\\test.txt");
ObjectOutputStream objectOutputStream=new ObjectOutputStream(new FileOutputStream(file));
objectOutputStream.writeObject(user);
objectOutputStream.flush();
//读取这个对象,注意对象必须序列化,否则读取时会报错
ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(file));
User readUser=(User)objectInputStream.readObject();
System.out.println(readUser.getUserName());
System.out.println(readUser.getUserPhone());
10:SequenceInputStream 合并输入流
SequenceInputStream 和并输入流,只能和平输入,未提供输出,
把多个输入流和合拼后一起输入到指定文件中
//创建一个集合把添加多个输入流
Vector<FileInputStream> vector = new Vector<FileInputStream>();
vector.addElement(new FileInputStream(new File("D:\\test1.txt")));
vector.addElement(new FileInputStream(new File("D:\\test2.txt")));
vector.addElement(new FileInputStream(new File("D:\\test3.txt")));
//创建一个枚举接口
Enumeration<FileInputStream> enumeration=vector.elements();
SequenceInputStream sequenceInputStream=new SequenceInputStream(enumeration);
byte[] buf = new byte[1024];
int len = 0;
while((len=sequenceInputStream.read(buf))!=-1)
{
System.out.println(new String(buf,0,len));
}
11:PipedInputStream/PipedOutputStream管道输出流和管道输入流
PipedInputStream/PipedOutputStream必须配套使用
他的最主要的作用就是2个线程间通信
我们在线程A中向PipedOutputStream中写入数据,这些数据会自动的发送到与PipedOutputStream对应的PipedInputStream中,进而存储在PipedInputStream的缓冲中;此时,线程B通过读取PipedInputStream中的数据。就可以实现,线程A和线程B的通信。
connect(..)两个类都有这个方法,就是关联另外一个流,两个类的这个方法是等价的,使用 一个即可
**Demo:创建一个Sender 类(线程),里面使用Scanner 来在控制台输入数据,
每次输入后PipedOutputStream 就write一次创建一个Reciver (线程)来接收Sender 发过来的数据,并打印**
public class Sender extends Thread {
private PipedOutputStream out = new PipedOutputStream();
public PipedOutputStream getPipedOutputStream() {
return out;
}
@Override
public void run() {
super.run();
wirteMessage();
}
private void wirteMessage() {
try {
Scanner scanner = new Scanner(System.in);
while (true) { //接收一次输入,然后就写出一次
String str= scanner.nextLine();
out.write(str.getBytes());
}
} catch (Exception e) {
}
}
}
public class Reciver extends Thread {
private PipedInputStream in = new PipedInputStream();
public PipedInputStream getPipedInputStream() {
return in;
}
@Override
public void run() {
readMessage();
super.run();
}
private void readMessage() {
try {
byte buf[]=new byte[1024];
int len=0;
while((len=in.read(buf))!=-1) {
System.out.println("接收到的数据是:"+new String(buf,0,len));
}
} catch (Exception e) {
}
}
}
public static void main(String[] args) {
try {
Sender sender = new Sender();
Reciver reciver = new Reciver();
PipedInputStream in = reciver.getPipedInputStream();
PipedOutputStream out = sender.getPipedOutputStream();
in.connect(out);
// out.connect(in);一样的
sender.start();
reciver.start();
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
12:PushbackInputStream/PushbackReader回推(回退)流
此流只有提供输入流,没有输出流
回推流,把某个数据回推到当前流的偏移位置
String s = "abcdefg";
ByteArrayInputStream in = new ByteArrayInputStream(s.getBytes());
PushbackInputStream pbin = new PushbackInputStream(in);
int temp;
while ((temp = pbin.read()) != -1) {
System.out.println("--------------------out:"+(char)temp) ;
if(temp=='a'){
pbin.unread('u') ; //这里回推U之后,再去读取数据就是U
//pbin.unread(tmep) ;结果死循环,
}
}
//结果:aubcdefg
解读上面代码,当我们读到a时,就回推字符u,此时流里就回推到a之前的位置,带上数据u,此时我们再取数据就取出u
为什么是在a的位置呢?
//查看流读取的源码知道,当我们读取流时通过偏移指针知道pos++,先取出值,然后pos+1
//即我们读取第一个字节完成后,pos偏移是值1
public synchronized int read() {
return (pos < count) ? (buf[pos++] & 0xff) : -1;
}
//unread回推方法源码偏移--pos, 其实就是回到上个字节位置,再赋值
public void unread(int b) throws IOException {
ensureOpen();
if (pos == 0) {
throw new IOException("Push back buffer is full");
}
buf[--pos] = (byte)b;
}