黑马程序员----8IO流
来源:互联网 发布:如何解除网络限制 编辑:程序博客网 时间:2024/05/27 00:33
>>IO流概述
IO流用来处理设备之间的数据传输
JAVA对数据的操作是通过流的方式
JAVA用于操作流的对象都在IO包中
流按操作数据分为两种:字节流与字符流
流按流向分为:输入流和输出流
>>
字节流的抽象基类:InputStream、OutputStream
字符流的抽象基类:Reader、Writer
>>需求:
使用java程序,将一个字符串数据写到硬盘上。
从需求来理解:操作字符数据,可是用字符流对象。写到硬盘其实就是才操作文件。
既然是写:需要找打具备写功能的输出流。
字符流体系中:有两个基类。
Reader
Writer
找到了Writer--OutputStreamWriter--FileWriter.
FileWriter:用来操作文件的字符串输出流对象。
eg:import java.io.*;
class FileWriterDemo
{
publicstatic void main(String[] args)throws IOException
{
//创建FileWriter字符写入流对象
FileWriter fw = new FileWriter("demo.txt");
/*创建该对象做的事情:
1,在堆内存中产生了一个实体。
2,调用了系统底层资源。其实是调用了window的功能。
在指定位置创建了一个文件,建立数据存储的目的地。
用于存放即将写入的数据。
3,因为目的地可能因为路径错误,而导致失败,所以抛出了IOException,需要对其处理。
注意:如果要创建的文件已经存在,那么会产生覆盖。
*/
//既然有了流对象,指定具体数据,使用fw对象的write方法将数据写出。
//fw对象的write方法,将数据写入到了流中,其实就是内存中。
fw.write("abcdef");
//刷新缓冲区,将流中的数据刷到目的地中。
fw.flush();
fw.write("ddd");
/*
close方法:
1,先刷新缓冲区的数据到目的地,其实就是调用了一次flush。
2,关闭了调用底层的资源。将资源释放。
*/
fw.close();
/*
flush()和close():不同:
flush():只刷新缓冲区。流依然存在,并可以继续使用。可以用多次。
close():也会刷新缓冲区,但是刷新后,立刻关闭流资源,流不可以在继续使用。只能用一次。
*/
}
}
>>IO异常的处理方式
eg:
import java.io.*; //注意导包
class FileWriteExceptionDemo
{
publicstatic void main(String[] args) //RuntimeException不需要在函数上声明
{
FileWriterfw = null;
/*
这里有三个代码块try、catch、finally
如果在try内定义fw,那么finally中的close便无法调用
*/
try
{
fw= new FileWriter("FileWriteExceptionDemoTxt.txt");
fw.write("abcd@163.com");
}
catch(IOExceptione)
{
System.out.println(e.toString());
thrownew RuntimeException("写入失败");//或者写异常日志
}
finally
{
//close也会抛出异常,所以要try
try
{
if(!(fw==null))
fw.close();
/*
如果在fw = new FileWriter这一步就已经抛出异常,
fw便为null,close无法执行,所以要判断
*/
}
catch(IOExceptione)
{
System.out.println(e.toString());
thrownew RuntimeException("关闭失败");//或者写异常日志
}
}
}
}
>>在已经有的文件后面进行数据追加
找到构造函数:
FileWriter(StringfileName, boolean append)
根据给定的文件名以及指示是否附加写入数据的 boolean 值来构造 FileWriter 对象。
eg:………………
try
{
fw = newFileWriter("demo.txt",true);
fw.write("hello\r\nworld");//如果写入回车换行符,在window中需要 \r\n 来完成。
………………
结果会在已经存在的demo.txt文件中添加内容而不是覆盖,如果demo.txt不存在,会先创建文件而后添加。
注意,windows的换行符是\r\n,如果仅仅使用\n,在记事本中会变成小黑块。而在UltraEdit中才能被识别。
>>同样,可以读取文件
java.lang.Object
|--java.io.Reader
|--java.io.InputStreamReader
|--java.io.FileReader
eg:importjava.io.*;
/*
读取一个硬盘上已有的文件文件中的数据。
并打印在控制台上。
要先查看字符读取流的基类对象Reader.
*/
class FileReaderDemo
{
public static void main(String[] args)throws IOException
{
/*
创建了一个读取流对象,并关联要读取的文件。
将要读取的文件作为构造函数的参数传递。意为:对象一初始化就必须要有一个已经存在的数据。
*/
FileReader fr = newFileReader("demo.txt");
//调用了读取流对象的read方法。一次读一个字符。
//int ch = fr.read();//这个read方法可以自动往下读取。
int ch = 0;
while((ch = fr.read())!=-1)//read方法在文件末尾返回-1
{
System.out.println((char)ch);
}
/*
System.out.println((char)ch);
int ch1 = fr.read();
System.out.println((char)ch1);
intch2 = fr.read();
System.out.println((char)ch2);
int ch3 = fr.read();
System.out.println(ch3);
int ch4 = fr.read();
System.out.println(ch4);
*/
//关闭资源。
fr.close();
}
}
演示另一种读取方式。read(char[]);
importjava.io.*;
classFileReaderDemo2
{
public static void main(String[]args)throws IOException
{
FileReader fr = newFileReader("demo.txt");
//因为要往read方法中传递一个字符数组,
//所以要先定义一个数组出来。
char[] buf = newchar[1024];//一般情况下,数组(缓冲区)的长度为1024的整数倍.
int len = 0;
while((len=fr.read(buf))!=-1)
{
System.out.println(newString(buf,0,len));
}
/*
int len = fr.read(buf);//将流中读到的字符存储到了数组中,返回读到的字符的个数。
System.out.println(len);
System.out.println(newString(buf,0,len));
int len1 = fr.read(buf);
System.out.println(len1);
System.out.println(newString(buf,0,len1));
int len2 = fr.read(buf);
System.out.println(len2);
System.out.println(newString(buf,0,len2));
int len3 = fr.read(buf);
System.out.println(len3);
*/
fr.close();
}
}
这里有一个有趣的现象,与“System.out.println(newString(buf,0,len));”有关!
假设我们创建的数组为:char[]buf = new char[2];,并且文件demo.txt中的信息为“abcde”这样一个字符串,那么使用:
System.out.println(len);
System.out.println(newString(buf));
输出的时候会发现,输出的内容依次为:ab,cd,ed。
显然,最后的ed的“d”不正常!
这个是因为每次只读取两个字符,但是读到“e”的时候,再读发现结束了。于是,先前的内容“cd”中的“c”被“e”替换,而“d”被保留下来了。
所以,改造输出方法,使用String的构造函数:
publicString(char[] value,int offset,int count)
改造输出方法:System.out.println(newString(buf,0,len));
通常采用数组输出方法,减少循环次数,提高效率。
>>复制的过程实际上就是读写的过程
eg:读取单个字符方式并简化异常处理
classCopyText
{
public static void main(String[] args)throws IOException
{
//先创建读取流对象和源文件相关联。
FileReader fr = newFileReader("demo.txt");
//创建一个目的文件,用于存储读取到的数据。
FileWriter fw = newFileWriter("copydemo.txt");
//进行不断的读写操作。
int ch = 0;
while((ch=fr.read())!=-1)
{
fw.write(ch);
}
fw.close();
fr.close();
}
}
eg:读取数组的方式并加上异常处理
importjava.io.*;
classCopyTxtDemo
{
public static void main(String[] args)
{
//读取文件的流
FileReader fr = null;
//写入文件的流
FileWriter fw = null;
try
{
fr = newFileReader("CopyTxtDemo.txt");
fw = newFileWriter("CopyTxtDemo_copy.txt");
char[] array = newchar[1024];
int len = 0;
while((len =fr.read(array))!=-1)
{
System.out.println(newString(array,0,len));
fw.write(array,0,len);
}
}
catch(IOException e)
{
throw newRuntimeException("读写失败");
}
finally
{
try
{
if(fw!=null)//不要忘了判断,否则抛空指针异常
{
fr.close();
fw.close();
}
}
catch(IOException e)
{
throw new RuntimeException("关闭失败");
}
}
}
}
>>数组的效率比单个字符的读写高,数组是作为缓冲区存在的,其实,有一个类就是作为缓冲区存在的:
java.lang.Object
|--java.io.Writer
|--java.io.BufferedWriter
Buffer就是缓冲的意思。
相对于父类Writer,BufferedWriter多了新功能:voidnewLine()//写入一个行分隔符。
eg:演示带缓冲技术的字符输出流对象。
import java.io.*;
classBufferedDemo
{
public static void main(String[] args)
{
FileWriter fw = null;
BufferedWriter buf = null;
try
{
fw = newFileWriter("bufferedDemoTxt.txt");
buf = newBufferedWriter(fw);
//缓冲区对象的存在为了给流提高效率。
//所以缓冲区对象的建立必须先要将流 初始化进来。
buf.write("abc");
buf.newLine();
//buf.write("\r\n");
//buf.newLine();和"\r\n"有何不同?
//buf.newLine();是兼容全平台的,而"\r\n"是win的格式
buf.write("efg");
buf.flush();
}
catch(IOException e)
{
throw new RuntimeException("写入异常!");
}
finally
{
try
{
if(fw !=null)
{
buf.close();
//fw.close();
/*不用关闭fw了。因为缓冲区是提高流的操作效率而存在。只是内部提供了数组。对数据进行缓冲,最终的写入动作还是流完成的。所以关闭缓冲区,其实就是在关闭流。也就是bufw.close,其实内部调用的就是fw.close();*/
}
}
catch(IOException e)
{
throw newRuntimeException("关闭异常!");
}
}
}
}
>>当然,还有类BufferedReader
java.lang.Object
|--java.io.Reader
|--java.io.BufferedReader
其中一个很酷的新方法:
StringreadLine() //读取一个文本行。
eg:演示字符流的读取缓冲区。
importjava.io.*;
classBufferedReaderDemo
{
public static void main(String[] args)throws IOException
{
FileReader fr = newFileReader("bufdemo.txt");
BufferedReader bufr = newBufferedReader(fr);
String line = null;
while((line=bufr.readLine())!=null)
{
System.out.println(line);
}
bufr.close();
}
}
注意:假设bufdemo.txt中的数据是这样的:
abc0
abc1
abc2
abc3
那么,其实还包括了三个换行符\r\n,一共22字节,但是readLine()只会判断而不会读取换行符,所以如果没有println而是print的话,输出便是一行数据一共16个字节:
abc0abc1abc2abc3
>>通过缓冲区的方式复制文件
eg:用已有的缓冲区复制文本文件。
import java.io.*;
class CopyTextBuf
{
public static void main(String[] args)
{
FileReader fr = null;
BufferedReader bufr = null;
FileWriter fw = null;
BufferedWriter bufw = null;
try
{
fr = newFileReader("SystemDemo.java");
bufr = new BufferedReader(fr);
fw = newFileWriter("copySystem.txt");
bufw = newBufferedWriter(fw);
String line = null;
while((line=bufr.readLine())!=null)
{
bufw.write(line);
bufw.newLine();
bufw.flush();
}
}
catch (IOException e)
{
throw newRuntimeException("读写失败");
}
finally
{
try
{
if(bufw!=null)
bufw.close();
}
catch (IOExceptione)
{
throw newRuntimeException("写入关闭失败");
}
try
{
if(bufr!=null)
bufr.close();
}
catch (IOExceptione)
{
throw newRuntimeException("读取关闭失败");
}
}
}
}
>>
字符流的缓冲区。是为了提高效率而存在。
BufferedWriter
newLine();
BufferedReader
readLine();
缓冲区的出现提供了比以前流对象功能更强的函数。
装饰设计模式。
当对类的功能进行增强时,可称之为对该类的装饰。
同时它的出现具备灵活性。
eg:
classPerson
{
void chi()
{
System.out.println("chifan");
}
}
classNewPerson //装饰Person对象的类。称为装饰类,只为增强Person的功能而出现。
{
private Person p;
NewPerson(Person p)
{
this.p = p;
}
void newChi()
{
System.out.println("来一杯");
p.chi();
System.out.println("甜点");
System.out.println("来一根");
}
}
class PersonDemo
{
public static void main(String[] args)
{
Person p = new Person();
p.chi();
//NewPerson np = new NewPerson(p);//传入Person对象
//np.newChi();
}
}
>>装饰和继承。
装饰设计模式是一种解决某一类问题的思想。该类问题的有效解决方案。
解决给类提供增强型功能的问题。
继承:是面向对象的特征之一。
Writer
|--TextWriter
|--MediaWirter
该体系的出现已经可以完成对文本数据和媒体数据的写操作。但是发现。效率较低。为了提高效率,就加入了缓冲技术。
文本写入需要缓冲
媒体写入需要缓冲
按照面向对象的思想,为了提高扩展,通过继承的方式完成。
Writer
|--TextWriter
|--BufferedTextWriter
|--MediaWirter
|--BufferedMedieWriter
这就完成了文本和媒体数据写操作效率提高。
当如果该体系加入一个子类BaseWriter,而且该子类也需要效率提高。
Writer
|--TextWriter
|--BufferedTextWriter
|--MediaWirter
|--BufferedMedieWriter
|--BaseWriter
|--BufferedBaseWriter
如果体系扩展,都需要定义一个该子类具备高效缓冲功能的子类。这样体系扩展很麻烦。如何把这个体系出现问题解决一下,并优化一下呢?我们发现,这些子类使用的缓冲技术都是一样的。缓冲区其实就是定义了临时存储容器将数据进行临时缓冲,至于具体的写操作,还是Writer的子类对象完成的,比如 TextWriter. MediaWriter等。既然这样,可以将缓冲技术单独封装成一个对象,要对哪个具体对象进行缓冲技术的使用,只要将该对象传递给缓冲区对象即可。
//对缓冲区对象进行单独描述。
classBufferedWriter extends Writer
{
BufferedWriter(Writer w)
{
}
// BufferedWriter(TextWriter tw)
// {}
// BufferedWriter(MediaWriter dw)
// {}
}
当缓冲技术单独封装成了对象后,它具备的还是写功能,只不过比其他对象的写功能进行高效。所以它还是Writer类中的一员。
所以这时体系变成了
Writer
|--TextWriter
|--MediaWirter
|--BufferedWriter:这是一个提供增强功能的类。就把这种优化方式,定义成一种最终的解决该问题的解决方案并起个名字:装饰设计模式。
和原来的体系,变的很清爽。
Writer
|--TextWriter
|--BufferedTextWriter
|--MediaWirter
|--BufferedMedieWriter
装饰设计模式的出现可以对一组类进行功能的增强。而且装饰类本事也是该体系中的一个子类。
代码体现:
通常情况下,
装饰类一般不单独存在。
都是通过构造函数接收被装饰的对象。
基于被装饰的对象的功能,并对外提供增强行的功能。
和继承的区别:
继承会让体系变得臃肿,
装饰更为灵活。
在IO中装饰设计模式用的很多。
比如
BufferedWriter
BufferedReader
>>既然发现BufferedWriter等是装饰类,那么,我们自己也可以定义装饰类
(注,这个是迄今为止我调试最长时间的程序,因为一个小小的分号)
eg:
importjava.io.*;
classMyBufferedReaderDemo
{
private Reader r;
MyBufferedReaderDemo(Reader r)
{
this.r = r;
}
public String myReadLine()throwsIOException
{
StringBuilder sb = newStringBuilder();
System.out.println("----------分割测试六----------");
int ch = 0;
while((ch = r.read())!=-1)
{
if((char)ch == '\r')
{
System.out.println("----------分割测试七----------");
continue;
}
if((char)ch == '\n')
{
System.out.println("----------分割测试八----------");
returnsb.toString();
}
else
{
System.out.println("----------分割测试九----------");
sb.append((char)ch);
System.out.println("----------分割测试十----------");
}
}
if(sb.length()!=0)
{
returnsb.toString();
}
return null;
}
public void myClose()throws IOException
{
r.close();
}
}
classMyBufferedReader
{
public static void main(String[]args)throws IOException
{
FileReader fr = newFileReader("MyBufferedReaderTxt.txt");
MyBufferedReaderDemo buf =new MyBufferedReaderDemo(fr);
System.out.println("----------分割测试一----------");
String line = null;
System.out.println("----------分割测试二----------");
while((line =buf.myReadLine())!=null);//问题在这里,分号
{
System.out.println("----------分割测试三----------");
System.out.println(line+" line test!");
System.out.println("----------分割测试四----------");
}
System.out.println("----------分割测试五----------");
buf.myClose();
}
}
上面是出问题的程序,加上了输出语句明确执行路径,判断那里出了问题,结果如下
F:\java\workspace>javaMyBufferedReader
----------分割测试一----------
----------分割测试二----------
----------分割测试六----------
----------分割测试九----------
----------分割测试十----------
----------分割测试九----------
----------分割测试十----------
----------分割测试九----------
----------分割测试十----------
----------分割测试九----------
----------分割测试十----------
----------分割测试七----------
----------分割测试八----------
----------分割测试六----------
----------分割测试九----------
----------分割测试十----------
----------分割测试九----------
----------分割测试十----------
----------分割测试九----------
----------分割测试十----------
----------分割测试九----------
----------分割测试十----------
----------分割测试七----------
----------分割测试八----------
----------分割测试六----------
----------分割测试九----------
----------分割测试十----------
----------分割测试九----------
----------分割测试十----------
----------分割测试九----------
----------分割测试十----------
----------分割测试九----------
----------分割测试十----------
----------分割测试六----------
----------分割测试三----------
null line test!
----------分割测试四----------
----------分割测试五----------
可以看到九和十一直循环,证明当循环出了问题!
改正后的程序:
importjava.io.*;
classMyBufferedReaderDemo
{
private Reader r;
MyBufferedReaderDemo(Reader r)
{
this.r = r;
}
public String myReadLine()throwsIOException//自定义ReadLine方法
{
StringBuilder sb = newStringBuilder();
int ch = 0;
while((ch = r.read())!=-1)
{
if((char)ch == '\r')
{
continue; //读到\r,就去判断下一个是不是\n
}
if((char)ch == '\n')
{
returnsb.toString();
}
else
{
sb.append((char)ch);
}
}
if(sb.length()!=0)
{
returnsb.toString();
}
return null;
}
public void myClose()throws IOException
{
r.close();
}
}
classMyBufferedReader
{
public static void main(String[]args)throws IOException
{
FileReader fr = newFileReader("MyBufferedReaderTxt.txt");
MyBufferedReaderDemo buf =new MyBufferedReaderDemo(fr);
String line = null;
while((line =buf.myReadLine())!=null)
{
System.out.println(line);
}
buf.myClose();
}
}
>>BufferedReader下还有一个子类LineNumberReader,可以生成文本的行号
java.lang.Object
|--java.io.Reader
|--java.io.BufferedReader
|--java.io.LineNumberReader
它复写了BufferedReader的readLine方法:
publicString readLine()
//throwsIOException读取文本行。无论何时读取行结束符,当前行号都将加 1。
eg:importjava.io.*;
classLineNumberReaderDemo
{
public static void main(String[]args)throws IOException
{
FileReader fr = newFileReader("MyBufferedReaderTxt.txt");
LineNumberReader lnbr = newLineNumberReader(fr);//装饰类
lnbr.setLineNumber(7);设定行号从7开始
String line = null;
while((line =lnbr.readLine())!=null)//这里行号会加一
{
//lnbr.setLineNumber(7);
//如果这样干,所有的行号都是7
System.out.println(lnbr.getLineNumber()+":"+line);//行号+文本内容
}
lnbr.close();
}
}
输出结果://注意这里的8
8:abc1
9:abc2
10:abc3
那么,我们也可以自己定义LineNumberReader,既然它是BufferedReader的子类,那么我们就让我们自定义的“LineNumberReader”继承上一个主题中的例子的“myMyBufferedReaderDemo”
eg:自定义的LineNumberReader类
classMyLineNumberReader extends MyBufferedReaderDemo
{
private int lineNumber;//有set和get方法,必然有一个私有变量
MyLineNumberReader(Reader r)
{
super(r);//父类构造方法
}
public String myReadLine()throwsIOException
{
lineNumber++;
return super.myReadLine();
}
public void setLineNumber(intlineNumber)
{
this.lineNumber = lineNumber;
}
public int getLineNumber()
{
return lineNumber;
}
}
继承太帅了!
>>以上为字符流,现在开始字节流之旅
字节流:
InputStream
OutputStream
注意字符流和字节流的一个重要区别,就是操作的数据不同,比如其中的方法:
voidwrite(byte[] b)//将 b.length 个字节从指定的 byte 数组写入此输出流。
voidwrite(char[] cbuf)//写入字符数组。
eg:写的演示
importjava.io.*;
classOutPutStreamWriteDemo
{
public static void main(String[]args)throws IOException
{
FileOutputStream fos = newFileOutputStream("OutPutStreamWriteDemoTxt.txt");
byte[] bytes ="abcdefghijklmn".getBytes();
fos.write(bytes);
fos.close();
}
}
eg:读的演示
importjava.io.*;
classInputStreamReadDemo
{
public static void main(String[]args)throws IOException
{
FileInputStream fis = newFileInputStream("OutPutStreamWriteDemoTxt.txt");
//两个动作,堆内存建立对象,调用windows资源
int by = 0;
while((by = fis.read()) !=-1)
{
System.out.print((char)by);
}
fis.close();//释放windows资源
fis = null;//对象还在堆内存中,使其变垃圾,自动回收
}
}
>>复制一张图片
importjava.io.*;
class CopyPic
{
public static void main(String[] args)throws IOException
{
FileInputStream fis = newFileInputStream("c:\\0.bmp");
FileOutputStream fos = newFileOutputStream("c:\\1.bmp");
byte[] buf = new byte[1024];
int len = 0;
while((len=fis.read(buf))!=-1)
{
fos.write(buf,0,len);
}
fos.close();
fis.close();
}
}
可不可以通过字符流复制该图片。不可以。因为读取图片数据时,字符流会查默认的编码表。
如果在编码中没有查到具体数据,就用其他数据替代。这时源数据错乱。导致生成图片无法观看。体积也会不同。
记住:只要操作数据是文本,建议使用字符流。因为它可以进行指定的编码的转换
除此之外,都用字节流。
>>其它的复制方式
eg:其它复制方式的演示,涉及几个新方法
importjava.io.*;
classOtherCopy
{
public static void main(String[] args)throws IOException
{
copy_3();
}
public static void copy_3()throwsIOException
{
FileInputStream fis = newFileInputStream("c:\\1.mp3");
FileOutputStream fos = newFileOutputStream("c:\\4.mp3");
int by = 0;
while((by = fis.read())!=-1)
{
fos.write(by);
}
fos.close();
fis.close();
}//这个方法让硬盘磁头一直游走,非常慢
public static void copy_2()throwsIOException
{
FileInputStream fis = newFileInputStream("c:\\1.mp3");
BufferedInputStream bufis =new BufferedInputStream(fis);
FileOutputStream fos = newFileOutputStream("c:\\3.mp3");
BufferedOutputStream bufos =new BufferedOutputStream(fos);
int by = 0;
while((by =bufis.read())!=-1)
{
bufos.write(by);
}
bufos.close();
bufis.close();
}
public static void copy_1()throwsIOException
{
FileInputStream fis = newFileInputStream("c:\\1.mp3");
//int len = fis.available();//获取和流相关联的文件的字节数
FileOutputStream fos = newFileOutputStream("c:\\2.mp3");
//定义一个刚刚好大小的数组。对于小体积数据可以使用。
byte[] buf = newbyte[fis.available()];//也可以作为获取文件体积的依据。
fis.read(buf);// 将所有数据存储到该数组中。
fos.write(buf);//将数组中的数据写入目的地中。
fis.close();
}
}
>>一般都要键盘录入,如何获取键盘录入的数据?
查阅API文档:
in
public static final InputStream in“标准”输入流。此流已打开并准备提供输入数据。通常,此流对应于键盘输入或者由主机环境或用户指定的另一个输入源。
eg:演示单个录入和连续录入方法
importjava.io.*;
class TransStream
{
public static void main(String[] args)throws IOException
{
//readIn();
printUpper();
}
/*
需求:
通过键盘录入数据,将数据转成大写打印在屏幕上。
当录入的数据是over时,结束打印程序。
思路:
1,通过System.in.read()获取键盘录入的字节。
2,通过循环不断的获取录入的字节。
3,定义一个临时存储容器,将录入的数据存储起来。
4,判断行标记,当满足一行时,将存储的数据转成字符串,
并判断是否是over,不是就转成大写打印,是,就结束程序。
*/
public static void printUpper()throwsIOException
{
int ch = 0;
InputStream in = System.in;
StringBuilder sb = newStringBuilder();
while(true)
{
ch = in.read();
/*
if(ch==-1)//键盘输入-1是两个字节,不可能结束
//通过控制台的ctrl+c可以强制结束录入,其实就是给流定义了结束标记。
{
System.out.println("----");
break;
}
//测试输入-1能否起到结束程序的效果,显然不行
*/
if(ch=='\r')
continue;
if(ch=='\n')
{
String s =sb.toString();
if(s.equals("over"))
break;
System.out.println(s.toUpperCase());
//清空缓冲区。否则输出内容会叠加
sb.delete(0,sb.length());
}
else
sb.append((char)ch);
}
}
public static void readIn()throwsIOException
{
InputStream in = System.in;//输入a
int b1 = in.read();//读取键盘录入。
System.out.println(b1);//输出97为a
int b2 = in.read();//读取键盘录入。
System.out.println(b2);//输出13为’\r’
int b3 = in.read();//读取键盘录入。
System.out.println(b3);//输出10为’\n’
}
}
>>如何转换字符流和字节流
类 InputStreamReader:InputStreamReader 是字节流通向字符流的桥梁:它使用指定的 charset 读取字节并将其解码为字符。它使用的字符集可以由名称指定或显式给定,或者可以接受平台默认的字符集。
类 OutputStreamWriter:OutputStreamWriter 是字符流通向字节流的桥梁:可使用指定的 charset 将要写入流中的字符编码成字节。它使用的字符集可以由名称指定或显式给定,否则将接受平台默认的字符集。
这两个类是字符流,称之为转换流
eg:演示两个类的使用
importjava.io.*;
classZhuanHuanLiuApplication
{
public static void main(String[]args)throws IOException
{
System.out.println("Youcan enter over to exit!");
InputStream in = System.in;
//转换字节成为字符
InputStreamReader isr = newInputStreamReader(in);
//建立缓冲区
BufferedReader bufr = newBufferedReader(isr);
OutputStream out =System.out;
//转换字符成为字节
OutputStreamWriter osw = newOutputStreamWriter(out);
//建立缓冲区
BufferedWriter bufw = newBufferedWriter(osw);
String line = null;
while((line =bufr.readLine())!=null)
{
if("over".equals(line))
{
break;
}
if("help".equals(line))
{
System.out.println("Youcan enter over to exit!");
continue;
}
bufw.write(line.toUpperCase());
bufw.newLine();
bufw.flush();
}
bufr.close();
bufw.close();
}
}
(个人理解:怎么理解字符转换成字节输出呢?因为我们看到的内容是输出到控制台的,而熟悉的System.out.println语句实际上调用PrintStream 类中的 println 方法。对于println方法的解释就是:打印字符。按照平台的默认字符编码将字符转换为一个或多个字节,并完全以 write(int) 方法的方式写入这些字节。)
>>例子:
需求1:输入数据,转成大写,输出到文件保存
eg:importjava.io.*;
classZhuanHuanLiuFileSave
{
public static void main(String[]args)throws IOException
{
//输入流
BufferedReader bufr =
newBufferedReader(new InputStreamReader(System.in));
//输出流
BufferedWriter bufw =
new BufferedWriter(new OutputStreamWriter(new FileOutputStream("ZhuanHuanLiuFileSaveTxt.txt")));
/*简化的输出到控制台语句
BufferedWriter bufw = new BufferedWriter(newOutputStreamWriter(System.out));*/
String line = null;
while((line =bufr.readLine())!=null)
{
if("over".equals(line))
{
break;
}
bufw.write(line.toUpperCase());
bufw.newLine();
bufw.flush();
}
bufw.close();
bufr.close();
}
}
需求2:从文件读入数据,转成大写,输出到控制台
eg://改变流语句便可
………………
//输入流
BufferedReader bufr =
new BufferedReader(new InputStreamReader(new FileInputStream("ZhuanHuanLiuFileSaveTxt2.txt")));
//输出流
BufferedWriterbufw =
newBufferedWriter(new OutputStreamWriter(System.out));
………………
还有需求3,将一个已有的文件,转成大写,保存到另一个文件中,很简单。将需求2中的“System.out”改成“new FileOutputStream("ZhuanHuanLiuFileSaveTxt.txt")”即可。
>>流的操作规律。
因为io包中的对象很多,最重要的是要知道完成数据处理是,要使用哪个对象最合适。
如何判断要使用哪些对象呢?
通过几个明确来判断对象的使用。
1,明确数据源,和 数据目的(数据汇)
数据源:InputStream Reader
数据目的:OutputStream Writer
2,明确数据的内容是否是纯文本。只要是纯文本数据,就使用字符流。
数据源: 是: Reader。
数据目的:是:Writer
如果不是,就使用InputStream或者OutputStream
如果数据不能明确,只有使用字节流。
这样就可以将四个基类,进行确定,要使用哪一个。
3,明确具体设备。
数据源:键盘(System.in),内存(数组),硬盘(File开头的流对象)。
数据目的: 控制台(System.out),内存(数组),硬盘(File开头的流对象)。
4,明确是否需要提高效率?
是:使用带Buffer对象。
5,是否需要一些特殊场景的操作,来完成数据的特殊处理。
需求1:复制一个文本文件。
1,
数据源:InputStrea,Reader
数据目的:OutputStream,Writer
2,
是否是纯文本。
是。
数据源:Reader
数据目的:Writer
3,
明确设备:
数据源:是一个文件,硬盘设备。FileReader
数据目的:是一个文件。硬盘设备。FileWriter.
代码就已经出来了。
FileReader fr = newFileReader("a.txt");
FileWriter fw = newFileWriter("b.txt");
4,
需要高效吗?
需要。
FileReader fr = newFileReader("a.txt");
BufferedReader bufr = new BufferedReader(fr);
FileWriter fw = newFileWriter("b.txt");
BufferedWriter bufw = newBufferedWriter(fw);
-----------------
需求2:将键盘录入的数据存储到一个文件中。
1,
数据源:InputStrea,Reader
数据目的:OutputStream,Writer
2,
是否是纯文本?
是。
数据源:Reader
数据目的:Writer.
3,
设备:
数据源:System.in;为了方便于操作数据源的字节数据,对其进行转换。使用转换流。
数据目的:硬盘文件。FileWriter.
发现一个问题,就是数据源是一个字节流。
因为纯文本使用字符流操作最方便,
所以,将数据源设备对应的字节流转成字符流。
InputStreamReader isr = new InputStreamReader(System.in);
FileWriter fw = newFileWriter("a.txt");
4,
需要高效吗?需要。
BufferedReader bufr = newBufferedReader(new InputStreamReader(System.in));
BufferedWriter bufw = newBufferedWriter(new FileWriter("a.txt"));
-----------------
需求3:将一个文本文件打印到控制台
1,
数据源:InputStrea,Reader
数据目的:OutputStream,Writer
2,
是否是纯文本?
是。
数据源:Reader
数据目的:Writer.
3,
数据源:硬盘文件FileReader
数据目的:控制台 System.out.因为目的是字节流,而操作的是字符流,所以要将字符流转成字节流到目的中。
使用到了转换流。OutoutStreamWriter
// FileReader fr = new FileReader("a.txt");
// PrintStream ps = System.out;//还没学到。所以改了。
FileReader fr= newFileReader("a.txt");
OutputStreamWriter osw = newOutputStreamWriter(System.out);
需要缓冲高效吗?需要。
BufferedReader bufr = newBufferedReader(new FileReader("a.txt"));
BufferedWriter bufw = newBufferedWriter(new OutputStreamWriter(System.out));
-----------------
需求4:后去键盘录入,并将数据打印到控制台。
自己完成。
>>流的操作涉及编码表
需求:复制一个文本文件。将一个GBK编码的文件,复制到另一个用UTF-8编码的文件中。
1,
数据源:InputStrea,Reader
数据目的:OutputStream,Writer
2,
是否是纯文本?
是。
数据源:Reader
数据目的:Writer.
3,
设备:
数据源:硬盘文件
数据目的:硬盘文件。
涉及到编码数据。
源:是gbk FileReader
目的:因为是UTF-8,所以只能使用转换流。转换流中需要明确具体的字节流和编码。
编码是UTF-8,字节流就是对应硬盘设备的FileOutputStream。
FileReader fr = newFileReader("a.txt");
OutputStreamWriter osw = new OutputStreamWriter(newFileOutputStream("b.txt"),"utf-8");
需要高效就加上buffer
Writer
|--OutputStreamWriter
|--FileWriter
Reader
|--InputStreamReader
|--FileReader
转换流其实就是将字节流和编码表相结合。将字节流中的字节数据,去查了具体的编码表。所以转换流才可以获取一个中文字符。
那么转换流的子类用于操作文件的对象FileReader 就直接使用父类的具有转换功能的read方法。就可以一次读一个字符。
FileReaderfr = new FileReader("a.txt");//该对象中已经内置了本机默认的字符编码表,
//对于简体中文版的机器默认编码表是GBK.
//通过字节读取流读取a.txt中中文数据,按照GBK编码表的来获取中文字符。
InputStreamReaderisr = new InputStreamReader(newFileInputStream("a.txt"),"GBK");
InputStreamReaderisr = new InputStreamReader(newFileInputStream("a.txt"),"GBK");
FileReaderfr = new FileReader("a.txt");
这两个句代码的功能是一样一样一样的。
区别:
第一句可以指定编码表。
第二句,固定本机默认编码表。
如果操作中文数据,仅使用本机默认码表,那么第二句书写简单。
如果操作中文数据,使用指定码表,必须使用第一句。 而且要将指定码表作为字符串传递到构造函数中。
eg:编码实例
importjava.io.*;
classBianMaDemo
{
public static void main(String[]args)throws IOException
{
FileReader fr = newFileReader("BianMaDemoTxt.txt");//GBK编码体系
OutputStreamWriter osw =
newOutputStreamWriter(new FileOutputStream("BianMaDemoTxtCopy.txt"),"utf-8");
/*如果FileReaderfr = new FileReader("BianMaDemoTxtCopy.txt");
会造成编码错乱*/
char[] buf = new char[10];
int len = fr.read(buf);
osw.write(buf,0,len);
fr.close();
osw.close();
}
}
>>————————————阶段分割——————————
>>File类,描述文件的类。流针对的是数据,而操作文件及其属性,需要另外的方法。
API描述:
java.io
类 File
java.lang.Object
java.io.File
所有已实现的接口:
Serializable, Comparable<File>
eg:演示File的构造方法以及分割符
………………
publicstatic void method_1()
{
//将指定文件封装成File对象。
File f = new File("c:"+File.separator+"2.bmp");//“\\”被取代
//System.out.println(f);
File f5 = newFile("c:\\abc\\a.txt");
File f1 = newFile("c:\\","a.txt");
File dir = newFile("c:\\");
File f2 = newFile(dir,"a.txt");
}
}
………………
由于\\是windows下的分割符,那么要提高平台的兼容性,可以使用File.separator代替分割符
>>File基本功能,创建文件和目录
eg:演示
importjava.io.*;
classCreateFileDemo
{
public static void main(String[]args)throws Exception
{
function_1();
function_2();
function_3();
}
public static void function_1()throws IOException
{//创建一个文件
File f = newFile("CreateFileDemoTxt.txt");
boolean b =f.createNewFile();
//往硬盘写数据,会抛出异常
//如果要创建的文件存在,返回false,注意和输出流的区别
System.out.println("Success?="+b);
}
public static void function_2()throwsSecurityException
{//创建一个目录
File f = newFile("CreateFileDemoFile");
boolean b = f.mkdir();//只能创建单个目录
System.out.println("Success?="+b);
}
public static void function_3()
{//创建层级目录
File f = newFile("CreateFileDemoFileCeng\\aa\\vv\\sda\\asdf\\regw\\qw");
boolean b = f.mkdirs();//可以创建多级目录,可以在已经创建的目录中添加目录
System.out.println("Success?="+b);
}
}
>>File类基本功能:删除
eg:importjava.io.*;
classDeleteFileDemo
{
public static void main(String[]args)throws Exception
{
function_1();
}
public static void function_1()
{//删除目录
File f = newFile("CreateFileDemoFile");
/*
windows目录的删除必须是从里往外删,如果要删除一个目录
而目录里有文件或者文件夹,那么会删除失败
*/
boolean b = f.delete();//删除方法
System.out.println("Successto delete?"+b);
}
}
还有一个特殊方法:voiddeleteOnExit()
void show()
{
创建一个文件。
deleteOnExit();//告诉jvm,程序退出,一定要把该文件删除。
操作这个文件。
//删除这个文件。
}
解释,如果我需要这样一个三步的操作,但是在“操作这个文件”环节异常了,退出。那么,删除这个文件就没有执行,为了解决这个隐患,提前告诉虚拟机,退出时,删除该文件。
>>File基本功能,判断
eg:importjava.io.*;
classFilePanDuanDemo
{
public static void main(String[]args)throws Exception
{
function_1();
}
public static void function_1()throwsIOException
{//演示判断文件和文件夹是否存在等等
File f = newFile("PanDuanDemo.txt");
f.mkdir();
//f.createNewFile();
/*
注意:不要想当然的认为xx.txt一定是文件。
文件夹也可以叫PanDuanDemo.txt。
而文件也可以没有诸如txt的后缀。
要看创建的是什么!
*/
boolean b = f.exists();//判断是否存在
System.out.println("exists():"+b);
if(b)//文件或文件夹存在才判断剩余的项
{
System.out.println("isFile:"+f.isFile());//是不是文件?
System.out.println("isDirector:"+f.isDirectory());//是不是目录?
System.out.println("isAbsolute:"+f.isAbsolute());//是不是绝对路径?
}
else
{
System.out.println("判断的目标不存在!");
}
}
}
其它重要方法:
booleancanExecute() //是否是可以执行的文件。
booleancanRead() //是否是可以读取的文件。
booleancanWrite() //是否是可以写的文件。
booleanisHidden() //是否是一个隐藏文件。
booleanequals(Object obj) //测试此抽象路径名与给定对象是否相等。
intcompareTo(File pathname) //按字母顺序比较两个抽象路径名。
>>File基本功能,获取
eg:publicstatic void method()
{
File f = newFile("a.txt");
long time = f.lastModified();//最后修改的时间,返回毫秒值
Date d = new Date(time);
DateFormat df =DateFormat.getDateTimeInstance(DateFormat.LONG,DateFormat.LONG);
String s = df.format(d);
System.out.println("lastModified():"+s);//可以阅读的时间
System.out.println("exists:"+f.exists());
System.out.println("length:"+f.length());//返回的是该文件的字节数。
//该方法针对文件而言,文件夹没有大小。
System.out.println("getName:"+f.getName());
System.out.println("getParent:"+f.getParent());
System.out.println("getAbsolutePath:"+f.getAbsolutePath());
//获取的封装内容的绝对路径,也就是完整路径。
System.out.println("getPath:"+f.getPath());//获取file内封装的路径内容。
}
>>重命名
eg:publicstatic void method()
{
File f= new File("qq.txt");
File f1 = newFile("c:\\ww.txt");
f.renameTo(f1);
/*
如果f1中是一个路径,那么
将f的文件名改成f1的文件名,
可以进行文件的移动(剪切+重命名)。
*/
}
>>获取方面还有一类比较特殊的方法,获取文件列表
String[]list()
返回一个字符串数组,这些字符串指定此抽象路径名表示的目录中的文件和目录。
String[]list(FilenameFilter filter)
返回一个字符串数组,这些字符串指定此抽象路径名表示的目录中满足指定过滤器的文件和目录。
File[]listFiles()
返回一个抽象路径名数组,这些路径名表示此抽象路径名表示的目录中的文件。
File[]listFiles(FileFilter filter)
返回抽象路径名数组,这些路径名表示此抽象路径名表示的目录中满足指定过滤器的文件和目录。
File[]listFiles(FilenameFilter filter)
返回抽象路径名数组,这些路径名表示此抽象路径名表示的目录中满足指定过滤器的文件和目录。
staticFile[] listRoots()
列出可用的文件系统根。
eg:获取演示
import java.io.*;
classFileListFunction
{
public static void main(String[] args)
{
//function_1();
function_2();
}
//获取某一目录下所有文件和文件夹
public static void function_2()
{
File dir = newFile("f:\\");
//File dir = newFile("0.txt");
String[] name = dir.list();
//获取当前目录下的文件夹和文件的名称包含隐藏文件。
//如果File中封装的是一个文件,那么返回的数组为null。
//所以此处最好为了安全性做一个判断。
if(name == null)
{
System.out.println("notfound!");
}
else
{
for(String str :name)
{
System.out.println(" "+str);
}
}
}
//获取系统可用盘符
public static void function_1()
{
File[] roots =File.listRoots();
for(File root:roots)
{
System.out.println(root);
}
}
}
小总结:
staticFile[] listRoots():获取有效盘符。
String[]list():获取的是当前目录下文件或者文件夹的名称。
File[]listFiles():获取的是当前目录下文件或者文件夹对应的对象。
如果仅获取文件名称,就用list方法。如果还要获取文件的其他信息,最好使用listFiles。因为它可以获取到文件对象。
这样就可以通过文件对象的方法,获取其他的内容。比如;文件大小,文件名称。修改时间等信息。
另外,还可以添加过滤器,获取特定的元素!
eg:importjava.io.*;
classFileListFunction
{
public static void main(String[] args)
{
File f = newFile("f:\\java");
//function_3();
function_4(f,0);
}
//获取目录下所有文件包括子目录(精彩方法,重要)
public static void function_4(Filedir,int level)
{
System.out.println(getLevel(level)+dir.getName());
File[] files =dir.listFiles();
level++;
for(int x = 0 ;x<files.length ; x++)
{
if(files[x].isDirectory())//是目录吗?
{
function_4(files[x],level);//递归调用
}
System.out.println(getLevel(level)+files[x].getName());
}
}
//使得输出有层级(重要)
public static String getLevel(intlevel)
{
StringBuilder sb = new StringBuilder();
sb.append("|--");
for(int x=0; x<level; x++)
{
//sb.append("|--");
sb.insert(0,"| ");
}
return sb.toString();
}
//带过滤器的list方法
public static void function_3()
{
File f = newFile("F:\\java");
//传入过滤器
String[] name = f.list(newSearchCondition());
System.out.println("find"+name.length+" pcs!"+"\n");
if(name == null)
{
System.out.println("notfound!");
}
else
{
for(String str :name)
{
System.out.println(" "+str);
}
}
}
//过滤器类
class SearchCondition implements FilenameFilter
{
publicboolean accept(File dir,String name)
{
//System.out.println(dir+":"+name);
//使用String类的方法endsWith判断是否是以某个字段结束的
//return name.endsWith(".rar");
//上面这种写法,如果遇到文件夹名叫xx.rar的也会输出,改进
//先判断一下是不是文件啊!再判断后缀
return ( new File(dir,name).isFile() &&name.endsWith(".rar") );
}
}
}
>>递归
eg:
classDiGuiDemo
{
public static void main(String[] args)
{
//toBin(60);
System.out.println(sum(10));
//System.out.println(sum(9000));//内存溢出
}
public static int sum(int num)
{
if(num == 1)
return 1;
else
return num +sum(--num);
}
public static void toBin(int num)//栈内存
{
if(num > 0)
{
toBin(num/2);
System.out.print(num%2);
}
}
}
递归作为一种编程手法。也有缺点:
当一个功能在被重复使用时,该功能的参数在随着功能变化。这时可以使用递归完成.
需要注意:
1,需要控制递归次数,不要过大。
2,递归必须要有条件。
否则,会出现栈内存溢出。
>>练习:删除一个带内容目录。
注意:window中删除一个空目录,直接用delete方法就可以了
而删除一个带内容的目录。必须要从里往外删除才可以。
所以要递归这个目录。
eg:importjava.io.*;
classRemoveDir
{
public static void main(String[] args)
{
File dir = newFile("F:\\test");
deleteDir(dir);
}
public static void deleteDir(File dir)
{
File[] files =dir.listFiles();
//出现了一个问题,有些时候会报空指针异常
//一个原因是读取到了隐藏的系统文件,所以多做一个判断
if(files != null)
{
for(int x = 0 ; x< files.length ; x++)
{
if(files[x].isDirectory())
{
deleteDir(files[x]);
}
else//如果没有写else,文件夹是没法删除的,等于会执行多余的步骤
{
System.out.println(files[x]+":"+files[x].delete());
}
}
System.out.println(dir+":"+dir.delete());//最后删除文件夹
}
}
}
>>io中的打印流
PrintStream 字节,
构造函数的参数特点:
1,字符串路径。
2,File对象。
3,字节输出流。
API定义:PrintStream为其他输出流添加了功能,使它们能够方便地打印各种数据值表示形式。它还提供其他两项功能。与其他输出流不同,PrintStream 永远不会抛出 IOException;而是,异常情况仅设置可通过 checkError 方法测试的内部标志。另外,为了自动刷新,可以创建一个 PrintStream;这意味着可在写入 byte 数组之后自动调用 flush 方法,可调用其中一个 println 方法,或写入一个换行符或字节 ('\n')。
PrintWriter字符,
PrintWriter:字符流中的打印流。
构造函数的参数特点:
1,字符串路径。
2,File对象,
3,字节输出流。
4,字符输出流。
API定义:向文本输出流打印对象的格式化表示形式。此类实现在 PrintStream 中的所有 print 方法。它不包含用于写入原始字节的方法,对于这些字节,程序应该使用未编码的字节流进行写入。
与 PrintStream类不同,如果启用了自动刷新,则只有在调用 println、printf 或 format 的其中一个方法时才可能完成此操作,而不是每当正好输出换行符时才完成。这些方法使用平台自有的行分隔符概念,而不是换行符。
此类中的方法不会抛出I/O 异常,尽管其某些构造方法可能抛出异常。客户端可能会查询调用 checkError() 是否出现错误。
打印流可以直接操作文件
eg:输入转换演示
importjava.io.*;
classPrintWriterDemo
{
public static void main(String[] args)throws IOException
{
function();
}
public static void function() throwsIOException
{
BufferedReader bufr = newBufferedReader(new InputStreamReader(System.in));
PrintWriter pw = newPrintWriter(System.out,true);//true自动刷新
String line = null;
while((line =bufr.readLine())!=null)
{
if(line.equals("over"))
{
break;
}
pw.println(line.toUpperCase());
//pw.flush();此处的刷新,可以通过构造函数自动解决
}
}
}
关于自动刷新的构造函数
PrintWriter
publicPrintWriter(OutputStream out,boolean autoFlush)
通过现有的 OutputStream 创建新的PrintWriter。
此便捷构造方法创建必要的中间OutputStreamWriter,
后者使用默认字符编码将字符转换为字节。
参数:
out - 输出流
autoFlush- boolean 变量;如果为 true,则 println、printf 或 format 方法将刷新输出缓冲区
新的需求:操作文件但是又想自动刷新,如何解决?
注意,如果操作文件的构造函数是不带自动刷新的
PrintWriter(StringfileName)
创建具有指定文件名称且不带自动行刷新的新PrintWriter。
PrintWriter(StringfileName, String csn)
创建具有指定文件名称和字符集且不带自动行刷新的新 PrintWriter。
eg:操作文件演示
………………
PrintWriterpw = new PrintWriter(newFileWriter("PrintWriterFileDemoTxt.txt"),true);
//将文件封装成一个流对象即可以使用自动刷新了,非常简单
………………
>>管道流
PipedInputStream
管道输入流应该连接到管道输出流;管道输入流提供要写入管道输出流的所有数据字节。通常,数据由某个线程从 PipedInputStream 对象读取,并由其他线程将其写入到相应的 PipedOutputStream。不建议对这两个对象尝试使用单个线程,因为这样可能死锁线程。管道输入流包含一个缓冲区,可在缓冲区限定的范围内将读操作和写操作分离开。如果向连接管道输出流提供数据字节的线程不再存在,则认为该管道已损坏。
PipedOutputStream
可以将管道输出流连接到管道输入流来创建通信管道。管道输出流是管道的发送端。通常,数据由某个线程写入 PipedOutputStream 对象,并由其他线程从连接的 PipedInputStream 读取。不建议对这两个对象尝试使用单个线程,因为这样可能会造成该线程死锁。如果某个线程正从连接的管道输入流中读取数据字节,但该线程不再处于活动状态,则该管道被视为处于毁坏 状态。
读取流和写入流可以进行连接。
但是需要被多线程操作。
因为read方法是阻塞式方法。
容易引发死锁。
eg:importjava.io.*
classPipedStreamDemo
{
public static void main(String[]args)throws IOException
{
PipedInputStream pins = newPipedInputStream();
PipedOutputStream pous = newPipedOutputStream();
pins.connect(pous);//管道连接方法
Thread ins = new Thread(newInput(pins));
Thread ous = new Thread(newOutput(pous));
ins.start();
ous.start();
}
}
classInput implements Runnable//实现线程
{
private PipedInputStream in;
Input(PipedInputStream in)
{
this.in = in;
}
public void run()//复写run方法,注意,不能在run方法上抛异常
{
try
{
byte[] by = newbyte[1024];
int len =in.read(by);
String str = newString(by,0,len);
System.out.println(str);
in.close();
}
catch(IOException e)
{
throw newRuntimeException("读取失败");
}
}
}
classOutput implements Runnable
{
private PipedOutputStream out;
Output(PipedOutputStream out)
{
this.out = out;
}
public void run()//注意,不能在run方法上抛异常
{
try
{
System.out.println(Thread.currentThread().getName()+".......写入开始");
out.write("run!".getBytes());
out.close();
}
catch(IOException e)
{
throw newRuntimeException("写入失败");
}
}
}
>>重点掌握对象:Properties
类 Properties
java.lang.Object
java.util.Dictionary<K,V>
java.util.Hashtable<Object,Object>
java.util.Properties
所有已实现的接口:
Serializable,Cloneable, Map<Object,Object>
直接已知子类:
Provider
Properties:该集合中存储的键和值都是字符串类型的数据,通常用配置文件的定义。
eg:方法演示
importjava.io.*;
importjava.util.*;
classPropertiesDemo
{
public static void main(String[]args)throws Exception
{
function_1();//基本添加和取出
function_2();//演示list方法
function_3();//演示取出系统属性集
function_4();//演示load方法,将硬盘中的键值对加载到Properties
function_5();//模拟一个load方法,即load方法原理
function_6();//修改硬盘中文件的内容并存储到源文件中
}
//基本添加和取出
public static void function_1()
{
//输出是无序的
Properties pps = newProperties();
pps.setProperty("cty1","1");
pps.setProperty("cty2","2.2");
pps.setProperty("cty3","3");
pps.setProperty("cty4","1.5");
Set<String> keySet =pps.stringPropertyNames();
for(String name:keySet)
{
System.out.println("Key="+name+":Value="+pps.getProperty(name));
}
}
/*特有方法:
void list(PrintStream out)
将属性列表输出到指定的输出流。
void list(PrintWriter out)
将属性列表输出到指定的输出流。
看到参数,证明只接收打印流
*/
//演示list方法
public static void function_2()
{
Properties pps = newProperties();
//PrintWriter pw = newPrintWriter(new OutputStreamWriter(System.out));
pps.setProperty("cty1","1");
pps.setProperty("cty2","2.2");
pps.setProperty("cty3","3");
pps.setProperty("cty4","1.5");
pps.list(System.out);//打印到控制台
//pps.list(pw);//打印到控制台
//pw.flush();
//pw.close();
}
//演示取出系统属性集
public static void function_3()
{
/*
还记得System类中的方法么?
static PropertiesgetProperties()//确定当前的系统属性。
*/
Properties prop =System.getProperties();
prop.list(System.out);
//prop.list(newPrintStream("sys.txt"));
}
/*特有方法:
void load(InputStream inStream)
从输入流中读取属性列表(键和元素对)。
voidload(Reader reader)
按简单的面向行的格式从输入字符流中读取属性列表(键和元素对)。
*/
//演示load方法,将硬盘中的键值对加载到Properties
public static void function_4()throwsIOException
{
Properties pps = newProperties();
System.out.println(pps);
FileInputStream fis = newFileInputStream("PropertiesDemoInfo.txt");
//将流的特定规则信息存储到集合中,注意:流中的信息必须有规则是键值对。用=分隔
pps.load(fis);
System.out.println(pps);
}
//模拟一个load方法,即load方法原理
public static void function_5()throwsIOException
{
BufferedReader bufr = newBufferedReader(new FileReader("PropertiesDemoInfo.txt"));
Properties pps = newProperties();
String str = null;
while((str = bufr.readLine())!= null)
{
String[] arr =str.split("=");
pps.setProperty(arr[0],arr[1]);
}
System.out.println(pps);
bufr.close();
/*
其实load方法很简单,就是通过流对象,读取文本中一行数据 。
在将该行数据通过=进行切割。左边作为键,右边作为值。
存入到Properties集合中。
*/
}
//修改硬盘中文件的内容并存储到源文件中
public static void function_6()throwsIOException
{
Properties pps = newProperties();
FileInputStream fis = newFileInputStream("PropertiesDemoInfo.txt");
pps.load(fis);
System.out.println(pps);
//修改其中一项的值
pps.setProperty("cty2","3.3");
System.out.println(pps);
//但是文件中的值还是2.2
/*怎么写入硬盘的文件呢?
void store(OutputStream out, String comments)
以适合使用load(InputStream) 方法加载到 Properties 表中的格式,
将此 Properties 表中的属性列表(键和元素对)写入输出流。
String comments是一个注释,会将自定义的内容以及修改的时间
添加到文件中,不要用中文
*/
FileOutputStream fos = newFileOutputStream("PropertiesDemoInfo.txt");
pps.store(fos,"run");//如果传入FileInputStream类会编译错误
fis.close();
fos.close();
}
}
>>练习:获取文件列表,不包括文件夹
(非常经典繁琐绕弯的例子)
eg:importjava.util.*;
importjava.io.*;
classGetFilePathList
{
public static void main(String[]args)throws Exception
{
File file = newFile("f:\\java");//要扫描的目录
File destFile = newFile(file,"workspace\\GetFilePathListTxt.txt");//保存列表的文件
FileFilter sf = newSuffixFilter(".java");
pathToFile(getFileList(file,sf),destFile);
}
//——————主要方法
public static void pathToFile(File[]files,File destFile)throws IOException
{
BufferedWriter bufw = null;
try
{
bufw = newBufferedWriter(new FileWriter(destFile));
for(File f:files)
{
bufw.write(f.getAbsolutePath());
bufw.newLine();
bufw.flush();
}
}
finally
{
try
{
if(bufw !=null)
{
bufw.close();
}
}
catch(IOException e)
{
throw newRuntimeException("关闭异常");
}
}
}
//——————存储指定文件的列表
/*
public static List<File>listFunction(File dir)
{
List<File> arraylist =new ArrayList<File>();
diGuiFunction(dir,arraylist);
return arraylist;
}这里有一个小点:获取的信息是在硬盘中真实存在的,
目的是查询而不是增删
所以限定其长度,数组更加合适。*/
public static File[] getFileList(Filedir,FileFilter filter)
{
List<File> list = newArrayList<File>();
diGuiFunction(dir,list,filter);
File[] files =list.toArray(new File[list.size()]);
return files;
}
//——————递归方法
private static void diGuiFunction(Filedir,List<File> list,FileFilter filter)
{
File[] files =dir.listFiles();
for(File file:files)
{
if(file.isDirectory())
{
diGuiFunction(file,list,filter);
}
else
{
if(filter.accept(file))
{
list.add(file);
}
}
}
}
}
//——————过滤器
classSuffixFilter implements FileFilter
{
private String suffix;
public SuffixFilter(String suffix)
{
this.suffix = suffix;
}
public boolean accept(File pathname)
{
returnpathname.getName().endsWith(suffix);
}
}
>>练习:
要求获取一个程序运行的次数。
如果满足5次,那么该程序退出,请给出注册的提示,并结束。
提示,你需要将次数持久化存储到硬盘上。
思路:
1,定义记录器记录住运行的次数。
因为计数器存储在内存中。当软件打开,计数器计数一次。当软件关掉,计数器就在内存中消失了。
当下次软件运行,计数器就重新开始计数器了。无法完成软件运行次数的记录。
2,希望将这个计数器被每一次应用程序运行所共享。
这就需要该计数器进行持久化存储。
3,光存储数字不合适。并不清楚该数字具体代表什么,所以会给该数字起个名称,以进行说明。
所以需要建立键值对形式的配置文件。
4,可以进行键值持久化存储的集合,就是Properties。可以完成属性配置文件操作。
eg:
importjava.io.*;
importjava.util.*;
classAppRunCount
{
public static void main(String[] args)
{
try
{
boolean b =new RunCount().runCount();
if(!b)
{
System.out.println("请注册");
return;
}
}
catch(IOException e)
{
e.printStackTrace();
throw newRuntimeException("配置文件建立失败");
//System.exit(0);
}
System.out.println("程序运行开始");
System.out.println("程序运行结束");
}
}
classRunCount
{
public boolean runCount()throwsIOException
{
Properties pps = newProperties();
File file = newFile("RunCountTxt.properties");
if(!file.exists())
{
file.createNewFile();
}
FileInputStream fis = newFileInputStream(file);
pps.load(fis);
//通过键获取对应的值
int num = 0;
String value =pps.getProperty("count");
if(value!=null)
{
num =Integer.parseInt(value);
if(num > 4)
{
returnfalse;
}
}
num++;
pps.setProperty("count",num+"");
//把集合中的改变后的数据存储到配置文件中。
FileOutputStream fos = newFileOutputStream(file);
pps.store(fos,"");
fos.close();
fis.close();
return true;
}
}
>>有意思的类,序列流
java.io
类 SequenceInputStream
java.lang.Object
java.io.InputStream
java.io.SequenceInputStream
构造方法摘要
SequenceInputStream(Enumeration<?extends InputStream> e)
通过记住参数来初始化新创建的SequenceInputStream,该参数必须是生成运行时类型为 InputStream 对象的 Enumeration 型参数。
SequenceInputStream(InputStreams1, InputStream s2)
通过记住这两个参数来初始化新创建的SequenceInputStream(将按顺序读取这两个参数,先读取 s1,然后读取 s2),以提供从此 SequenceInputStream 读取的字节。
注意接收的类型Enumeration和InputStream
eg:演示将3个文件的内容复制到一个文件中
importjava.io.*;
importjava.util.*;
classSequenceInputStreamDemo
{
public static void main(String[]args)throws IOException
{
Vector<FileInputStream>ve = new Vector<FileInputStream>();
ve.add(new FileInputStream
("F:\\java\\workspace\\SequenceInputStreamDemoFile\\1.txt"));
ve.add(new FileInputStream
("F:\\java\\workspace\\SequenceInputStreamDemoFile\\2.txt"));
ve.add(new FileInputStream
("F:\\java\\workspace\\SequenceInputStreamDemoFile\\3.txt"));
Enumeration<FileInputStream>en = ve.elements();
SequenceInputStream sis = newSequenceInputStream(en);
FileOutputStream fos = newFileOutputStream
("F:\\java\\workspace\\SequenceInputStreamDemoFile\\4.txt");
byte[] b = newbyte[1024*1024];
int len = 0;
while(true)//对付大文件的方法
{
if((len =sis.read(b)) != -1)
{
fos.write(b,0,len);
}
else
{
break;
}
}
fos.close();
sis.close();
}
}
其中应用到接口:
publicinterface Enumeration<E>
实现Enumeration 接口的对象,它生成一系列元素,一次生成一个。连续调用 nextElement 方法将返回一系列的连续元素。
例如,要输出Vector<E> v 的所有元素,可使用以下方法:
for (Enumeration<E> e = v.elements();e.hasMoreElements();)
System.out.println(e.nextElement());这些方法主要通过向量的元素、哈希表的键以及哈希表中的值进行枚举。枚举也用于将输入流指定到 SequenceInputStream 中。
>>切割文件
eg:
importjava.io.*;
importjava.util.*;
/*
演示文件被切割。
一个读取流对应多个输出流。
按照指定的文件大小的方式进行切割(当然也可以按照文件个数进行切割 )
切割文件应该具备内容:
属性:被切文件。
行为:切割方法。
*/
classFileSplit
{
private File destFile;
FileSplit(File destFile)
{
this.destFile = destFile;
}
public void splitFile()throwsIOException
{
if(!destFile.exists())
{
throw newRuntimeException("被切的文件不存在");
}
FileInputStream fis = newFileInputStream(destFile);
FileOutputStream fos = null;
byte[] buf = newbyte[1024*1024];
int len = 0;
int num = 1;
File dir = newFile("c:\\partfiles");
File file = null;
while((len =fis.read(buf))!=-1)
{
file = newFile(dir,(num++)+".partf");
fos = newFileOutputStream(file);
fos.write(buf,0,len);
fos.close();
}
System.out.println("num="+num);
//必须要产生一个碎片文件信息,方便于合并使用 。
/*
合并文件并不只是使用序列流这么简单,下载碎片文件的人并不知道这些碎片原先是什么文件,并且被切成了一共几个碎片等等,所以,务必还要有一个说明作用的配置文件。
*/
File confFile = newFile(dir,num+".properties");
//文件后缀一般用".properties"
Properties prop = newProperties();
prop.setProperty("filename",destFile.getName());
prop.setProperty("partnum",num+"");
//System.out.println(prop);
fos = newFileOutputStream(confFile);
prop.store(fos,"");
fos.close();
fis.close();
}
}
classFileSplitDemo
{
public static void main(String[] args)throws IOException
{
File destFile = newFile("c:\\0.bmp");
FileSplit fs = newFileSplit(destFile);
fs.splitFile();
}
}
>>文件碎片合并方法
eg:包括判断配置文件;从配置文件中获取信息;判断碎片文件;合并碎片文件
具体事例以后重写
>>通过迭代和匿名内部类来替换Vector集合
eg:vector集合已经过时,使用ArrayList代替,但是ArrayList只能使用迭代器,不能使用Enumeration的枚举功能,演示通过匿名内部类复写Enumeration类。
importjava.io.*;
importjava.util.*;
classMegerFile
{
private File dir;
MegerFile(File dir)
{
this.dir = dir;
}
/*
合并方法应该由多个小功能组成。
1,判断配置文件。
2,获取配置文件信息。
3,获取碎片文件集合。
4,集合中的读取流关联的数据进行合并。
*/
public void meger()throws IOException
{
if(!dir.exists())
{
throw newRuntimeException("要合并的数据所在的目录不存在");
}
//获取指定目录中是否有配置信息文件。
File[] files =dir.listFiles(new ConfigFilter(".properties"));
if(files.length==0)
{
throw newRuntimeException("没有配置文件,无法进行合并");
}
//先读取配置文件信息。
FileInputStream fis = newFileInputStream(files[0]);
Properties prop = newProperties();
prop.load(fis);
String filename =prop.getProperty("filename");
int num =Integer.parseInt(prop.getProperty("partnum"));
File file = newFile(dir,filename);
File[] partFiles =dir.listFiles(new ConfigFilter(".partf"));
//Vector<FileInputStream>v = new Vector<FileInputStream>();
ArrayList<FileInputStream>v = new ArrayList<FileInputStream>();
for(int x=0; x<num-1; x++)
{
if(!partFiles[x].exists())
{
throw newRuntimeException("缺少一个碎片文件,名称为:"+partFiles[x].getName());
}
v.add(newFileInputStream(partFiles[x]));
}
finalIterator<FileInputStream> it = v.iterator();
//注意final,内部类定义在局部只能访问final修饰的局部变量
/*
因为arraylist无法直接获取枚举对象,所以自己通过匿名内部类定义了一个该接口的子类对象。
方法如何实现呢?因为枚举和迭代的功能是重复,所以,可以通过arraylist获取迭代,
枚举只要使用迭代即可完成。
*/
Enumeration<FileInputStream>en = new Enumeration<FileInputStream>()
{
public booleanhasMoreElements()
{
returnit.hasNext();
}
publicFileInputStream nextElement()
{
returnit.next();
}
};
//Enumeration<FileInputStream>en = v.elements();
SequenceInputStream sis = newSequenceInputStream(en);
FileOutputStream fos = new FileOutputStream(file);
byte[] buf = new byte[1024];
int len = 0;
while((len=sis.read(buf))!=-1)
{
fos.write(buf,0,len);
}
fos.close();
sis.close();
}
}
classConfigFilter implements FileFilter
{
private String suffix;
ConfigFilter(String suffix)
{
this.suffix = suffix;
}
public boolean accept(File pathName)
{
returnpathName.getName().endsWith(suffix);
}
}
classMegerFileDemo
{
public static void main(String[] args)throws IOException
{
File dir = new File("c:\\partfiles");
MegerFile mf = newMegerFile(dir);
mf.meger();
}
}
>>轻松一下,练习:猜数字小游戏
eg:注意其中异常的抛出以及判断
importjava.util.*;
importjava.io.*;
classGuessNumGame
{
public static void main(String[] args)
{
new Guess().play();
}
}
classGuess
{
private int num;
Guess()
{
Random r = new Random();
num = r.nextInt(100)+1;
}
public void play()
{
System.out.print("Thegame is starting!"+"\n"+"Please enter your number:");
boolean b = false;
try
{
while(!b)
{
int num =Input();
if(num>= 1 && num <= 100)
{
b= guess(num);
}
else
{
System.out.println("数字超出范围,请重新输入");
}
}
}
catch(NumberFormatExceptione)
{
System.out.println("输入数据非法,重新输入");
}
catch(IOException e)
{
throw newRuntimeException("游戏数据异常!");
}
}
//键盘输入方法
private int Input()throwsIOException,NumberFormatException
{
BufferedReader bufr = newBufferedReader(new InputStreamReader(System.in));
String line =bufr.readLine();
return Integer.parseInt(line);
}
//数字比较方法
private boolean guess(int playerNum)
{
if(playerNum < num)
{
System.out.print("小了,继续:");
}
else if(playerNum > num)
{
System.out.print("大了,继续:");
}
else
{
System.out.println("Congratulations!The num is"+num);
return true;
}
return false;
}
}
>>java.io
类 RandomAccessFile
java.lang.Object
java.io.RandomAccessFile
所有已实现的接口:
Closeable,DataInput, DataOutput
随机访问文件,自身具备读写方法,注意看,没有后缀(Reader,Stream等),不是四大体系的成员。
通过skipBytes(intx)和seek(int x)达到随机访问的目的
构造方法摘要 :
RandomAccessFile(Filefile, String mode)
创建从中读取和向其中写入(可选)的随机访问文件流,该文件由 File 参数指定。
RandomAccessFile(Stringname, String mode)
创建从中读取和向其中写入(可选)的随机访问文件流,该文件具有指定名称。
说明只能操作文件
其中的mode参数:
mode 参数指定用以打开文件的访问模式。允许的值及其含意为:
"r" 以只读方式打开。调用结果对象的任何 write 方法都将导致抛出 IOException。
"rw" 打开以便读取和写入。如果该文件尚不存在,则尝试创建该文件。
"rws" 打开以便读取和写入,对于 "rw",还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备。
"rwd" 打开以便读取和写入,对于"rw",还要求对文件内容的每个更新都同步写入到底层存储设备。
RandomAccessFile:
特点:
1,即可以读,又可以写。
2,内部封装了一个大的byte类型的数组,这就说明
该对象操作的数据是字节数据。
说名其中封装了字节的读取流和写入流。
而且可以使用内部的指针对这个数组进行数据的操作。
3,提供了getFilePointer方法获取指针的位置,
还提供了seek方法设置指针的位置。
4,通过该对象的构造函数可以得知,该对象只能操作文件。
也就说源和目的都是一个文件。
并通过构造函数的另一个参数来确定,访问方式。
该变量只能接收四个值。
r:只读,rw:读写。rws。rwd。
5,该对象中的方法可以操作基本数据类型。
6,注意被操作的文件数据,希望有规律。这样可以通过数据的整数倍来控制指针的偏移。
对数据进行操作,达到,随机访问的效果。
可以应用于多线程对大数据的写入。同时写入,只要给每一个线程分配
起始索引位,就可以完成多线程随机写入。
提高了写入效率。
使用的前提:
1,必须是文件。
2,数据有规律。比如等长数据。import java.io.*;
classRandomAccessFileDemo
{
public static void main(String[] args)throws IOException
{
writeDemo3();
//readDemo2();
}
//对已有数据进行修改。
public static void writeDemo3()throwsIOException
{
RandomAccessFile raf = newRandomAccessFile("info.txt","rw");
raf.seek(8*3);//从指针索引位8开始进行写入。
raf.write("赵六".getBytes());
raf.writeInt(72);
raf.close();
}
/*
既然能写,那么读也应该没有问题。
通过指针的操作,完成读取的随机效果。
*/
public static void readDemo2()throwsIOException
{
RandomAccessFile raf = newRandomAccessFile("info.txt","r");
raf.seek(8*1);
byte[] buf = new byte[4];
int len = raf.read(buf);
String s= newString(buf,0,len);
System.out.println("name="+s);
int age = raf.readInt();//一次读四个字节并转成int数值。
System.out.println("age="+age);
raf.close();
}
//通过seek方法指定指针的位置,进行数据写入。
/*
发现RandomAccessFile操作的文件如果已经存在,不会再次创建,直接操作已有文件。。
发现通过seek的指针定位,就可以完成数据的随机写入。
它可以完成已有数据的修改。
*/
public static void writeDemo2()throwsIOException
{
RandomAccessFile raf = newRandomAccessFile("info.txt","rw");
raf.seek(8*2);//从指针索引位8开始进行写入。
//raf.write("李四".getBytes());
//raf.writeInt(67);
raf.write("王武".getBytes());
raf.writeInt(68);
raf.close();
}
/*
通过该对象写点数据。
数据: 人员信息: 姓名,年龄
*/
public static void writeDemo()throwsIOException
{
RandomAccessFile raf = newRandomAccessFile("info.txt","rw");
raf.write("张三".getBytes());
//raf.writeBytes("张三");//解析出了问题。
//raf.writeChars("张三");//解析出了问题。
//raf.write(65);//write:只将一个int整数的最低字节写出。
raf.writeInt(65);
raf.close();
}
public static void readDemo()throwsIOException
{
RandomAccessFile raf = newRandomAccessFile("info.txt","r");
byte[] buf = new byte[4];
int len = raf.read(buf);
String s= newString(buf,0,len);
System.out.println("name="+s);
int age = raf.readInt();//一次读四个字节并转成int数值。
System.out.println("age="+age);
raf.close();
}
}
>>持久化对象,专门操作对象的流
ObjectInputStream
ObjectOutputStream
封装了直接操作对象的方法
然后,提一下一个没有方法的接口,一般这类接口称之为标记接口
java.io
接口Serializable
API解释:类通过实现java.io.Serializable 接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。可序列化类的所有子类型本身都是可序列化的。序列化接口没有方法或字段,仅用于标识可序列化的语义。
eg:演示将对象存入硬盘中并取出,注意,简化了异常抛出处理
importjava.io.*;
classObjectStreamDemo
{
public static void main(String[]args)throws Exception
{
Output();
System.out.println("Outputrun!");
Input();
}
public static void Input()throwsException
{
ObjectInputStream ois =
new ObjectInputStream(newFileInputStream("ObjectStreamDemoTxt.txt"));
Person p =(Person)ois.readObject();//会抛出ClassNotFoundException
System.out.println(p.getName()+":"+p.getAge());
ois.close();
}
public static void Output()throwsException
{
ObjectOutputStream ous =
new ObjectOutputStream(newFileOutputStream("ObjectStreamDemoTxt.txt"));
ous.writeObject(newPerson("cty",24));
ous.close();
}
}
//试验类,注意实现标记接口使其具备序列化资格
classPerson implements Serializable
{
private String name;
private int age;
Person(String name,int age)
{
this.name = name;
this.age = age;
}
public String getName()
{
return name;
}
public int getAge()
{
return age;
}
}
>>关于持久化的小知识点
上面的试验类Person
如果做一些修改:………………
privatestatic String name;//变静态
privateint age;
………………
这时打开存入硬盘的文件(乱码),会发现文件中被写入的信息变短了,如果做一次输出,发现结果为:null:24
证明,对象中的静态数据是不会被持久化的,因为其不再堆内存中。
如果非静态变量,也不想被持久化,怎么办?加上一个关键字:transient
再次做一些修改:………………
private Stringname;
privatetransient int age; //修饰不被持久化
………………
输出结果为:cty:0
>>关于标记接口Serializable:
用于给类文件加一个UID。就是一个序列号。该序列通过类中的成员的数字签名完成运算得来的。
当类中的成员发生大的改动时类会重新编译,生成带有新的UID的序列号。这样就和曾存储的原来的类生成的对象的序列号不匹配。这样就可以让使用者必须重新对新类产生的对象进行存储。避免新类接收老对象出现安全隐患,这就是序列号的功能所在。
如果是类中没有成员大的改动,只是只是有个别的修改和已存储的对象没有太大影响。
就需要重新进行存储。希望可以用新的类接收读到的老对象。
这时可以在定义类时指定序列化,而不让jvm自动算该序列化。
eg:………………
class Person implements Serializable
{
public static final longserialVersionUID = 42L;
//给person指定一个固定的序列号。
//这样就可以保证该类即使被改动,序列号不会变化。
public String name;
private int age;
Person(String name,int age)
{
………………
>>操作基本数据类型
Object也可以操作基本数据类型,但是主要用于操作对象的初始化。
如果只为了操作基本数据类型,那么有这两个类:
DataInputStream
DataOutputStream
它们也属于装饰流。
构造函数:
DataOutputStream(OutputStreamout)
创建一个新的数据输出流,将数据写入指定基础输出流。
DataInputStream(InputStreamin)
使用指定的底层 InputStream 创建一个DataInputStream。
特有方法:
publicfinal void writeUTF(String str)
throws IOException
以与机器无关方式使用UTF-8 修改版编码将一个字符串写入基础输出流。
首先,通过writeShort 方法将两个字节写入输出流,表示后跟的字节数。该值是实际写出的字节数,不是字符串的长度。根据此长度,使用字符的 UTF-8 修改版编码按顺序输出字符串的每个字符。如果没有抛出异常,则计数器 written 增加写入输出流的字节总数。该值至少是 2 加 str 的长度,最多是 2 加 str 的三倍长度。
publicfinal String readUTF()
throws IOException
参见 DataInput 的 readUTF 方法的常规协定。
从包含的输入流中读取此操作需要的字节。
这两个方法是对应的,也就是说,都使用UTF特定编码,使用其它方法读或写都无效。
eg:演示基本数据类型类和特有方法
classDataStreamDemo
{
public static void main(String[] args)throws IOException
{
readDemo();
}
public static void readDemo()throwsIOException
{
DataInputStream dis = newDataInputStream(new FileInputStream("data.txt"));
String s = dis.readUTF();
System.out.println(s);
}
public static void writeDemo()throwsIOException
{
DataOutputStream dos = newDataOutputStream(new FileOutputStream("data.txt"));
dos.writeUTF("你好");
dos.close();
}
}
>>操作数组的流对象
ByteArrayInputStream
ByteArrayOutputStream
对应的设备为内存,数组存在内存嘛。
public class ByteArrayInputStreamextendsInputStreamByteArrayInputStream 包含一个内部缓冲区,该缓冲区包含从流中读取的字节。内部计数器跟踪 read 方法要提供的下一个字节。关闭 ByteArrayInputStream 无效。此类中的方法在关闭此流后仍可被调用,而不会产生任何 IOException。
public class ByteArrayOutputStreamextends OutputStream此类实现了一个输出流,其中的数据被写入一个 byte 数组。缓冲区会随着数据的不断写入而自动增长。可使用toByteArray() 和 toString() 获取数据。关闭 ByteArrayOutputStream 无效。此类中的方法在关闭此流后仍可被调用,而不会产生任何 IOException。
两个类根本没有调用底层资源,所以close是无效的。
构造函数:
ByteArrayInputStream(byte[]buf)
创建一个ByteArrayInputStream,使用 buf 作为其缓冲区数组。
ByteArrayInputStream(byte[]buf, int offset, int length)
创建ByteArrayInputStream,使用 buf 作为其缓冲区数组。
ByteArrayOutputStream()
创建一个新的 byte 数组输出流。
ByteArrayOutputStream(intsize)
创建一个新的 byte 数组输出流,它具有指定大小的缓冲区容量(以字节为单位)。
eg:演示使用流来操作数组
importjava.io.*;
classByteArrayStreamDemo
{
public static void main(String[]args)throws IOException
{
//FileInputStream fis = newFileInputStream("ByteArrayStreamDemoTxt.txt");
ByteArrayInputStream bais =new ByteArrayInputStream("abcedff".getBytes());
ByteArrayOutputStream baos =new ByteArrayOutputStream();
int ch = 0;
while((ch = fis.read()) !=-1)
{
baos.write(ch);
}
System.out.println(baos.toString());
}
}
直接操作byte[]不就的了吗?干嘛还要使用流对象来完成呢?
因为数组的操作,无非就是数组中的元素进行设置和获取。这个操作正好符合了读和写的操作。使用流的操作思想在操作数组。
另外,还有字符数组:
CharArrayReader
CharArrayWriter
>>————————————阶段分割——————————
>>GB2312、GBK编码表,中文,占两个字节,最高位都是1。
Unicode码表,所有的文字都用两个字节表示
UTF-8,最多用三个字节表示一个字符
eg:转换流演示编码
importjava.io.*;
classBianMaDemo
{
public static void main(String[]args)throws IOException
{
outPut();
inPut();
}
public static void outPut()throwsIOException
{
OutputStreamWriterosw = new OutputStreamWriter(newFileOutputStream("BianMaDemogbk.txt"),"gbk");
osw.write("你好!");
osw.close();
}
public static void inPut()throwsIOException
{
InputStreamReader isr = newInputStreamReader(new FileInputStream("BianMaDemogbk.txt"),"gbk");
char[] buf = new char[1024];
int len = isr.read(buf);
String s = newString(buf,0,len);
System.out.println(s);
}
}
>>字符串的编码与解码
class EncodeDemo
{
public static void main(String[] args)throws Exception
{
String s = "你好";
byte[] buf =s.getBytes("GBK");//编码。
show(buf);
String s1 = newString(buf,"UTF-8");//解码。
System.out.println("s1="+s1);
//对s1这个乱码,在进行ISO8859-1编码,获取该字符串的源字节数据。
byte[] buf1 =s1.getBytes("UTF-8");
show(buf1);
String s2 = newString(buf1,"gbk");
System.out.println("s2="+s2);
}
public static void show(byte[] buf)
{
for(byte b : buf)
{
System.out.println(Integer.toHexString(b&255));
}
}
}
>>练习:
"abc你好ef"对该字符串进行字节个数的截取,
如果截取完出现半个中文,舍弃。
定义一个功能完成这个需求。
思路:
判断最后一个被截取的字节是不是负数。
如果是负数,继续往前判断,连续的负数的个数。
如果是偶数,说明没有半个中文。不用舍弃。
如果是奇数,说明有半个中文出现,舍弃最后一个字节。
注意:
在gb2312的码表中,中文是两个字节,都是负数。也就是每一个字节的最高为都是1.
在gbk码表中,中文还是两个字节,但是有的中文,第一个字节最高是1,而第二个字节最高有可能是0。比如“琲”的编码为-89,105
作业:
这个字符是UTF-8编码。
这时如果按字节截取,功能怎么实现呢?
class Test
{
public static void main(String[] args)throws Exception
{
String str = "abc琲琲e琲f";
byte[] arr =str.getBytes("gbk");
for(int x=0; x<arr.length;x++)
{
System.out.println("截取"+x+"个字节对应的字符串是:"+cutString(str,x));
}
/**/
//show();
}
public static void show()throwsException
{
String str = "琲";
byte[] arr =str.getBytes("gbk");
for(byte b : arr)
{
System.out.println(b);
}
}
public static String cutString(Stringstr,int len)throws Exception
{
byte[] buf =str.getBytes("GBK");
int count = 0;
for(int x=len-1; x>=0;x--)
{
if(buf[x]<0)
count++;
else
break;
}
if(count%2==0)
return newString(buf,0,len);
else
return newString(buf,0,len-1);
}
}
- 黑马程序员----8IO流
- 黑马程序员 IO流
- 黑马程序员IO流
- 黑马程序员-IO流
- 黑马程序员---IO流
- 黑马程序员-----IO流
- 黑马程序员--io流
- 黑马程序员:IO流
- 黑马程序员----IO流
- 黑马程序员--IO流
- 黑马程序员-IO流
- 黑马程序员:IO流
- 黑马程序员IO流
- 黑马程序员:IO流
- 黑马程序员-IO流
- 黑马程序员---IO流
- 黑马程序员---IO流
- 黑马程序员---IO流
- java 读取网络图片的大小(宽+高)
- 字符编码问题
- opengl基础知识
- linux-2.6.32在mini2440开发板上移植(9)之添加触摸屏驱动程序
- 字符串常见算法
- 黑马程序员----8IO流
- linux下删除目录及其子目录下某种类型文件
- nyoj 289 苹果(01背包一维和二维实现)
- 第五周-项目3-长方体类
- HDU 2099 整除的尾数
- sscanf用法
- hdu 1016 深搜
- VMware虚拟机配置Ubuntu桥接方式(Bridged)使虚拟机和宿主机能互相ping通
- h du1022 火车进站