File类
File类主要是JAVA为文件这块的操作(如删除、新建等)而设计的相关类File类的包名是java.io,其实现了Serializable, Comparable两大接口以便于其对象可序列化和比较
创建一个文件/文件夹
删除文件/文件夹
获取文件/文件夹
判断文件/文件夹是否存在
对文件夹进行遍历
获取文件的大小
File类是一个与系统无关的类,任何的操作系统都可以使用这个类中的方法
重点:记住这第三个单词
file——文件
directory——文件夹/目录
path——路径
绝对路径————是一个完整的路径
以盘符( 比如C:)开始的路径
c:\a.txt
c:\demo\b.txt
相对路径————是一个简化的路径
相对指的是相对于当前项目的根目录
如果使用当前项目的根目录,路径可以简化书写
D:\java\Java语言高级\File类\File类\File类的概述.avi–>简化为——File类的概述.avi(可以省略项目的根目录)
注意
路径是不区分大小写
路径中的文件名称分隔符widows使用反斜杠,反斜杠是转义字符,`两个反斜杠代表一个普通反斜杠。
File类的静态属性
static String pathSeparator——与系统有关的路径分隔符,为了方便,它被表示为一个字符串。
static char pathSeparatorChar——与系统有关的路径分隔符。
windows下是分号,Linux下是冒号
static String Separator——与系统有关的默认名称分隔符,它被表示为一个字符串。
static char SeparatorChar——与系统有关的默认名称分隔符。
windows下是 \,Linux下是 /
public class filetest {
public static void main(String[] args) {
System.out.println(File.pathSeparator); //系统路径分隔符,win是分号,liunx是冒号
System.out.println(File.separator);
}
}
File类的构造方法
File(String pathname)
——通过将给定路径名字符串转换为抽象路径名来创建一个新的File实例。
参数
String pathname————字符串的路径名称
路径可以是以文件结尾,也可以是以文件夹结尾(win中文件有扩展名就是文件平)。
路径可以是相对路径(以项目根目录),也可以是绝对路径。
路径可以是存在的,也可以是不存在的。
创建File对象,只是把字符串路径封装为File对象,不考虑路径的真假情况。
public class filetest {
public static void main(String[] args) throws IOException {
File file = new File("xiong.txt");
file.createNewFile(); //只是文件名的话,文件创建放在项目的根目录下。
File file2 = new File("xiong");
file2.createNewFile();
file2.mkdir();
}
}
File(String parent, String child)
——根据parent路径名字符串和child路径名字符串来创建一个新的File实例。
参数————————把路径分成了两部分
String parent————父路径,该路径中的文件夹是已存的,不存在会报错
String child—————子路径
好处
父路径和子路径可以单独书写,使用起来非常灵活;父路径和子路径都可以变化。
public class filetest {
public static void main(String[] args) throws IOException {
File file = new File("C:\\abc\\","ccc.txt");
file.createNewFile();
System.out.println(file.getAbsolutePath());
}
}
File(File parent, String child)
——根据parent路径名字符串和child路径名字符串来创建一个新的File实例。
好处
父路径和子路径可以单独书写,使用起来非常灵活;父路径和子路径都可以变化。
父路径是File类型,可以使用File的方法对路径进行一些操作,在使用路径创建对象。
File fileparent = new File("c:\\abc");
fileparent.mkdir();
File file = new File(fileparent,"ccc.txt");
file.createNewFile();
System.out.println(file.getAbsolutePath());
File类中访问文件名相关的常用方法
public String getName()————返回此File标识的文件或目录的名称。
public String getPath()————将此File转换为路径名字符串。
public String getAbsolutePath()————返回此File的绝对路径名字符串,返回的是绝对路径。
public String getParent()————返回File对象所对应的目录(最后一级子目录)的父目录名称。
public boolean renameTo(File newName)————重命名此File对象所对应的文件或目录,成返回true
File类检查文件的相关常用方法
public boolean exists()————此File表示的文件或目录是否实际存在 。
public boolean isDirectory()————此File表示的是否为目录 。
public boolean isFile()————此File表示的是否为文件 。
public canWrite()————此File是否可写
public canRead()————此File是否可读
public isAbsolute()————此判断创建File对象时是不是用的是绝对路径
获取文件常规信息的常用方法
public long lastModified()————返回文件的最后修改时间。
public long length()————返回文件内容的长度。
File类创建删除文件的常用方法
public boolean creatNewFile()——当且仅当具有该名称的文件尚不存在时,创建一个新的空文件。
creatNewFile声明抛出了IOException,我们调用这个方法,就必须处理这个异常,要么throws,要么try catch
public boolean delete()——删除由此File表示的文件或目录。
此方法,可以删除构造方法路径中给出的文件/文件夹。注意delete方法直接在硬盘删除文件/文件夹,不走回收站,删除要谨慎。
public boolean mkdir()——创建由此File表示的目录。
创建单级空文件夹
public boolean mkdirs()——创建由此File表示的目录,包括任何必需但不存在的父目录。
既可以创建单级空文件夹,也可以创建多级文件夹
File类遍历(文件夹)目录功能
public String[ ] list( )——返回一个String数组,表示该File目录中的所有子文件或子目录。
public File[ ] listFiles( )——返回一个File数组,表示该File目录中的所有子文件或子目录。
注意
list方法和listFiles方法遍历的是构造方法中给出的目录。
如果构造方法中给出的目录的路径不存在,会抛出空指针异常。
如果构造方法中给出的路径不是一个目录,也会抛出空指针异常。
public class filefind {
public static void main(String[] args) {
File file=new File("C:\\Users\\Administrator\\Desktop\\STSWORKSPACE\\EduSystem");
String[] fileNames = file.list();
File[] files = file.listFiles();
for(String filename:fileNames){
System.out.println(filename);
}
for(File subfile:files){
System.out.println(subfile);
}
}
}
----------------------------------------------------------------------
.classpath
.mymetadata
.project
.settings
src
WebRoot
C:\Users\Administrator\Desktop\STSWORKSPACE\EduSystem\.classpath
C:\Users\Administrator\Desktop\STSWORKSPACE\EduSystem\.mymetadata
C:\Users\Administrator\Desktop\STSWORKSPACE\EduSystem\.project
C:\Users\Administrator\Desktop\STSWORKSPACE\EduSystem\.settings
C:\Users\Administrator\Desktop\STSWORKSPACE\EduSystem\src
C:\Users\Administrator\Desktop\STSWORKSPACE\EduSystem\WebRoot
文件过滤器的原理和使用
在File类中有两个listFiles重载的方法,方法的参数传递的就是过滤器。
File[ ] listFiles(FilesFilter filter)
java.io.FilesFilter 接口————用于抽象路径名(File对象)的过滤器。我常通过内部类的方式执行accept方法。
FilesFilter 接口里包含了一个抽象方法:boolean accept(File pathname)
参数
File pathname———使用listFiles方法遍历目录,得到的每一个文件对象。
例:找到一个目录下所有扩展名为.java的文件,并打印其绝对路径
public class filefind {
public static void main(String[] args) {
File file=new File("C:\\Users\\Administrator\\Desktop\\STSWORKSPACE\\EduSystem");
//文件过滤器,筛选想要的文件
getJavaFiles(file);
}
//获得所有.java文件
public static void getJavaFiles(File file){
if(!file.isDirectory()){
return;
}
//是文件夹,进一步扫描
File[] files = file.listFiles(new FileFilter() {
@Override
public boolean accept(File subFile) {
if(subFile.isDirectory()) return true;
else
return subFile.getName().toLowerCase().endsWith(".java");
}
});
for(File rsFile:files){
if(rsFile.isDirectory()){
getJavaFiles(rsFile); //递归,如果找到的子文件还是文件夹的话,再找
}else{
System.out.println(rsFile); //输入文件的绝对路径
}
}
}
}
File[ ] listFiles(FilenameFilter filter)
java.io.FilenameFilter 接口————实现此接口的类实例可用于过滤文件名。
FilenameFilter接口里包含了一个抽象方法:boolean accept(File dir,String name)
参数
File dir ——————构造方法中传递的被遍历的目录
String name————使用lisFiles方法遍历目录,获取的每一个文件/文件夹的名称。
RandomAccessFile
Java还提供了专门处理文件内容的类,即RandomAccessFile(随机访问文件)类。该类是Java语言中功能最为丰富的文件访问类,它提供了众多的文件访问方法。
RandomAccessFile类支持“随机访问”方式,这里“随机”是指可以跳转到文件的任意位置处读写数据。
RandomAccessFile对象类有个位置指示器,指向当前读写处的位置,
当前读写n个字节后,文件指示器将指向这n个字节后面的下一个字节处。
刚打开文件时,文件指示器指向文件的开头处,可以移动文件指示器到新的位置,随后的读写操作将从新的位置开始。
RandomAccessFile类在随机读取时有很大的优势,但该类仅限于操作文件,不能访问其他的I/O设备,如网络、内存映像等。
RandomAccessFile类的构造方法如下所示:
RandomAccessFile(File file , String mode)
//创建随机存储文件流,文件属性由参数File对象指定
RandomAccessFile(String name , String mode)
//创建随机存储文件流,文件名由参数name指定
这两个构造方法均涉及到一个String类型的参数mode,它决定随机存储文件流的操作模式,其中mode值及对应的含义:
“r”:以只读的方式打开,调用该对象的任何write(写)方法都会导致IOException异常
“rw”:以读、写方式打开,支持文件的读取或写入。若文件不存在,则创建之。
“rws”:以读、写方式打开,与“rw”不同的是,还要对文件内容或元数据的每次更新都同步更新到底层的存储设备中去。
“rwd”:以读、写方式打开,与“rw”不同的是,还要对文件内容的每次更新都同步更新到底层的存储设备中去。
任何文件系统中的数据分为数据和元数据。数据是指普通文件中的实际数据,即文件的实际内容;
而元数据指用来描述一个文件的特征的系统数据,诸如访问权限、文件拥有者以及文件数据块的分布信息(inode…)等等。
例:
public class RandomAccessFileTest{
public static void main(String[] args) {
RandomAccessFile rf=null;
RandomAccessFile rfr=null; //读用的
try {
Employee employee1 = new Employee("shaowen", 42); //创建对象时,名字不能用汉字,用了汉字,只要要一个就变了,三个对象不能保证为36个字节了。
Employee employee2= new Employee("zhangsan", 20);
Employee employee3=new Employee("wangmazi", 15);
rf= new RandomAccessFile("c:\\employee.txt", "rw");
rf.write(employee1.name.getBytes()); //有很多写方法,这里选了write(字节数组)
rf.writeInt(employee1.age);
rf.write(employee2.name.getBytes()); //有很多写方法,这里选了write(字节数组)
rf.writeInt(employee2.age);
rf.write(employee3.name.getBytes()); //有很多写方法,这里选了write(字节数组)
rf.writeInt(employee3.age);
rfr = new RandomAccessFile("c:\\employee.txt", "r");
System.out.println("最先读第二个员工的信息!"); //即跳过12个字节,开始读
rfr.skipBytes(12); //指针跑过12个字节,指向第13个字节
String str="";
for(int i=0;i<8;i++) {
str+=(char)rfr.readByte(); //指针会随读而改变指向
}
System.out.println("第二个员工的名字:"+str);
System.out.println("第二个员的的年龄:"+rfr.readInt());
System.out.println("回头读第一个员工信息");
rfr.seek(0);
String str1="";
for(int i=0;i<8;i++) {
str1+=(char)rfr.readByte(); //指针会随读而改变指向
}
System.out.println("第一个员工的名字:"+str1);
System.out.println("第一个员工的年龄:"+rfr.readInt());
}catch(IOException e) {
e.printStackTrace();
}finally {
if(rf!=null) {
try {
rf.close();
}catch(IOException ex) {
ex.printStackTrace();
}
}
}
}
}
class Employee{
public String name;
public int age;
public Employee(String name,int age) { //构造方法,创建对象时,保证12个字节,因为int是固定4个字节,所以name要保证8个字节
//保证名字长度是8个字节,以便等下测试获取
if(name.length()>8) {
name = name.substring(0,8);
}else {
while(name.length()<8) {
name=name+"\u0000"; //utf-8编码 \u0000表示空格
}
this.name=name;
}
this.age=age;
}
}
------------------------------------------------------------------------------------------------------------------------
最先读第二个员工的信息!
第二个员工的名字:zhangsan
第二个员的的年龄:20
回头读第一个员工信息
第一个员工的名字:shaowen
java IO流
Java IO流的概念
大多数应用程序都需要实现与设备之间的数据传输,例如键盘可以输入数据,显示器可以显示程序的运行结果等。
在Java中,将这种通过不同输入输出设备(键盘,内存,显示器,网络等)之间的数据传输抽象的表述为“流”。
Java中的“流”都位于java.io包中,称之为IO(输入输出)流。
输入流和输出流是相对于内存设备而言的,将外设中的数据读取到内存中即输入,将内存的数据写入到外设中即输出。
注:java的输入流主要是InputStream和Reader作为基类,而输出流则是主要由outputStream和Writer作为基类。它们都是一些抽象基类,无法直接创建实例。
字节流和字符流的用法几乎完成全一样,区别在于字节流和字符流所操作的数据单元不同,
字节流操作的单元是数据单元是8位的字节,字符流操作的是数据单元为16位的字符。
字节流主要是由InputStream和outPutStream作为基类,而字符流则主要有Reader和Writer作为基类。
按照流的角色划分为节点流和处理流.
可以从/向一个特定的IO设备(如磁盘,网络)读/写数据的流,称为节点流。节点流也被称为低级流。
处理流则用于对一个已存在的流进行连接和封装,通过封装后的流来实现数据的读/写功能。处理流也被称为高级流。
当使用处理流进行输入/输出时,程序并不会直接连接到实际的数据源,没有和实际的输入和输出节点连接。
使用处理流的一个明显的好处是,只要使用相同的处理流,程序就可以采用完全相同的输入/输出代码来访问不同的数据源
打个比方在进一步理解流的概念
java把所有设备里的有序数据抽象成流模型,简化了输入/输出处理。java IO流共涉及40多个类,这些类看上去很杂乱,但实际上很有规则,而且彼此之间存在非常紧密的联系, Java Io流的40多个类都是从如下4个抽象类基类中派生出来的。
InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。
对于InputStream和Reader而言,它们把输入设备抽象成为一个”水管“,这个水管的每个“水滴”依次排列,如图
输入流使用隐式的记录指针来表示当前正准备从哪个“水滴”开始读取,每当程序从InputStream或者Reader里面取出一个或者多个“水滴”后,记录指针自定向后移动;除此之外,InputStream和Reader里面都提供了一些方法来控制记录指针的移动。
对于OutputStream和Writer而言,它们同样把输出设备抽象成一个”水管“,只是这个水管里面没有任何水滴,如图
当执行输出时,程序相当于依次把“水滴”放入到输出流的水管中,
输出流同样采用隐示指针来标识当前水滴即将放入的位置,
每当程序向OutputStream或者Writer里面输出一个或者多个水滴后,记录指针自动向后移动。
处理流可以“嫁接”在任何已存在的流的基础之上,这就允许Java应用程序采用相同的代码,
透明的方式来访问不同的输入和输出设备的数据流。
字节流和字符流的基类方法
IO体系的基类(InputStream/Reader,OutputStream/Writer)
字节流和字符流的操作方式基本一致,
只是操作的数据单元不同——字节流的操作单元是字节,字符流的操作单元是字符。
所以字节流和字符流就整理在一起了。
InputStream/Reader,OutputStream/Writer是所有输入/输出流的抽象基类,
本身并不能创建实例来执行输入/输出,
但它们将成为所有输入/输出流的模板,
所以它们的方法是所有输入/输出流都可使用的方法。
在InputStream里面包含读数据的方法。
int read(); 从输入流中读取单个字节,返回所读取的字节数据(字节数据可直接转换为int类型)。
int read(byte[] b) 从输入流中最多读取b.length个字节的数据,并将其存储在字节数组b中,返回实际读取的字节数。
int read(byte[] b,int off,int len); 从输入流中最多读取len个字节的数据,并将其存储在数组b中,放入数组b中时,并不是从数组起点开始,而是从off位置开始,返回实际读取的字节数。
在Reader中包含也有如下3个方法。
int read(); 从输入流中读取单个字符,返回所读取的字符数据(字符数据可直接转换为int类型)。
int read(char[] b) 从输入流中最多读取b.length个字符的数据,并将其存储在字节数组b中,返回实际读取的字符数。
int read(char[] b,int off,int len); 从输入流中最多读取len个字符的数据,并将其存储在数组b中,放入数组b中时,并不是从数组起点开始,而是从off位置开始,返回实际读取的字符数。
InputStream和Reader提供的一些移动指针的方法:
void mark(int readlimit ); 在记录指针当前位置记录一个标记(mark)。
boolean markSupported(); 判断此输入流是否支持mark()操作,即是否支持记录标记。
void reset(); 将此流的记录指针重新定位到上一次记录标记(mark)的位置。
long skip(long n); 记录指针向前移动n个字节/字符。
OutputStream和Writer:
OutputStream和Writer的用法也非常相似,两个流都提供了如下三个方法:
void write(int c); 将指定的字节/字符输出到输出流中,其中c即可以代表字节,也可以代表字符。
void write(byte[]/char[] buf); 将字节数组/字符数组中的数据输出到指定输出流中。
void write(byte[]/char[] buf, int off,int len ); 将字节数组/字符数组中从off位置开始,长度为len的字节/字符输出到输出流中。
因为字符流直接以字符作为操作单位,所以Writer可以用字符串来代替字符数组,即以String对象作为参数。
Writer里面还包含如下两个方法:
void write(String str); 将str字符串里包含的字符输出到指定输出流中。
void write (String str, int off, int len); 将str字符串里面从off位置开始,长度为len的字符输出到指定输出流中。
要死记硬背的读流方法
1.字符串流,把字符串转为输入流,读输入流,输出流转化为字符串数据,可打印,
public class StringReaderTest {
public static void main(String[] args) throws IOException {
String s = "study 5 hard and 6 make 11 progress every day";
StringReader sr=new StringReader(s); //把字符串转为字符串输入流
StringWriter sWriter = new StringWriter(); //想打印流中的数据,我们用字符串输出流的方法来转换字符串
char[] c = new char[1024];
int hasRead = 0;
while((hasRead=sr.read(c))!=-1){
sWriter.write(c,0,hasRead);
}
System.out.println(sWriter.toString());
}
}
2.文件流,读一个文件中的数据流(转为输入流再读),输出流写到另一个文件中。
public class inputstream {
public static void main(String[] args) throws IOException {
//把在c盘上一个文件读出来,如果文件是图片,视频,音频,要用字节流,文本文件,字节流,字符流都可用
// InputStream is =new FileInputStream("C:\\a.txt");
/*FileInputStream fis=null;
try{
fis = new FileInputStream("c:\\a.txt");
//定义两个东西,一个水漂-数组,一个计数器
byte[] b = new byte[1024];
int hasRead = 0;
while((hasRead=fis.read(b))>0){
System.out.println(new String(b,0,hasRead));
}
}catch(Exception e){
e.printStackTrace();
}finally {
//为什么必须有finally块呢?
fis.close();
}
}*/
//try语句中不用关闭流
try(FileInputStream fis = new FileInputStream("c:\\a.txt");
FileOutputStream fos = new FileOutputStream("c:\\b.txt")){
//定义两个东西,一个水漂-数组,一个计数器
byte[] b = new byte[1024];
int hasRead = 0;
while((hasRead=fis.read(b))>0){
fos.write(b);
}
}catch(Exception e){
e.printStackTrace();
}
}
}
输入输出流的体系
java输入/输出流体系中常用的流的分类
访问文件,数组,管道,字符串都是节点流,除了基类,节点流,其它是处理流类,处理流要与它结合起来才起作用。
下面是整理出这些IO流的特性及使用方法,只有清楚每个IO流的特性和方法。才能在不同的需求面前正确的选择对应的IO流进行开发。
文件输入输出流
<1.1>文件输入流
FileInputStream和FileReader,
它们都是节点流——会直接和指定文件关联。
public class inputstream {
public static void main(String[] args) throws IOException {
//把在c盘上一个文件读出来,如果文件是图片,视频,音频,要用字节流,文本文件,字节流,字符流都可用
// InputStream is =new FileInputStream("C:\\a.txt");
FileInputStream fis=null;
try{
fis = new FileInputStream("c:\\a.txt");
//定义两个东西,一个水漂-数组,一个计数器
byte[] b = new byte[1024];
int hasRead = 0;
while((hasRead=fis.read(b))>0){
System.out.println(new String(b,0,hasRead));
}
}catch(Exception e){
e.printStackTrace();
}finally {
//为什么必须有finally块呢?
fis.close();
}
}
}
注:上面程序最后使用了fis.close()来关闭该文件的输入流,程序里面打开的文件IO资源不属于内存的资源,
垃圾回收机制无法回收该资源,所以必须在程序里关闭打开的IO资源。
Java 7改写了所有的IO资源类,它们都实现了AntoCloseable接口,因此都可以通过自动关闭资源的try语句来关闭这些IO流。
改进后的代码如下,不要finally块了
public class inputstream {
public static void main(String[] args) {
try(FileInputStream fis = new FileInputStream("c:\\a.txt")){
//定义两个东西,一个水漂-数组,一个计数器
byte[] b = new byte[1024];
int hasRead = 0;
while((hasRead=fis.read(b))>0){
System.out.println(new String(b,0,hasRead));
}
}catch(Exception e){
e.printStackTrace();
}
}
}
字符流读取示例:
public class ReaderDemo {
public static void main(String[] args) throws IOException {
FileReader fileReader = new FileReader("C:\\a.txt");
int read;
while ((read = fileReader.read()) != -1) {
//字符流,可以直接输出
System.out.print((char)read);
}
fileReader.close();
}
}
<1.2>文件输出流
FileOutputStream/FileWriter
try(FileInputStream fis = new FileInputStream("c:\\a.txt");
FileOutputStream fos = new FileOutputStream("c:\\b.txt")){
//定义两个东西,一个水漂-数组,一个计数器
byte[] b = new byte[1024];
int hasRead = 0;
while((hasRead=fis.read(b))>0){
fos.write(b);
}
}catch(Exception e){
e.printStackTrace();
}
}
}
数组输入输出流
数组流:实质上是和内存中的数组相关的一个流,可以将字节数组写到输出流中,也可以将字节数组从输入流中读出来,不涉及磁盘。可以看作是btye[]/char[]和输入/输出流之间的一个数据形式的转换!
应用场景:例,我有一个程序调用第三方的类中的方法,该方法中有流参数时。
<2.1>输入流
ByteArrayinputStream/CgarArrayReader
public class arraystream {
public static void main(String[] args) throws IOException {
byte[] b1=new byte[]{1,2,3,4};
ByteArrayInputStream bais = new ByteArrayInputStream(b1); //把数组变成流了
System.out.println("初始时:剩余"+bais.available()+"个字节");
//收流
byte[] b2= new byte[2];
bais.read(b2);
System.out.println(Arrays.toString(b2));
System.out.println("读取一次后,输入流里剩下的数据是: "+bais.available()+"个字节");
//再读一次,流里没有字节了
}
}
--------------------------------------------------------------------
初始时:剩余4个字节
[1, 2]
读取一次后,输入流里剩下的数据是: 2个字节
<2.2>输出流
ByteArrayOutputStream/CharArrayWriter
数组输出流,将数据形式转换以后,还是一个可自动扩容的 byte 数组,默认初始化 32 个字节的大小,最大容量是 2^31-9 个字节(2G)。只要数据不超过2G,都可以往里写。每次写数据之前,会先计算需要的容量大小,如果需要扩容,扩大到 max{原来的两倍,需要的容量大小}
public class arraystream {
public static void main(String[] args) throws IOException {
//数组输出流
byte[] b1= new byte[]{1,2,3,4};
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write(b1);
byte[] b2=baos.toByteArray();
System.out.println(Arrays.toString(b2));
FileOutputStream fos = new FileOutputStream("c:\\c.txt");
baos.writeTo(fos); //把流转换成其它流了,但不经过处理,c.txt中是乱码
}
}
为了演示扩容情况,我们设重写数组输出流类,利用getBuf来实时测缓冲区长度(也是返回buf[]的长度)
public class arraystream {
public static void main(String[] args) throws IOException {
MyByteArrayOutputstream out = new MyByteArrayOutputstream();
//默认情况下输出流的buf的长度是多少呢,是32个字节
System.out.println("默认情况下输出流的buf的长度是多少呢?是"+out.getBuf().length);
System.out.println("当前用了:"+out.size());
for(int i=0;i<33;i++) {
out.write(1); //写33个长度的数据
}
System.out.println("超容扩容后:"+out.getBuf().length);
System.out.println("当前用了:"+out.size());
out.write(new byte[98]); //这次扩容不是两倍扩了,是直接有多少扩多少,是131长度的容量了。
System.out.println("超容扩容后:"+out.getBuf().length);
System.out.println("当前用了:"+out.size());
}
}
class MyByteArrayOutputstream extends ByteArrayOutputStream{
public byte[] getBuf() {
return super.buf;
}
}
---------------------------------------------------------------------------
默认情况下输出流的buf的长度是多少呢?是32
当前用了:0
超容扩容后:64
当前用了:33
超容扩容后:131
当前用了:131
管道流的使用
管道流是用来在多个线程之间进行信息传递的Java流。
管道流分为字节流管道流和字符管道流。
字节管道流:PipedOutputStream 和 PipedInputStream。
字符管道流:PipedWriter 和 PipedReader。
PipedOutputStream、PipedWriter 是写入者/生产者/发送者;
PipedInputStream、PipedReader 是读取者/消费者/接收者。
java的管道输入与输出实际上使用的是一个循环缓冲数组来实现的。输入流PipedInputStream从这个循环缓冲数组中读数据,输出流PipedOutputStream往这个循环缓冲数组中写入数据。当这个缓冲数组已满的时候,输出流PipedOutputStream所在的线程将阻塞;当这个缓冲数组为空的时候,输入流PipedInputStream所在的线程将阻塞。
在使用管道流之前,需要注意以下要点:
1.管道流仅用于多个线程之间传递信息,若用在同一个线程中可能会造成死锁;
2.管道流的输入输出是成对的,一个输出流只能对应一个输入流,使用构造函数或者connect函数进行连接;
3.一对管道流包含一个缓冲区,其默认值为1024个字节,若要改变缓冲区大小,可以使用带有参数的构造函数;
4.管道的读写操作是互相阻塞的,当缓冲区为空时,读操作阻塞;当缓冲区满时,写操作阻塞;
5.管道依附于线程,因此若线程结束,则虽然管道流对象还在,仍然会报错“read dead end”;
6.管道流的读取方法与普通流不同,只有输出流正确close时,输出流才能读到-1值,而文件流普通流读完直接返回-1。
public class PipeStream {
public static void main(String[] args) throws IOException {
//创建线程池,队列为5,
ThreadPoolExecutor executor = new ThreadPoolExecutor(6,12,200,TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(5));
//先声明管道输出流
PipedOutputStream pout = new PipedOutputStream();
PipedInputStream pin= new PipedInputStream(pout); //异常直接丢出,这里时把输出流,输入流连成一对
Sender sender = new Sender(pout);
Receiver receiver = new Receiver(pin);
executor.execute(sender);
executor.execute(receiver);
executor.shutdown();
try {
executor.awaitTermination(1, TimeUnit.HOURS); //一小时后,线程池没有结束,强制结束
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//先写生产者线程类,输出流
class Sender implements Runnable{
private PipedOutputStream pout;
public Sender(PipedOutputStream pout) { //构造函数
this.pout = pout;
}
@Override
public void run() {
try {
String s = "生产者发送测试数据";
byte[] b=s.getBytes();
pout.write(b); //发送信息
pout.close();
System.out.println("生产者发送的信息:"+s);
}catch(IOException ex) {
ex.printStackTrace();
}
}
}
//接爱者,消费者线程,输入流
class Receiver implements Runnable{
private PipedInputStream pin;
public Receiver(PipedInputStream pin) {
this.pin = pin;
}
@Override
public void run() {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
byte[] b = new byte[1024];
int hasRead =0;
while((hasRead=pin.read(b))!=-1) {
//处理数据
baos.write(b,0,hasRead);
}
byte[] rs = baos.toByteArray();
String s =new String(rs,0,rs.length);
System.out.println("接收到的信息为:"+s);
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
--------------------------------------------------------------------------------------------
生产者发送的信息:生产者发送测试数据
接收到的信息为:生产者发送测试数据
字符串流的使用
定义:字符串流,以一个字符串为数据源,来构造一个字符流。
作用:在Web开发中,客户端经常要从服务器端上获取数据,
数据返回的格式通过一个字符串(XML、JSON),网络间的数据交互需要用到流,这个时候我们需要把这个字符串构造为一个字符流,才能在网络上传出去。
本质上,我们也可以像数组流那样去理解,字符串流就是字符串和流之间的转换!
package cn.ybzy.io;
import java.io.*;
//字符串流,不是字符流
public class StringWriterTest {
public static void main(String[] args) {
String s="XIONGSHAOWEN XUHUIFENG,XIONGLINZHOU XIONGLINXIAO";
StringWriter sw =new StringWriter();
sw.write(s);
sw.append(" xiongshaowen");
sw.append(" qiuhehua");
sw.flush();
StringBuffer sBuffer=sw.getBuffer();
String sws= sw.toString();
System.out.println(sBuffer);
System.out.println(sws);
}
}
StringReader的使用举例
public class StringReaderTest {
public static void main(String[] args) throws IOException {
String s = "study 5 hard and 6 make 11 progress every day";
StringReader sr=new StringReader(s); //把字符串转为字符串输入流
StringWriter sWriter = new StringWriter(); //想打印流中的数据,我们用字符串输出流的方法来转换字符串
char[] c = new char[1024];
int hasRead = 0;
while((hasRead=sr.read(c))!=-1){
sWriter.write(c,0,hasRead);
}
System.out.println(sWriter.toString());
}
}
类java.io.StreamTokenizer
可以获取字符串输入流并将其分析为Token(标记)。有四个,返回值为int型,TT_EOF是字符串结尾,TT_EOL是行尾,TT_NUMBER是数字标记,TT_WORD是标记为单词.
public class StreamTokenizerTest {
public static void main(String[] args) throws IOException {
String s = "Study java 8.0 and 11.0 every day!";
StringReader sReader =new StringReader(s);
StreamTokenizer st =new StreamTokenizer(sReader);
int numCount = 0;
int wordCount = 0;
while(st.nextToken()!=StreamTokenizer.TT_EOF) {
int flagType=st.ttype;
if(flagType == StreamTokenizer.TT_NUMBER) {
System.out.println(st.nval);
numCount++;
}else if(flagType==StreamTokenizer.TT_WORD) {
System.out.println(st.sval);
wordCount++;
}
}
System.out.println("整个字符串中有数字"+numCount+"个");
System.out.println("整个字符串中有单词"+wordCount+"个");
}
}
Study
java
8.0
and
11.0
every
day
整个字符串中有数字2个
整个字符串中有单词5个
处理流
处理流也没啥复杂的,只不过是把普通流转一下而起。如缓冲流,读写文件,就是处理四个流的问题。
1.缓冲流
字节缓冲流的用法
BufferedInputStream继承于FilterInputStream,提供缓冲输入流功能。缓冲输入流相对于普通输入流的优势是,它提供了一个缓冲数组,每次调用read方法的时候,它首先尝试从缓冲区里读取数据,若读取失败(缓冲区无可读数据),则选择从物理数据源(譬如文件)读取新数据,最后再将缓冲区中的内容部分或全部返回给用户,好处从缓冲区里读取数据远比直接从物理数据源(譬如文件)读取速度快。
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class BufferedStreamTest {
public static void main(String[] args) {
FileInputStream fis =null;
FileOutputStream fos = null;
BufferedInputStream bfis = null;
BufferedOutputStream bfos = null;
try {
fis = new FileInputStream("c:\\a.txt");
fos = new FileOutputStream("C:\\b.txt");
bfis = new BufferedInputStream(fis);
bfos = new BufferedOutputStream(fos);
byte[] b= new byte[1024];
int hasRead = 0;
while((hasRead=bfis.read(b))!=-1) {
bfos.write(b);
}
}catch(Exception e) {
e.printStackTrace();
}finally {
if(bfis!=null) {
try {
bfis.close();
}catch(IOException ex) {
ex.printStackTrace();
}
}
if(bfos!=null) {
try {
bfos.close();
}catch(IOException ex) {
ex.printStackTrace();
}
}
}
}
}
可以看到使用字节缓冲流读取和写入数据的方式和文件流(FileInputStream,FileOutputStream)并没有什么不同,
只是把处理流套接到节点流(文件流)上进行读写。
注意:
上面代码中我们使用了缓存流和文件流,但是我们只关闭了缓冲流。
这个需要注意一下,当我们使用处理流套接到节点流上的使用的时候,只需要关闭最上层的处理就可以了。java会自动帮我们关闭下层的节点流。
2.转换流
转换流的使用(InputStreamReader/OutputStreamWriter):
转换流的特点:
- 其是字符流和字节流之间的桥梁
- 可对读取到的字节数据经过指定编码转换成字符,转换的时候还可以指定我们需要字符的编码方式
- 可对读取到的字符数据经过指定编码转换成字节
在IDEA(eclipse)中,使用FileReader 读取项目中的文本文件。由于IDEA的设置,都是默认的UTF-8编码,所以没有任何问题。但是,当读取Windows系统中创建的文本文件时,由于Windows系统的默认是GBK编码(以记事本为例,保存默认不是UTF-8格式),就会出现中文乱码。
public class ReaderDemo {
public static void main(String[] args) throws IOException {
FileReader fileReader = new FileReader("C:\\File_GBK.txt");
int read;
while ((read = fileReader.read()) != -1) {
//字符流,可以直接输出
System.out.print((char)read);
}
fileReader.close();
}
}
-------------------------------------------------------------------------
XIONGSHAOWEN
������
那么如何读取GBK编码的文件呢?
InputStreamReader类
转换流java.io.InputStreamReader,是Reader的子类,是从字节流到字符流的桥梁
。它读取字节,并使用指定的字符集将其解码为字符。它的字符集可以由名称指定,也可以接受平台的默认字符集。
构造方法
InputStreamReader(InputStream in): 创建一个使用默认字符集的字符流。
InputStreamReader(InputStream in, String charsetName): 创建一个指定字符集的字符流。
指定编码读取
public class FileInputStreamTest {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("c:\\File_GBK.txt");
InputStreamReader isr=new InputStreamReader(fis,"GBK");
int read;
while((read=isr.read())!=-1) {
System.out.println((char)read);
}
isr.close();
}
}
--------------------------------------------------------------------------------
X
I
O
N
G
S
H
A
O
W
E
N
熊
少
文
OutputStreamWriter类
转换流java.io.OutputStreamWriter ,是Writer的子类,是从字符流到字节流的桥梁
。使用指定的字符集讲字符编码为字节。它的字符集可以由名称指定,也可以接受平台的默认字符集。
注意: 输出流与输入流的转换方向相反。
构造方法
OutputStreamWriter(OutputStream in): 创建一个使用默认字符集的字符流。
OutputStreamWriter(OutputStream in, String charsetName): 创建一个指定字符集的字符流。
练习:转换文件编码
将GBK编码的文本文件,转换为UTF-8编码的文本文件。
指定GBK编码的转换流,读取文本文件。
使用UTF-8编码的转换流,写出文本文件。
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
//把GBK文件转为UTF-8格式文件,相当于,记事本另存为UTF-8,File_GBK.txt是GBK,File_UTF8.txt是UTF-8格式,
public class OutputStreamWriterTest {
public static void main(String[] args) {
//没写编码格式,是默认工具的编码格式,我的idea工具设置了utf-8格式编码
try(InputStreamReader isr = new InputStreamReader(new FileInputStream("c:\\File_GBK.txt"),"GBK");
OutputStreamWriter osw =new OutputStreamWriter(new FileOutputStream("c:\\File_UTF8.txt"))) {
char[] c = new char[1024];
int hasRead = 0;
while((hasRead=isr.read(c))!=-1) {
osw.write(c,0,hasRead);
}
}catch(Exception e) {
e.printStackTrace();
}
}
}
对象流
ObjectInputStream/ObjectOutputStream 的使用:对象流是对象序列化的工具。
所谓的对象的序列化就是将对象转换成二进制数据流的一种实现手段,通过将对象序列化,可以方便的实现对象的传输及保存。
对象流的用处非常单一又专业,只用于序列化和反序列化。一般应用中我们是通过在内存中转换,持久化对象到数据库或临时访问。
一些注意事项 :
1.读取顺序和写入顺序一定要一致,不然会读取出错。
2.在对象属性前面加transient关键字,则该对象的属性不会被序列化。
class Person implements Serializable{
public String name;
public transient int age; //如果加了transient,则该字段不被序列化
public String sex;
}
例:把Person一个对象,保存到c:\Person.temp中
public class ObjectStream {
public static void main(String[] args) {
Person person = new Person();
person.name="熊少文";
person.age=42;
person.sex="男";
//对象序列化工具对象流之输出流,可以把person对象,变成二进制的数据保存到磁盘中。
ObjectOutputStream oout = null;
try {
//首先:转化为二进制输出流
FileOutputStream out = new FileOutputStream("c:\\Person.temp");
//进一步封装成缓冲流
BufferedOutputStream bout = new BufferedOutputStream(out);
//再一步封装成对象流
oout= new ObjectOutputStream(bout);
//输出到磁盘文件中
oout.writeObject(person);
}catch(IOException ex) {
ex.printStackTrace();
}finally {
if(oout!=null) { //只要关闭顶层流,其它流java自动关闭其它流
try {
oout.close();
}catch(IOException e) {
e.printStackTrace();
}
}
}
}
}
class Person implements Serializable{
public String name;
public int age;
public String sex;
}
例:从Person.temp中读取对象反序列化到内存中,输出结果看看
....
ObjectInputStream ois = null;
try {
FileInputStream fis =new FileInputStream("c:\\Person.temp");
BufferedInputStream bis = new BufferedInputStream(fis);
ois = new ObjectInputStream(bis);
Person person2= (Person)ois.readObject();
System.out.println(person2.name+","+person2.age+","+person2.sex);
}catch(IOException ex) {
ex.printStackTrace();
}finally {
if(ois!=null) {
ois.close();
}
}
}
}
class Person implements Serializable{
public String name;
public int age;
public String sex;
}