IO(Input Output)流
一、IO流用来处理设备之间的数据传输。Java对数据的操作通过流的形式,用于操作流的对象都在IO包中。流按操作数据分为两种:字节流和字符流。
ASCII码表:美国信息标准交换码。英文字母与十进制数字的映射
GB2312码表,GBK,18030:中国的文字与二进制数字的映射表
Unicode码表,utf-8:将各个国家的文字重新编排的映射表
字符流的对象里融合了编码表,访问使用什么样的编码表可以来指定
二、IO流常用基类
字节流的抽象基类:InputStream,OutputStream
字符流的抽象基类:Reader,Writer
注意:由这四个类派生出来的子类名称都是以其父类名称作为子类名的后缀。如:InputStream的子类FileputStream;Reader的子类FileReader
三、FileWrite类的基本用法
代码:public class FileWriteDemo {
public static void main(String[] args) throws IOException {
//第一步创建一个FileWriter对象,该对象一旦被初始化就必须要明确被操作的文件
//而且该文件会被创建到指定目录下。如果该目录下已有同名文件,原有文件将被覆盖
//其实该步就是在明确数据要存放的位置
FileWriter fw=new FileWriter("demo.txt");
//第二步调用write方法,将字符串写入到流中,此时demo.txt中还没有内容
fw.write("abcde");
//第三步刷新流对象中缓冲的数据
fw.flush();//此时打开demo.txt文件,abcde已被写入
//还可以继续添加内容,但每次添加都要刷新
fw.write("kjls");
fw.flush();//这时文件中的内容为:abcdekjls
//close方法关闭流资源,关闭之前会刷新一次内部缓冲中的数据,将数据刷到目的地中
//与flush的区别:flush刷新后流可以继续使用,close刷新后会将流关闭。
fw.close();
}
}
四、 IO流的异常处理 :因为它的每句话都有异常抛出,但是之间有关联,所以应放在一个try代码块中
public class FileWriterDemo1 {
public static void main(String[] args) {
//此对象的声明应放在外面,因为如果放在try代码块中, catch和finally中的代码块
//无法访问到。所以应在外面建立引用,在try中初始化
FileWriter fw=null;
try
{
//FileWriter fw=new FileWriter("demo.txt")
fw=new FileWriter("demo.txt");
fw.write("adfdfjd");
}
catch(IOException e)
{
System.out.println("catch:"+e.toString());
}
finally
{
//关闭资源的代码是一定要执行的应放在finally代码块中,不要忘记对这句话try—catch
try
{
if(fw!=null)//因为如果fw为空,不能调用close。要避免这种错误一定要写上这句话
fw.close();
}
catch(IOException e)
{
System.out.println(e.toString());
}
}
}
}
五、对已有的文件的数据续写
//传递一个true参数,代表不覆盖已有的文件。并在已有文件的末尾处进行数据的续写
fw=new FileWriter("demo.txt",true);
//写入时如果想要换行,在Windows系统下用\r\n
fw.write("hailou\r\nxieixe");
六、读取文件的操作:第一种方式,read()方法,一次读取一个字符。代码示例://创建一个文件读取流对象,和指定名称的文件相关联
//要保证该文件是已经存在的,如果不存在,会发生FileNotFoundException异常
FileReader fr=new FileReader("demo.txt");
//调用读取流对象的read方法
//read():一次读一个字符。而且会自动往下读,返回字符的int类型,如果读到文件末尾则返回-1
int ch=0;
while((ch=fr.read())!=-1)
{
System.out.println("ch="+(char)ch);
}
第二种读取文件的方式:通过字符数组进行读取,read(char[] cbuf),将字符读入数组。代码示例:
FileReader fr=null;
try {
fr = new FileReader("demo.txt");
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//定义一个字符数组,用于存储读到字符。
//该read(char[] buf)返回的是读到字符个数,当读到文件末尾没有内容时返回-1
char[] buf=new char[1024];//数组长度一般定义1024的倍数
int num=0;
try {
while((num=fr.read(buf))!=-1)
{
System.out.println(new String(buf,0,num));
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
fr.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
七、将E盘的一个文本文件复制到F盘,有两种方法,较好的一种通过字符数组。另一种一个一个读取字符
复制的原理:
其实就是将E盘下的文件数据存储到F盘的一个文件中
步骤:
1,在F盘创建一个文件,用于存储E盘文件中的数据。
2,定义读取流和E盘文件关联
3,通过不断的读写完成
4,关闭所有资源
public class FileReaderText2 {
public static void main(String[] args) {
//copy_1();
copy_2();
}
public static void copy_1()
{
FileWriter fw=null;
FileReader fr=null;
try
{
fw=new FileWriter("F:\\FileReaderDemo_copy.txt");
fr=new FileReader("FileReaderDemo.java");
int len=0;
char[] buf=new char[1024];
while((len=fr.read(buf))!=-1)
{
fw.write(buf, 0, len);
}
}
catch(IOException e)
{
throw new RuntimeException("读写失败");
}
finally
{
try
{
if(fr!=null)
fr.close();
} catch (Exception e) {
// TODO: handle exception
}
try
{
if(fw!=null)
fw.close();
} catch (Exception e) {
// TODO: handle exception
}
}
}
八、字符流的缓冲区:缓冲区的出现提高了对数据的读写效率
对应类:BufferedWriter(对Writer中的子类对象进行功能增强的缓冲区)和BufferedReader(对Reader中的子类对象进行功能增强的缓冲区)
缓冲区要结合流才可以使用,在流的基础上对流的功能进行了增强
//缓冲区的出现是为了提高流的操作效率,所以在建缓冲区之前,必须要现有流对象
//该缓冲区中提供了一个跨平台的换行符
import java.io.*;
public class BufferedWriterDemo {
public static void main(String[] args) throws Exception {
//首先,创建一个字符写入流对象
FileWriter fw=new FileWriter("buf.txt");
//为提高字符写入流效率,加入了缓冲技术 ,只要将需要被提高
//效率的流对象作为参数传递给缓冲区的构造函数即可
BufferedWriter bufw=new BufferedWriter(fw);
bufw.write("abcde");
//记住,只要用到缓冲区,就要记得刷新
bufw.newLine();
for(int x=1;x<5;x++)
{
bufw.write("ooo"+x);
bufw.newLine();
bufw.flush();
}
bufw.flush();
//关闭缓冲区就是关闭缓冲区中的流对象
bufw.close();
}
}
//字符读取缓冲区:该缓冲区提供了一个一次读取一行的方法readLine()
//当读到文件末尾时,返回null。readLine()返回的时候只返回回车符之前的内容,回车符不被包含
public class BufferedReaderDemo {
/**
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
//创建一个读取流对象和文件相关联
FileReader fr=new FileReader("buf.txt");
//为了提高读取效率,加入缓冲技术。将字符读取流对象
//作为参数传递给缓冲区的对象的构造函数
BufferedReader bufr=new BufferedReader(fr);
//缓冲区对象使用reaLine方法如果读到文件末尾,则返回null
String line=null;
//bufr.readLine()一次读取一行
while((line=bufr.readLine())!=null)
{
System.out.println(line);
}
bufr.close();
}
}
九、使用缓冲区拷贝文件代码示例
public class CopyTextByBuffer {
public static void main(String[] args) {
BufferedReader bufr=null;
BufferedWriter bufw=null;
try
{
bufr=new BufferedReader(new FileReader("BufferedReaderDemo.java"));
bufw=new BufferedWriter(new FileWriter("bufRead_copy.txt"));
String line=null;
while((line=bufr.readLine())!=null)
{
bufw.write(line);
//记住换行
bufw.newLine();
bufw.flush();
}
}
catch(IOException e)
{
throw new RuntimeException("读写失败");
}
finally
{
try
{
if(bufr!=null)
bufr.close();
}
catch(IOException e)
{
throw new RuntimeException("读取失败");
}
try
{
if(bufw!=null)
bufw.close();
}
catch(IOException e)
{
throw new RuntimeException("写入失败");
}
}
}
}
十、模仿BufferedReader中的特有方法readLine方法,自定义一个类包含与此方法功能相同的一个方法
代码演示:
class MyBufferedReader
{
//因为BufferedReader中readLine方法底层就是调用的FileReader的read方法
//首先让MyBufferedReader这个模拟类一初始化就可以有一个流对象进来
private FileReader r;
MyBufferedReader(FileReader r)
{
this.r=r;
}
public String myReadLine()throws IOException
{
int ch=0;
//因为readLine方法是通过将一个个的字符存入数组,再将字符数组转换成字符串得到一行的
//因此为了简便,在这使用StringBuilder容器
StringBuilder sb=new StringBuilder();
while((ch=r.read())!=-1)
{
//windows下的换行符是\r\n,
if(ch=='\r')
continue;//如果读到\r,则不执行下面代码继续读下一个字符应该是\n
if(ch=='\n') //如果读到\n说明一行已经完了,则返回换行符之前存入的内容
return sb.toString();
else
sb.append((char)ch);
}
//当读到文件的最后一行时,最后一行可能没有换行符,但是内容已经
//被存入到sb中,只不过没有被返回来。所以最后判断缓冲区sb的长度如果不等于0
if(sb.length()!=0)
return sb.toString();
//如果读到文件末尾返回null
return null;
}
public void myClose()throws IOException
{
r.close();
}
}
public class MyBufferedReaderDemo {
public static void main(String[] args) {
MyBufferedReader mybuf=null;
String line=null;
try
{
mybuf=new MyBufferedReader(new FileReader("buf.txt"));
while((line=mybuf.myReadLine())!=null)
{
System.out.println(line);
}
} catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
finally
{
try
{
if(mybuf!=null)
mybuf.myClose();
}
catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
十一、java的io包中有一个 LineNumberReader继承了BufferedReader,增加了两个方法可以设置行号和获取行号。现在模拟该类得到具有同样功能的类
代码示例:
package myday1803;
import java.io.*;
//模拟一个带行号的缓冲区对象,即:模拟LineNumberReader方法
class MylineNumber1
{
private int lineNumber;
private Reader r;
MylineNumber1(Reader r)
{
this.r=r;
}
//模拟readLine方法
public String myReadLine()throws IOException
{
//读取完一行,行号加一
lineNumber++;
StringBuilder sb=new StringBuilder();
int ch=0;
while((ch=r.read())!=-1)
{
if(ch=='\r')
continue;
if(ch=='\n')
return sb.toString();
else
sb.append((char)ch);
}
if(sb.length()!=0)
return sb.toString();
return null;
}
//设置行号
public void setLineNumber(int lineNumber)
{
this.lineNumber=lineNumber;
}
//获取行号
public int getLineNumber()
{
return lineNumber;
}
public void myClose() throws IOException
{
r.close();
}
}
public class MylineNumberReader1 {
public static void main(String[] args) {
MylineNumber1 mylnr=null;
try
{
mylnr=new MylineNumber1(new FileReader("BufferedReaderDemo.java"));
String line=null;
while((line=mylnr.myReadLine())!=null)
{
System.out.println(mylnr.getLineNumber()+":"+line);
}
}
catch(IOException e)
{
throw new RuntimeException("读写失败");
}
finally
{
try
{
if(mylnr!=null)
mylnr.myClose();
}
catch(IOException e)
{
throw new RuntimeException("关闭失败");
}
}
}
}
十二、以上都是字符流的用法,下面开始记录字节流的内容
字节流的写入:OutputStream 不需要刷新 写都是Output
字节流的读取:InputStream 有三种方式,除了像字符流一样一个一个的或者通过数组读取外。还有一种FileInputStream特有的方法可以读取。
public static void main(String[] args) throws IOException {
//writeFile();
//readFile_1();
//readFile_2();
readFile_3();
}
//通过字节数组读取
public static void readFile_1() throws IOException
{
FileInputStream fis=new FileInputStream("fos.txt");
byte[] buf=new byte[1024];
int len=0;
while((len=fis.read(buf))!=-1)
{
System.out.println(new String(buf,0,len));
}
}
//一个字节一个字节的读取
public static void readFile_2()throws IOException
{
FileInputStream fis=new FileInputStream("fos.txt");
int ch=0;
while((ch=fis.read())!=-1)
{
System.out.println((char)ch);
}
}
//通过FileInputStream的特有方法abailable()来获取文件的长度
public static void readFile_3()throws IOException
{
FileInputStream fis=new FileInputStream("fos.txt");
byte[] buf=new byte[fis.available()];//这种方式不如第一种好,因为有可能内存没有文件大导致内存溢出
fis.read(buf);
System.out.println(new String(buf));
}
//写
public static void writeFile() throws IOException
{
FileOutputStream fos=new FileOutputStream("fos.txt");
String s="hddsoncsj";
fos.write(s.getBytes());
fos.close();
}
十三、拷贝一张图片,代码示例
/拷贝一张图片
public class CopyPictureText {
public static void main(String[] args) {
FileOutputStream fos=null;
FileInputStream fis=null;
try
{
fos=new FileOutputStream("c:\\GUI__copy.PNG");
fis=new FileInputStream("c:\\GUI.PNG");
byte[] buf=new byte[1024];
int len=0;
while((len=fis.read(buf))!=-1)
{
fos.write(buf,0,len);
}
}
catch(IOException e)
{
throw new RuntimeException("读写失败");
}
finally
{
try
{
if(fos!=null)
fos.close();
}
catch(IOException e)
{
throw new RuntimeException("关闭失败");
}
try
{
if(fis!=null)
fis.close();
}
catch(IOException e)
{
throw new RuntimeException("关闭失败");
}
}
}
}
十四、字节流:FileInputStream FileOutputStream
BufferedInputStream BufferedOutputStream
模拟BufferedInputStream写一个含有与其read方法功能一致的方法的类
BufferedInputStream中read方法的原理:在文件里,通过fileInputStream的read[buf]方法,读一批数据存入字节数组缓冲区buf中,通过BufferedInputStream的read()方法从缓冲区buf中一个一个读取。当取完了,在从文件中读下一批换中去存入字节数组中,重复上述过程,知道将文件读取完。
public class MyBufferedInputStream
{
private InputStream ins;
private byte[] buf=new byte[1024];//定义缓冲区数组
private int pos=0;//定义指针
private intcount=0;//定义计数器
MyBufferedInputStream(InputStream ins)
{
this.ins=ins;
}
//为什么不返回一个字节而是返回整型?
/*一个字节byte8位,如果一个文件的第一个字节就是11111111即-1,那么该方法就直接
返回-1不再往下执行了。返回int类型将byte类型提升了,但是遇到第一个字节是-1还是-1;
是-1的原因是在八个1前面补1导致的,那么在八个1前面补0,既可以保留原来字节数不变,
又可以避免-1的出现。那么如何补0呢?与上255即可
11111111 11111111 11111111 11111111(与)
00000000 00000000 00000000 11111111*/
public int myRead() throws IOException
{
//如果计数器等于0,说明buf中的元素已经被取光可重新从文件中读取数据存储到buf中
if(count==0)
{
//通过ins对象读取硬盘上的数据并存储到buf中,计数器的值为存入缓冲区中的总的字节数
count=ins.read(buf);
if(count<0)//当count<0时说明已读到文件末尾
return -1;
pos=0;
byte b=buf[pos];
count--;//读取第一个字节,计数器减1
pos++;//同时指针后移一位
return b&255;//返回读取的第一个字节
}
else if(count>0)//如果count>0,继续往后读
{
byte b=buf[pos];
count--;
pos++;
return b&0xff;
}
return -1;
}
public void myClose()throws IOException
{
ins.close();
}
}
十五、将字节流转换成字符流。读取InputStreamReader
将字符流转换成字节流。写入OutputStreamWriter
键盘录入的最常见写法:
BufferedReader bufr1=
New BufferedReader(new InputStreamReader(System.in));
BufferedWriter bufw1=
new BufferedWriter(new OutputStreamWriter(System.out));
比如:要求用键盘录入数据,录入一行数据后,就将该行数据打印。如果录over,那么停止录入。
代码示例:
public static void trans() throws IOException
{
//首先,获取键盘录入对象
InputStream in=System.in;
//然后,将字节流对象转换成字符流对象,使用转换流。InputStreamReader
InputStreamReader isr=new InputStreamReader(in);
//然后,为了提高效率,将字符串进行缓冲区技术高效操作。使用BufferedReader
BufferedReader bufr=new BufferedReader(isr);
String line=null;
while((line=bufr.readLine())!=null)
{
//设置结束标志
if("over".equals(line))
break;
System.out.println(line.toUpperCase());
}
bufr.close();
}
十六、流操作的基本规律。流对象有很多,不知道该用哪一个
可以通过两个明确来完成:
1,明确源和目的。
源:输入流:InputStream Reader
目的:输出流:OutputStream Writer
2,操作的数据是否是纯文本:如果是,则用字符流;如果不是,则用字节流
3,当体系明确后,在明确要使用哪两个具体的对象。
通过设备来区分:
源设备:内存,硬盘,键盘
目的设备:内存,硬盘,控制台
代码示例:
需求1:把键盘录入的数据存储到文件中
* 源:键盘 目的:文件
* 需求2:将一个文件的数据打印在控制台上
* 源:文件 目的:键盘
public static void main(String[] args) throws IOException {
//need_1();
need_2();
}
public static void need_1() throws IOException
{
BufferedReader bufr=
new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bufw=
new BufferedWriter(new OutputStreamWriter(new FileOutputStream("aa.txt")));
String line=null;
while((line=bufr.readLine())!=null)
{
//设置结束标志
if("over".equals(line))
break;
bufw.write(line);
bufw.newLine();
bufw.flush();
}
}
public static void need_2() throws IOException
{
BufferedReader bufr=
new BufferedReader(new InputStreamReader(new FileInputStream("BufferedReaderDemo.java")));
BufferedWriter bufw=
new BufferedWriter(new OutputStreamWriter(System.out));
String line=null;
while((line=bufr.readLine())!=null)
{
//设置结束标志
if("over".equals(line))
break;
bufw.write(line);
bufw.newLine();
bufw.flush();
}
}
注意:转换流什么时候使用呢?
它是字符和字节之间的桥梁,通常涉及到字符编码转换时,需要用到转换流。
十七、File类
用来将文件或者文件夹封装成对象
方便对文件与文件夹的属性信息进行操作
File对象可以作为参数传递给流的构造函数
File的常见方法:
1,创建:
boolean createNewFile():在指定位置创建文件,如果该文件已经存在,则不创建,返回false
boolean mkdir():创建文件夹的方法,创建成功为true,否则false
boolean mkdirs():创建多级文件夹
File f=new File("file.txt");
f.createNewFile();
2,删除:
boolean delete():删除成功返回true,删除失败返回false
Void deleteOnExit():在程序退出时删除指定文件
3,判断(在判断是否是目录是否是文件之前必须先判断文件对象封装的内容是否存在,用exists()方法)
boolean exists():文件是否存在,该方法使用频率较高
boolean isDirectory():判断File对象是否是目录
boolean isFile():判断File对象是否是文件
boolean isHidden():是否是隐藏文件
boolean isAbsolute():是否是绝对路径
4,获取信息
getName(); 获取文件或目录的名称
getPath(); 获取相对路径那个,File对象封装的内容是什么返回的就是什么
getAbsolutePath(); 获取绝对路径
getParent();返回父目录,如果没有指定父目录则返回null
Long lastModified();返回文件最后一次被修改的时间
Long length();文件大小
5,list()和listRoots()和list(FilenameFilter filter)的用法
public static void main(String[] args) {
//listDemo();
//listRootsDemo();
listFilesDemo();
}
public static void listFilesDemo()
{
File dir=new File("c:\\");
File[] files=dir.listFiles();
for(File f:files)
{
System.out.println(f.getName()+":"+f.length());
}
}
public static void listDemo_2()
{
//要求只打印指定目录下的后缀名为.java的文件名称。使用方法list(FilenameFilter filter)
//FilenameFilter是一个接口里面只含有一个方法accept(File dir,String name),所以
//需要写一个类实现它,但是只有一个方法,所以可以传一个匿名内部类进来。
File dir=new File("E:\\ZhangXue\\myeclipse\\Workspaces\\MyEclipse 10\\day14\\src\\myday2001");
String[] arr=dir.list(new FilenameFilter()
{
public boolean accept(File dir,String name)
{
System.out.println("dir:"+dir+"name:"+name);//打印结果dir就是上面指定的目录,name就是该目录下所有的文件名
if(name.endsWith(".java"))
return true;
else
return false;
}
}
);
System.out.println("len:"+arr.length);
for(String name:arr)
{
System.out.println(name);
}
}
public static void listDemo()
{
//注意:调用list方法的File对象必须是封装了一个目录,该目录必须存在
//如果是一个文件则返回空指针
File f=new File("c:\\");
String[] names=f.list();//列出当前目录下所有文件包含隐藏文件
for(String name:names)
{
System.out.println(name);
}
}
public static void listRootsDemo()
{
//列出可用的文件系统根目录
File[] files=File.listRoots();
for(File f:files)
{
System.out.println(f);
}
}
十八、列出指定目录下所有内容,包含子目录下的文件或文件夹。
分析:因为目录中可能还有目录,只要使用同一个列出目录功能的函数即可。在列出过程中判断如果还是目录的话,则再次调用本功能。也就是函数自身调用自身。这种表现形式或者编程手法称为递归。
使用递归时用注意:1,限定条件 2,要注意递归的次数,尽量避免内存溢出。
//要求:列出指定目录下文件或文件夹,包含子目录的内容,也就是列出指定目录
//下所有内容。
public class FileDemo3 {
public static void main(String[] args) {
File dir=new File("F:\\mpich");
showDir(dir);
//toBin(6);
}
//写一个函数其功能就是列出指定目录下所有文件或文件夹的名名称
public static void showDir(File dir)
{
System.out.println(dir);
File[] files=dir.listFiles();
for(int x=0;x<files.length;x++)
{
//判断files[x]是否是目录,如果是递归,调用本函数,如果不是打印
if(files[x].isDirectory())
showDir(files[x]);
else
System.out.println(files[x]);
}
}
//将整数化为二进制数
public static void toBin(int num)
{
//打印结果 0 1 1
/*while(num>0)
{
System.out.println(num%2);
num=num/2;
}*/
//递归,打印结果1 1 0
if(num>0)
{
toBin(num/2);
System.out.println(num%2);
}
}
}
十九、删除指定目录下的所有内容。从里往外删
代码示例://删除一个带内容的目录。
//删除原理:在window中,删除目录从里面往外删除
public class FileDemo4 {
public static void main(String[] args) {
File dir=new File("F:\\mpich");
deleteDir(dir);
}
//写一个方法实现该功能和之前写的打印原理一样,只不过改成删除
public static void deleteDir(File dir)
{
File[] files=dir.listFiles();
for(int x=0;x<files.length;x++)
{//判断是否是目录,如果是,则进到目录里面,判断下一级
if(!files[x].isHidden()&&files[x].isDirectory())
deleteDir(files[x]);
else //如果不是则证明是文件,删除该文件
System.out.println(files[x].toString()+":"+files[x].delete());
}
//删除目录
System.out.println(dir+"::"+dir.delete());
}
}
二十、要求:将一个指定目下的.java文件的绝对路径,存储到一个文本文件中,建立一个Java文件列表文件
public class JavaFileList {
/**
* 思路:1,对指定的目录进行递归
* 2,获取递归过程所有的java文件的路径
* 3,将这些路径存储到集合中
* 4,将集合中的内容存储到文本文件中
*/
public static void main(String[] args) {
File dir=new File("F:\\javalearn\\day03");
List<File> list=new ArrayList<File>();
String name="TextNameList.txt";
fileToList(dir,list);
File file=new File(dir,name);//将该文本文件建立在指定目录下
writeToFile(list,file.toString());
}
public static void fileToList(File dir,List<File> list)
{
File[] files=dir.listFiles();
for(File file:files)
{
//判断是否是目录,如果是,则递归,继续判断下一级
if(file.isDirectory())
fileToList(file,list);
else //如果不是目录,则证明是文件,则添加到集合中
{
if(file.getName().endsWith(".java"))
list.add(file);
}
}
}
//将集合中的数据存储到文本文件中,源:集合;目的:文件。是纯文本
public static void writeToFile(List<File> list,String name)
{
BufferedWriter bufr=null;
try
{
bufr=new BufferedWriter(new FileWriter(name));
//通过高级for循环遍历集合,将元素写入文本文件中
for(File f:list)
{
String path=f.getAbsolutePath();
bufr.write(path);
bufr.newLine();
bufr.flush();
}
}
catch(IOException e)
{
throw new RuntimeException();
}
finally
{
try
{
if(bufr!=null)
bufr.close();
}
catch(IOException e)
{
throw new RuntimeException();
}
}
}
}
二十一、Properties的简述
Properties是hashTable的子类,也就是说它具备map集合的特点,而且它里面存储的键值对都是字符串。是集合中和IO技术相结合的集合容器。该对象的特点:可以用于键值对形式的配置文件。
那么在加载数据时,通常有固定格式,通常是键=值。
public class PripertiesDemo {
/**
* 如何将流中的数据存储到集合中。比如将info.txt的键值数据存储到集合中
* 思路:1,用一个流和该文件关联
* 2,读取一行数据,将该行数据用“=”进行切割
* 3,等号左边作为键,右边作为值。存入到Properties集合中即可
* @throws IOException
*/
public static void main(String[] args) throws IOException {
//setAndGet();
//method_1();
loadDemo();
}
//展示load方法的使用,就是通过流将键值对存入Properties集合中
public static void loadDemo() throws IOException
{
Properties prop=new Properties();
FileInputStream fis=new FileInputStream("info.txt");
//将流中的数据加载进集合
prop.load(fis);
//如果发现该文件中有一个键对应的值是错误的,通过store(OutputStream out,String comments)方法修改
//第一个参数传入一个写入流,第二个参数是注释
prop.setProperty("wangwu", "68");
FileOutputStream fos=new FileOutputStream("info.txt");
prop.store(fos, "xiugai");
System.out.println(prop);
prop.list(System.out);//功能与输出语句类似,打印集合中的内容
}
public static void method_1() throws IOException
{
BufferedReader bufr=new BufferedReader(new FileReader("info.txt"));
String line=null;
Properties prop=new Properties();
while((line=bufr.readLine())!=null)
{
String[] arr=line.split("=");
System.out.println(arr[0]+":"+arr[1]);
prop.setProperty(arr[0], arr[1]);
}
bufr.close();
System.out.println(prop);
}
//Properties设置和获取元素
public static void setAndGet()
{
Properties prop=new Properties();
//向集合中添加元素
prop.setProperty("zhangds","23");
prop.setProperty("lisi","30");
System.out.println(prop);
//根据键获取对应的值
String value=prop.getProperty("lisi");
System.out.println(value);
//stringPropertyNames 1.5版本以后才有
Set<String> names=prop.stringPropertyNames();
for(String s:names)
{
System.out.println(s+":"+prop.getProperty(s));
}
}
}
二十二、写一个程序用于记录运行次数。如果使用次数已到,那么给出注册提示。
分析:比较容易想到计数器,但是计数器定义在程序中,随着程序的运行而在内存中存在,并进行自增。当应用程序退出时,该计数器在内存中也消失了。下一次启动该程序,又重新开始从0计数。这是不符合要求的。
我们需要的是程序即使结束,该计数器的值也存在。下次程序启动会先加载该计数器的值并加1后在重新存储起来。所以要建立一个配置文件,用于记录该软件的使用次数。该配置文件使用键值对的形式便于阅读并操作数据。
键值对数据是map集合,数据是以文件形式存储,使用io计数。
那么map+io---->Properties.配置文件可以实现用用程序数据的共享。
代码示例:
public class RunTime {
public static void main(String[] args) throws IOException {
Properties prop=new Properties();
//将文件封装成File对象
File file=new File("count.ini");
//判断如果该文件不存在,则重新创建
if(!file.exists())
file.createNewFile();
//定义文件读取流与file相关联
FileInputStream fis=new FileInputStream(file);
//将该流中的数据加载到prop集合中
prop.load(fis);
//定义计数器
int count=0;
//通过键获取值,最开始第一次运行time不存在返回null
String value=prop.getProperty("time");
if(value!=null)
{
count=Integer.parseInt(value);
if(count>=5)
{
System.out.println("您好,使用次数已到,请注册");
}
}
count++;
//修改集合中time的值
prop.setProperty("time", count+"");
//将修改写回到文件中
FileOutputStream fos=new FileOutputStream(file);
prop.store(fos, "");
fos.close();
fis.close();
}
}
二十三、打印流。该流提供了打印方法,可以将各种数据类型的数据都原样打印。
字节流打印:PrintStream 其构造函数可以接收的参数类型
1,file对象 File 2,字符串路径 String 3,字节输出流 OutputStream
字符流打印:PrintWriter 其构造函数可以接收的参数类型(在web开发中非常常用)
1,File对象 File 2,字符串路径 String 3,字节输出流 OutputStream 4,字符输出流 Writer
代码示例:
//要求键盘录入数据,使用PrintWriter将录入的数据变成大写打印在控制台上
public static void method_1() throws IOException
{
//键盘录入
BufferedReader bufr=new
BufferedReader(new InputStreamReader(System.in));
//如果第二个参数为true则自动刷新
//PrintWriter pw=new PrintWriter(System.out,true);
PrintWriter pw=new PrintWriter(System.out);
String line=null;
while((line=bufr.readLine())!=null)
{
if("over".equals(line))
break;
pw.println(line.toUpperCase());
pw.flush();
}
pw.close();
bufr.close();
}
二十四、序列流SequenceInputStream 主要功能合并
构造函数:SequenceInputStream(Enumeration<? extends InputStream> e)和SequenceInputStream(InputStream s1, InputStream s2)
代码示例:
//序列流的使用SequenceInputStream
public class SequenceDemo {
public static void main(String[] args) throws IOException {
Vector<FileInputStream> v=new Vector<FileInputStream>();
v.add(new FileInputStream("1.txt"));
v.add(new FileInputStream("2.txt"));
v.add(new FileInputStream("3.txt"));
Enumeration<FileInputStream> en=v.elements();
SequenceInputStream sis=new SequenceInputStream(en);
//目的,将集中后的流写入到文件中
FileOutputStream fos=new FileOutputStream("4.txt");
byte[] buf=new byte[1024];
int len=0;
while((len=sis.read(buf))!=-1)
{
fos.write(buf,0,len);
}
fos.close();
sis.close();
}
}
要求1:切割文件,比如将一个3M多的图片,切割成三份,前两份大小为1M
要求2:将切割后的文件重新合并起来
public class SplitFileDemo {
/**
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
//splitFile();
sequenceMethod();
}
//切割文件
public static void splitFile() throws IOException
{
//读取流与原文件关联
FileInputStream fis=new FileInputStream("F:\\1.jpg");
FileOutputStream fos=null;
byte[] buf=new byte[1024*100];
int len=0; int count=1;
while((len=fis.read(buf))!=-1)
{
fos=new FileOutputStream("F:\\"+count+++".part");
fos.write(buf,0,len);
//写完一个马上关闭流
fos.close();
}
fis.close();
}
//将切割的碎片文件合并起来
public static void sequenceMethod() throws IOException
{
//因为Vector效率低,所以用ArrayList集合,但代码比较麻烦,因为序列流的参数为Enumeration
ArrayList<FileInputStream> al=new ArrayList<FileInputStream>();
//将碎片文件与读取流相关联并添加到集合对象中
for(int x=1;x<5;x++)
{
al.add(new FileInputStream("F:\\"+x+".part"));
}
final Iterator<FileInputStream> it=al.iterator();
//ArrayList集合用枚举取出元素,代码
Enumeration<FileInputStream> en=new Enumeration<FileInputStream>()
{
public boolean hasMoreElements()
{
return it.hasNext();
}
public FileInputStream nextElement()
{
return it.next();
}
};
//源,定义序列流传入en对象,合并四个文件
SequenceInputStream sis=new SequenceInputStream(en);
//目的,将合并后的流写入文件中,因为是图片,所以用字节流
FileOutputStream fos=new FileOutputStream("F:\\1_合并.jpg");
byte[] buf=new byte[1024];
int len=0;
while((len=sis.read(buf))!=-1)
{
fos.write(buf,0,len);
}
fos.close();
sis.close();
}
}
二十四、可以直接操作对象的字节流
ObjectInputStream 和ObjectOutputStream
注意:被操作的对象需要实现Serializable接口,它没有方法称为标记接口。
public class ObjectStreamDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//writeObj();
readObj();
}
//将对象写入文件中,正常情况下对象是在堆内存中,并不是永久存在的
//为了使其永久存在,将其写入文件,存到硬盘上
public static void writeObj() throws IOException
{
ObjectOutputStream oos= //存入的目的地
new ObjectOutputStream(new FileOutputStream("obj.txt"));
oos.writeObject(new Person("zdfs",20,"kr"));
oos.close();
}
//将刚才的文件的内容读取出来
public static void readObj() throws IOException, ClassNotFoundException
{
ObjectInputStream ois=
new ObjectInputStream(new FileInputStream("obj.txt"));
Person p=(Person)ois.readObject();
System.out.println(p);
ois.close();
}
}
//自己封装一个Person类实现Serializable接口
class Person implements Serializable
{
//自定义序列号,当该类的成员变化时,使用该类对象的对象流仍能使用
static final long serialVersionUID = 42L;
private String name;
private int age;
//静态是不能序列化的
static String country="cn";
//如果你不想加static 但是也不想被序列化,保证其值在对内存中存在而不在文本文件中存在
transient String sex;
public Person(String name,int age,String country)
{
this.name=name;
this.age=age;
this.age=age;
}
public String toString()
{
return name+":"+age;
}
二十五、管道流 (io结合多线程技术)
PipedInputStream和PipedOutputStream
输出可以直接进行连接,通过结合线程使用
//管道流PipedInputStream和PipedOutputStream是成对使用的
public class PipedStreamDemo {
/**
* 因为管道流用到了多线程,这里设计一个线程读,一个线程写
*/
public static void main(String[] args) throws IOException {
PipedInputStream ppis=new PipedInputStream();
PipedOutputStream ppos=new PipedOutputStream();
ppis.connect(ppos);
Read r=new Read(ppis);
Write w=new Write(ppos);
new Thread(r).start();
new Thread(w).start();
}
}
class Read implements Runnable
{
//使该类一初始化就创建管道读取流
private PipedInputStream in;
Read(PipedInputStream in)
{
this.in=in;
}
public void run()
{
try
{
byte[] buf=new byte[1024];
System.out.println("开始读取数据");
int len=in.read(buf);
String s=new String(buf,0,len);
System.out.println(s);
in.close();
}
catch(IOException e)
{
throw new RuntimeException("写入管道流写入失败");
}
}
}
class Write implements Runnable
{
private PipedOutputStream out;
public Write(PipedOutputStream out)
{
this.out=out;
}
public void run()
{
try
{
System.out.println("开始写入数据");
Thread.sleep(6000);//睡眠6秒
String str="paped com in";
out.write(str.getBytes());
out.close();
}
catch(Exception e)
{
throw new RuntimeException("输出管道流输出失败");
}
}
}
二十六、类RandomAccessFile
该类不算是IO体系中的子类。但是它是Io包中的成员,因为它具备读和写的功能。其内部封装了一个大型的byte数组,而通过指针对数组的元素进行操作。可以通过getFilePointer获取指针位置。同时可以通过seek来改变指针的位置。
其实该类完成读写的原理就是内部封装了字节输入流和字节输出流。通过构造函数可以看出,该类只能操作文件。而且该操作文件还有模式:只读 r ; 读写:rw
而且该对象的构造函数要操作的文件不存在,会自定创建。如果存在则不会覆盖。
如果模式为只读 r的话,不会创建文件。回去读取一个已存在的文件,如果该文件不存在,则会出现异常
如果模式为rw,操作的文件不存在,会自动创建。如果存在则不会覆盖。
public class RandomAccessFileDemo {
public static void main(String[] args) throws IOException {
//writeFile();
//readFile();
writeFile_2();
}
//通过该类往文件中写数据
public static void writeFile() throws IOException
{
RandomAccessFile raf=new RandomAccessFile("raf.txt","rw");
raf.write("李四".getBytes());
raf.writeInt(36);
raf.write("王五".getBytes());
raf.writeInt(36);
raf.close();
}
//将文件中的数据读取出来
public static void readFile() throws IOException
{
RandomAccessFile raf=new RandomAccessFile("raf.txt","r");
//数组定义为4个字节,就会取出前两个汉字四个字节。姓名和年龄一共8个字节
byte[] buf=new byte[4];
raf.read(buf);
String name=new String(buf);
int age=raf.readInt();
System.out.println("name="+name+" "+"age="+age);//打印结果name=李四 age=36
//如果想要获取王五的姓名和年龄,则可以通过调整对象中指针
raf.seek(8);
//跳过指定的字节数
raf.skipBytes(4);//与seek的区别,只能像前跳,seek可以随便指定指针位置
byte[] buf1=new byte[4];
raf.read(buf1);
String name1=new String(buf1);
int age1=raf.readInt();
System.out.println("name="+name1+" "+"age="+age1);
raf.close();
}
public static void writeFile_2() throws IOException
{
RandomAccessFile raf=new RandomAccessFile("raf.txt","rw");
//并不在该文件的内容后边直接添加,想要空一个位置再添加一个内容,将指针指到第四个位置
raf.seek(8*3);
raf.write("当当".getBytes());
raf.writeInt(103);
raf.close();
}
}
二十七、操作基本数据类型的类
DataInputStream和DataOutputStream
public class DataStreamDemo {
public static void main(String[] args)throws IOException
{
//writeData();
readData();
}
//像文件中存入基本数据类型
public static void writeData()throws IOException
{
DataOutputStream dos=
new DataOutputStream(new FileOutputStream("data.txt"));
dos.writeChars("haha");
dos.writeChar(97);
dos.writeInt(36);
dos.writeDouble(242.8943);
dos.close();
}
//读取文件中的数据
public static void readData()throws IOException
{
DataInputStream dis=
new DataInputStream(new FileInputStream("data.txt"));
char[] ch=new char[4];
for(int i=0;i<4;i++)
{
ch[i]=dis.readChar();
}
char c=dis.readChar();
int num=dis.readInt();
double d=dis.readDouble();
System.out.println("s="+ch.toString());
System.out.println("c="+c);
System.out.println("num="+num);
System.out.println("d="+d);
dis.close();
}
//该类有一个特殊的方法writeUTF,它写入的字符串,只能通过readUTF读取
public static void writeUtfMethod()throws IOException
{
DataOutputStream dos=
new DataOutputStream(new FileOutputStream("utf.txt"));
dos.writeUTF("你好");//写入汉字,因为英文字母没有涉及到编码
dos.close();
}
public static void readUtfMethod()throws IOException
{
DataInputStream dos=
new DataInputStream(new FileInputStream("utf.txt"));
dos.readUTF();
dos.close();
}
}
二十八、用于操作字节数组的流对象
ByteArrayInputStream:在构造的时候,需要接受数据源,而且数据源是一个字节数组。
ByteArrayOutputStream:在构造的时候,不用定义数据目的,因为该对象中已经内部封装了可变长度的字节数组。这就是数据目的地。
注意:因为这两个流对象都操作的数组,并没有使用系统资源。所以不用进行close关闭。
操作字符数组:CharArrayReader和CharArrayWriter
操作字符串:StringReader和StringWriter
public class ByteArrayStreamDemo {
public static void main(String[] args) {
String s="abcdef";
//源
ByteArrayInputStream bais=
new ByteArrayInputStream(s.getBytes());
//目的
ByteArrayOutputStream baos=
new ByteArrayOutputStream();
//因为该类中封装了可变长的字节数组,所以不用定义缓冲数组
int by=0;
while((by=bais.read())!=-1)
{
baos.write(by);
}
System.out.println(baos.size());
System.out.println(baos.toString());
//可以将源中的数据存储到文件中
//baos.writeTo(new FileOutputStream("a.txt"));
}
}
二十九、字符编码
字符流的出现为了方便操作字符。更重要的是加入了编码转换。
通过子类转换流:InputStreamReader和OutputStreamWriter来完成。在两个对象进行构造的时候可以加入字符集,即编码表。
ASCII:美国标准信息交换码。用一个字节的7位可以表示
ISO8859-1:拉丁码表,欧洲码表 用一个字节的8位表示
GB2312:中国的中文编码表。大概容纳了六七千字
GBK:中国的中文编码表升级,融合了更多的中文文字符号。
Unicode:国际标准码,融合了多种文字。所有文字都用两个字节来表示,java语言使用的就是Unicode
UTF-8:最多用三个字节表示一个字符。
public class EncodeStream {
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
writeText();
//readText();
}
public static void writeText()throws IOException
{
OutputStreamWriter osw=
new OutputStreamWriter(new FileOutputStream("gbk.txt"),"GBK");
osw.write("你好"); //四个字节
//osw.close();
OutputStreamWriter osw1=
new OutputStreamWriter(new FileOutputStream("utf.txt"),"UTF-8");
osw1.write("你好");//六个字节
osw1.close();
}
public static void readText()throws IOException
{
InputStreamReader isr=
new InputStreamReader(new FileInputStream("gbk.txt"),"GBk");
char[] buf=new char[10];
int len=isr.read(buf);
String str=new String(buf,0,len);
System.out.println(str);
isr.close();
}
}
三十、
注意:当使用GBK编码,ios8859-1解码,发生错误时,可以再按iso8859-1编码,按GBK解码一次得到正确结果
而当使用GBK编码,utf-8解码,发生错误时,可以再按utf-8编码,按GBK解码一次得不到正确结果。因为GBK和utf-8都识别中文
import java.io.*;
import java.util.Arrays;
public class EncodeDemo {
/**
* 编码:字符串变成字节数组
* String---->byte[] str.getBytes(); str.getBytes(charsetName);
* 解码:字节数组变成字符串
* byte[]--->String new String(byte[]); new String(byte[],chasetName);
* @throws IOException
*/
public static void main(String[] args) throws IOException {
//编码:将字符串变为字节数组
String s="你好";
byte[] by=s.getBytes();
System.out.println(Arrays.toString(by));//输出[-60, -29, -70, -61]
//解码:将字节数组变为字符串
String str=new String(by);
System.out.println(str);//输出:你好
//可以指定编码表,编码用的gbk
byte[] by1=s.getBytes("GBK");
System.out.println(Arrays.toString(by1));
//同样可以指定编码表,而解码用的iso8859-1,解码肯定错误.
//这种情况下,将解码得到的新字符串重新按iso8859-1编码,然后按GBK解码可以得到正确的
String str1=new String(by1,"iso8859-1");
System.out.println(str1);//输出:你好
byte[] b=str1.getBytes("iso8859-1");
System.out.println(Arrays.toString(b));
String str2=new String(b,"gbk");
System.out.println(str2);
}
}
三十一、“联通”的问题
public class LianTongDemo {
/**
* 问题描述:新建一个记事本,写入“联通”两个字保存关闭记事本,再重新打开该记事本,“联通”二字变为乱码
* 原因:本来记事本是按照GBK编码的,再打开记事本时应该按照GBK解码,但是“联通”这两个字所对应的GBK码表中的二进制
* 数字非常特殊,正好符合utf-8的编码形式,所以重新打开记事本时,记事本自动按照utf-8来解读了,出现了乱码
* @throws IOException
*/
public static void main(String[] args) throws IOException {
String s="联通";
byte[] by=s.getBytes("gbk");
for(byte b:by)
{
System.out.println(Integer.toBinaryString(b&255));
/*打印结果:
11000001
10101010
11001101
10101000
*/
}
}
}
三十二、
练习:有5个学生,每个学生有3门课的成绩。
从键盘录入以上数据(包括姓名,三门课的成绩),输入的格式,
如:张三,50,90,49计算出总成绩。并把学生信息和计算出的总成绩按分数高低
顺序存储到stuinfo.txt中。
分析:首先应描述学生对象。然后定义一个可操作学生对象的工具类
思路:
1,通过获取键盘录入一行数据
2,因为学生有很多,那么就需要存储,用到集合。因为要对学生的总分排序,所以
可以使用TreeSet。
3,将集合的信息写入到文件中。
import java.io.*;
import java.util.*;
//首先封装学生类对象,因为需要将学生按照总分排序,所以最好让学生对象自身具备比较性
class Student implements Comparable
{
private String name; //学生姓名
private int ma,cn,en; //数学语文英语成绩
private int sum; //总分
public Student(String name,int ma,int cn,int en)
{
this.name=name;
this.ma=ma;
this.cn=cn;
this.en=en;
sum=ma+cn+en;
}
//继承了Comparable接口,就要覆盖compareTo方法
public int compareTo(Object obj)
{
if(!(obj instanceof Student))
throw new RuntimeException("类型不匹配");
Student s=(Student)obj;
int num=new Integer(this.sum).compareTo(new Integer(s.sum));
if(num==0)
return this.name.compareTo(s.name);
return num;
}
public String getName()
{
return name;
}
public int getSum()
{
return sum;
}
//因为有可能存到HashSet集合中所以覆盖hashCode方法
public int hashCode()
{
return name.hashCode()+sum*6;
}
//覆盖equals方法
public boolean equals(Object obj)
{
if(!(obj instanceof Student))
throw new RuntimeException("类型不匹配");
Student s=(Student)obj;
return this.name.equals(s.name)&&this.sum==s.sum;
}
public String toString()
{
return "Student["+name+","+ma+","+cn+","+en+"]"+":"+sum;
}
}
//然后定义一个可操作学生信息的工具类
class StuInfoTool
{
//默认排序
public static Set<Student> getStudents()throws IOException
{
return getStudents(null);
}
//按照比较器排序
//通过键盘录入学生信息并封装成学生对象存储到集合中
public static Set<Student> getStudents(Comparator<Student> cmp) throws IOException
{
//键盘录入
BufferedReader bufr=
new BufferedReader(new InputStreamReader(System.in));
Set<Student> stus=new TreeSet<Student>(cmp);
//读取,并按照逗号分隔,封装成学生对象
String line=null;
while((line=bufr.readLine())!=null)
{
if("over".equals(line))
break;
String[] info=line.split(",");
Student stu=
new Student(info[0],Integer.parseInt(info[1]),
Integer.parseInt(info[2]),Integer.parseInt(info[3]));
stus.add(stu);
}
bufr.close();
return stus;
}
//把集合中的数据存储到文件中
public static void writeToFile(Set<Student> s) throws IOException
{
BufferedWriter bufw=
new BufferedWriter(new FileWriter("stuinfo.txt"));
for(Student st:s)
{
bufw.write(st.toString());
//bufw.write(st.getSum()+"");必须加后边的分号,要不然打开记事本有乱码出现
bufw.newLine();
bufw.flush();
}
bufw.close();
}
}
public class StudentInfoDemo {
public static void main(String[] args) throws IOException {
//Set<Student> stus=StuInfoTool.getStudents();
//StuInfoTool.writeToFile(stus); //这样输出的是总分从低到高的顺序输出
//而要求是按照总分从高到低的顺序输出,所以可以修改上边的compareTo方法,但最好
//不要那样。我们可以使用比较器
//强行逆转比较器
Comparator<Student> cmp=Collections.reverseOrder();
Set<Student> stus1=StuInfoTool.getStudents(cmp);
StuInfoTool.writeToFile(stus1);
/*
通过比较器记事本的输出结果
Student[gdsef,66,33,77]:176
Student[gsgdf,75,43,32]:150
Student[hfdsg,24,44,55]:123
*/
/*
默认排序记事本的输出结果
Student[gdsef,66,33,77]:123
Student[gsgdf,75,43,32]:150
Student[hfdsg,24,44,55]:176
*/
}
}