黑马程序员——IO流学习总结

时间:2021-11-12 15:37:58
------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------

一、IO流概述

1、IO流用来处理设备之间的数据传输,Java对数据的操作是通过流的方式,Java用于操作流的对象都在IO包中。
流按操作数据分为两种:字节流与字符流;流按流向分为:输入流,输出流。
字节流的抽象基类:InputStream ,OutputStream;
字符流的抽象基类: Reader , Writer;

2、字符流对应的类:FileReader,FileWriter 字节流对应的类:FileInputStream ,FileOutputStream;
3、字符流与字节流的缓冲区:缓冲区的出现提高了对数据的读写效率,缓冲区要结合流才可以使用,在流的基础上对流的功能进行了增强。 字符流缓冲区对应类: BufferedWriter,BufferedReader 字节流缓冲区对应类: BufferedInputStream,BufferedOutputStream

4、LineNumberReader:跟踪行号的缓冲输入字符流:setLineNumber()与getLineNumber()
5、读取键盘录入以及转换流: System.in:对应的是标准输入设备,键盘 System.out:对应的是标准输出设备,控制台 InputStreamReader :字节到字符的桥梁。解码。
OutputStreamWriter:字符到字节的桥梁。编码。

6、流的操作规律:之所以要弄清楚这个规律,是因为流对象太多,开发时不知道用哪个对象合适。想要知道开发时用到哪些对象。只要通过四个明确即可:
1)明确源和目的,源:InputStream  Reader;目的:OutputStream  Writer。
2)明确数据是否是纯文本数据。源:是纯文本:Reader;不是纯文本:InputStream。目的:是纯文本 Writer;不是纯文本:OutputStream。到这里,就可以明确需求中具体要使用哪个体系。
3)明确具体的设备。源设备:硬盘:File;键盘:System.in;内存:数组;网络:Socket流。目的设备:硬盘:File;控制台:System.out;内存:数组;网络:Socket流。
4)是否需要其他额外功能。是否需要高效(缓冲区);是否需要转换。

7、File类:用于将文件和文件夹封装成对象。 1)创建。boolean createNewFile():如果该文件不存在,会创建,如果已存在,则不创建。不会像输出流一样会覆盖;boolean mkdir();boolean mkdirs()。
2)删除。boolean delete();void deleteOnExit():退出时删除。
3)获取。String getAbsolutePath();String getPath();String getParent();String getName();long length();long lastModified()。
4)判断。boolean exists();boolean isFile();boolean isDirectory()。

8、Properties类:Hashtable的子类。 特点:1)该集合中的键和值都是字符串类型。2)集合中的数据可以保存到流中,或者从流获取。通常该集合用于操作以键值对形式存在的配置文件。 
9、打印流:什么时候用?当需要保证数据表现的原样性时,就可以使用打印流的打印方法来完成,这样更为方便。保证原样性的原理:其实就是将数据变成字符串,在进行写入操作。 字节打印流:PrintStream。特点: 1)构造函数接收File对象,字符串路径,字节输出流。意味着打印目的可以有很多。 2)该对象具备特有的方法 打印方法 print println,可以打印任何类型的数据。 3)特有的print方法可以保持任意类型数据表现形式的原样性,将数据输出到目的地。 PrintWriter:字符打印流。特点: 1)当操作的数据是字符时,可以选择PrintWriter,比PrintStream要方便。 2)它的构造函数可以接收 File对象,字符串路径,字节输出流,字符输出流。 3)构造函数中,如果参数是输出流,那么可以通过指定另一个参数true完成自动刷新,该true对println方法有效。

10、其他IO流:

序列流:SequenceInputStream。特点:
1)将多个字节读取流和并成一个读取流,将多个源合并成一个源,操作起来方便;
2)需要的枚举接口可以通过Collections.enumeration(collection)。

操作对象的流:ObjectInputStream与ObjectOutputStream。并且被操作的对象需要实现Serializable (标记接口)。 操作基本数据类型的流:DataInputStream与DataOutputStream。
操作字节数组的流:ByteArrayInputStream与ByteArrayOutputStream。
操作字符数组的流:CharArrayReader与CharArrayWrite。
操作字符串:StringReader 与 StringWriter。


二、FileWriter(文件续写)与 FileReader文件读取(结束标志-1)

例一:需求:将一些文字存储到硬盘一个文件中。
public class FileWriterDemo {

private static final String LINE_SEPARATOR = System.getProperty("line.separator");

public static void main(String[] args) throws IOException {

// 创建一个可以往文件中写入字符数据的字符输出流对象。
/*
* 既然是往一个文件中写入文字数据,那么在创建对象时,就必须明确该文件(用于存储数据的目的地)。
* 如果文件不存在,则会自动创建,如果文件存在,则会被覆盖
* 如果构造函数中加入true,可以实现对文件进行续写!
*/
FileWriter fw = new FileWriter("demo.txt",true);
/*
* 调用Writer对象中的write(string)方法,写入数据。
* 其实数据写入到临时存储缓冲区中。
*/
fw.write("abcde"+LINE_SEPARATOR+"hahaha");
//fw.write("xixi");
// 进行刷新,将数据直接写到目的地中。
//fw.flush();

//关闭流,关闭资源。在关闭前会先调用flush刷新缓冲中的数据到目的地。
fw.close();
}
}
例二: 第一中读取方式:
public class FileReaderDemo {

public static void main(String[] args) throws IOException {
//1,创建读取字符数据的流对象。
/*
* 在创建读取流对象时,必须要明确被读取的文件。一定要确定该文件是存在的。
* 用一个读取流关联一个已存在文件。
*/
FileReader fr = new FileReader("demo.txt");

int ch = 0;
while((ch=fr.read())!=-1){
System.out.println((char)ch);
}
fr.close();
}
}
第二种读取方式,通过字符数组进行读取
public class FileReaderDemo2 {

public static void main(String[] args) throws IOException {

FileReader fr = new FileReader("demo.txt");
/*
* 使用read(char[])读取文本文件数据。
* 先创建字符数组。
*/
char[] buf = new char[1024];
int len = 0;
while((len=fr.read(buf))!=-1){
System.out.println(new String(buf,0,len));
}
fr.close();
}
}

三、IO异常处理方式

例三:
public class IOExceptionDemo {

private static final String LINE_SEPARATOR = System.getProperty("line.separator");

public static void main(String[] args) {

FileWriter fw = null;
try {
fw = new FileWriter("k:\\demo.txt");
fw.write("abcde" + LINE_SEPARATOR + "hahaha");
} catch (IOException e) {
System.out.println(e.toString());
} finally {
if (fw != null)
try {
fw.close();
} catch (IOException e) {
// code....
throw new RuntimeException("关闭失败");
}
}
}
}

四、IO流练习之文件拷贝(两种方式分别列举说明)

例四:
/*
* 需求:作业:将c盘的一个文本文件复制到d盘。:
* 1,需要读取源,
* 2,将读到的源数据写入到目的地。
* 3,既然是操作文本数据,使用字符流。
*/
public class CopyTextTest {

public static void main(String[] args) throws IOException {

//1,读取一个已有的文本文件,使用字符读取流和文件相关联。
FileReader fr = new FileReader("IO流_2.txt");
//2,创建一个目的,用于存储读到数据。
FileWriter fw = new FileWriter("copytext_1.txt");
//3,频繁的读写操作。
int ch = 0;
while((ch=fr.read())!=-1){
fw.write(ch);
}
//4,关闭流资源。
fw.close();
fr.close();
}
}

public class CopyTextTest_2 {

private static final int BUFFER_SIZE = 1024;

public static void main(String[] args) {

FileReader fr = null;
FileWriter fw = null;
try {
fr = new FileReader("IO流_2.txt");
fw = new FileWriter("copytest_2.txt");
//创建一个临时容器,用于缓存读取到的字符。
char[] buf = new char[BUFFER_SIZE];//这就是缓冲区。
//定义一个变量记录读取到的字符数,(其实就是往数组里装的字符个数 )
int len = 0;
while((len=fr.read(buf))!=-1){
fw.write(buf, 0, len);
}
} catch (Exception e) {
throw new RuntimeException("读写失败");
}finally{
if(fw!=null)
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
if(fr!=null)
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

五、缓冲区BufferedWriter与BufferedReader

例五:
public class BufferedWriterDemo {

private static final String LINE_SEPARATOR = System.getProperty("line.separator");

public static void main(String[] args) throws IOException {

FileWriter fw = new FileWriter("buf.txt");
//为了提高写入的效率。使用了字符流的缓冲区。
//创建了一个字符写入流的缓冲区对象,并和指定要被缓冲的流对象相关联
BufferedWriter bufw = new BufferedWriter(fw);

//使用缓冲区的写入方法将数据先写入到缓冲区中。
//bufw.write("abcdefq"+LINE_SEPARATOR+"hahahha");
//bufw.write("xixiixii");
//bufw.newLine();
//bufw.write("heheheheh");

for(int x=1; x<=4; x++){
bufw.write("abcdef"+x);
bufw.newLine();
bufw.flush();
}
//使用缓冲区的刷新方法将数据刷目的地中。
//bufw.flush();
//关闭缓冲区。其实关闭的就是被缓冲的流对象。
bufw.close();
}
}
BufferedReader终止符null
public class BufferedReaderDemo {

public static void main(String[] args) throws IOException {

FileReader fr = new FileReader("buf.txt");
BufferedReader bufr = new BufferedReader(fr);

String line = null;
while((line=bufr.readLine())!=null){
System.out.println(line);
}
bufr.close();
}
}
缓冲区练习:通过缓冲区复制文件
public class CopyTextByBufTest {

public static void main(String[] args) throws IOException {

FileReader fr = new FileReader("buf.txt");
BufferedReader bufr = new BufferedReader(fr);

FileWriter fw = new FileWriter("buf_copy.txt");
BufferedWriter bufw = new BufferedWriter(fw);

String line = null;
while((line=bufr.readLine())!=null){
bufw.write(line);
bufw.newLine();
bufw.flush();
}
bufw.close();
bufr.close();
}
}
自定义的读取缓冲区:其实就是模拟一个BufferedReader.
分析:缓冲区中无非就是封装了一个数组,并对外提供了更多的方法对数组进行访问。 其实这些方法最终操作的都是数组的角标。
缓冲的原理:其实就是从源中获取一批数据装进缓冲区中。在从缓冲区中不断的取出一个一个数据。在此次取完后,在从源中继续取一批数据进缓冲区。当源中的数据取光时,用-1作为结束标记。 

public class MyBufferedReader{

private Reader r;
//定义一个数组作为缓冲区。
MyBufferedReader(Reader r){
this.r = r;
}

public String myReadLine() throws IOException{

StringBuilder sb = new StringBuilder();
int ch = 0;
while((ch = r.read())!=-1){
if(ch=='\r')
continue;
if(ch=='\n')
return sb.toString();
//将从缓冲区中读到的字符,存储到缓存行数据的缓冲区中。
sb.append((char)ch);
}
if(sb.length()!=0)
return sb.toString();
return null;
}

public void myClose() throws IOException {
r.close();
}
}
public class MyBufferedReaderDemo {public static void main(String[] args) throws IOException {FileReader fr = new FileReader("buf.txt");MyBufferedReader bufr = new MyBufferedReader(fr);String line = null;while((line=bufr.myReadLine())!=null){System.out.println(line);}}}

六、FileInputStream与FileOutputStream

例六:
public class ByteStreamDemo {

public static void main(String[] args) throws IOException {
//demo_write();
demo_read();
}

public static void demo_read() throws IOException {

//1,创建一个读取流对象。和指定文件关联。
FileInputStream fis = new FileInputStream("bytedemo.txt");

//System.out.println(fis.available()); //返回字节个数
//byte[] buf = new byte[fis.available()];
//fis.read(buf);
//System.out.println(new String(buf));

//建议使用这种读取数据的方式
byte[] buf = new byte[1024];
int len = 0;

while((len=fis.read(buf))!=-1){
System.out.println(new String(buf,0,len));
}
fis.close();
}

public static void demo_write() throws IOException {

//1,创建字节输出流对象。用于操作文件.
FileOutputStream fos = new FileOutputStream("bytedemo.txt");
//2,写数据。直接写入到了目的地中。
fos.write("abcdefg".getBytes());
//fos.flush();
fos.close();//关闭资源动作要完成。
}
}

七、字节流缓冲区BufferedInputStream 与 BufferedOutputStream(以复制MP3文件为例)

例七:MP3复制的四种方式
public class CopyMp3Test {

public static void main(String[] args) throws IOException {
//copy_1();
//copy_2();
//copy_3();
copy_4();
}
//千万不要用,效率没有!
public static void copy_4() throws IOException {
FileInputStream fis = new FileInputStream("c:\\0.mp3");
FileOutputStream fos = new FileOutputStream("c:\\4.mp3");

int ch = 0;
while((ch =fis.read())!=-1){
fos.write(ch);
}
fos.close();
fis.close();
}

//不建议。
public static void copy_3() throws IOException {
FileInputStream fis = new FileInputStream("c:\\0.mp3");
FileOutputStream fos = new FileOutputStream("c:\\3.mp3");

byte[] buf = new byte[fis.available()];
fis.read(buf);
fos.write(buf);
fos.close();
fis.close();
}

public static void copy_2() throws IOException {
FileInputStream fis = new FileInputStream("c:\\0.mp3");
BufferedInputStream bufis = new BufferedInputStream(fis);

FileOutputStream fos = new FileOutputStream("c:\\2.mp3");
BufferedOutputStream bufos = new BufferedOutputStream(fos);

int ch = 0;
while((ch=bufis.read())!=-1){
bufos.write(ch);
}
bufos.close();
bufis.close();
}

public static void copy_1() throws IOException {
FileInputStream fis = new FileInputStream("c:\\0.mp3");
FileOutputStream fos = new FileOutputStream("c:\\1.mp3");

byte[] buf = new byte[1024];
int len = 0;
while((len=fis.read(buf))!=-1){
fos.write(buf,0,len);
}
fos.close();
fis.close();
}
}
当然我们也可以尝试自定义一个字节流缓冲区。
特别需要注意的是:int与byte两种基本类型的区别,byte类型提升为int类型,只需&255
public class MyBufferedInputStream {
private InputStream in;
private byte[] buf= new byte[1024];
private int pos=0,count=0;

public MyBufferedInputStream(InputStream in){
this.in = in;
}

public int myRead() throws IOException {
if(count == 0){
count = in.read(buf);
if(count < 0)
return -1;
pos = 0;
byte b = buf[pos];
count--;
pos++;
return b&255;
}else if(count > 0){
byte b = buf[pos];
count--;
pos++;
return b&255;
}
return -1;
}

public void myClose() throws IOException{
in.close();
}

public static void main(String[] args){
MyBufferedInputStream bufis = null;
BufferedOutputStream bufos = null;

try {
bufis = new MyBufferedInputStream(new FileInputStream("1.mp3"));
bufos = new BufferedOutputStream(new FileOutputStream("2.mp3"));

int by = 0;
while( (by = bufis.myRead()) != -1){
bufos.write(by);
}

} catch (IOException e) {
throw new RuntimeException("复制文件失败");
}finally{
try {
if(bufis != null)
bufis.myClose();
} catch (IOException e) {
throw new RuntimeException("读取关闭失败");
}
try {
if(bufos != null)
bufos.close();
} catch (IOException e) {
throw new RuntimeException("写入关闭失败");
}
}
}
}

八、读取键盘录入以及转换流

例八:读取键盘录入:
/*
* 读取一个键盘录入的数据,并打印在控制台上。
*
* 键盘本身就是一个标准的输入设备。
* 对于java而言,对于这种输入设备都有对应的对象。
*/
public class ReadKey {

public static void main(String[] args) throws IOException {

//readKey();
//System.out.println((int)'\r');
//System.out.println((int)'\n');
readKey2();
}

public static void readKey2() throws IOException {
/*
* 获取用户键盘录入的数据,
* 并将数据变成大写显示在控制台上,
* 如果用户输入的是over,结束键盘录入。
* 思路:
* 1,因为键盘录入只读取一个字节,要判断是否是over,需要将读取到的字节拼成字符串。
* 2,那就需要一个容器。StringBuilder.
* 3,在用户回车之前将录入的数据变成字符串判断即可。
*/
//1,创建容器。
StringBuilder sb = new StringBuilder();
//2,获取键盘读取流。
InputStream in = System.in;
//3,定义变量记录读取到的字节,并循环获取。
int ch = 0;
while((ch=in.read())!=-1){
//在存储之前需要判断是否是换行标记 ,因为换行标记不存储。
if(ch=='\r')
continue;
if(ch=='\n'){
String temp = sb.toString();
if("over".equals(temp))
break;
System.out.println(temp.toUpperCase());
sb.delete(0, sb.length());
}
else
//将读取到的字节存储到StringBuilder中。
sb.append((char)ch);
//System.out.println(ch);
}
}

public static void readKey() throws IOException {
InputStream in = System.in;
int ch = in.read();//阻塞式方法。
System.out.println(ch);
int ch1 = in.read();//阻塞式方法。
System.out.println(ch1);
int ch2 = in.read();//阻塞式方法。
System.out.println(ch2);
//in.close();
//InputStream in2 = System.in;
//int ch3 = in2.read();
}
}
例九:转换流实例(提高效率,用到缓冲区)
public class TransStreamDemo {

public static void main(String[] args) throws IOException {

//字节流。
InputStream in = System.in;
//将字节转成字符的桥梁。装换流。
InputStreamReader isr = new InputStreamReader(in);
//字符流,缓冲区以提高效率。
BufferedReader bufr = new BufferedReader(isr);

OutputStream out = System.out;
OutputStreamWriter osw = new OutputStreamWriter(out);
BufferedWriter bufw = new BufferedWriter(osw);

String line = null;
while((line=bufr.readLine())!=null){
if("over".equals(line))
break;
bufw.write(line.toUpperCase());
bufw.newLine();
bufw.flush();
}
}
}

九、File类实例:

例十:获取指定目录下当前的所有文件夹或者文件对象,并将其写入一个文本文件中
public class FileList {

public static void main(String[] args) throws IOException{
File dir = new File("E:\\教学视频");
List<File> list = new ArrayList<File>();
filetolist(dir,list);
//System.out.println(list.size());

File filename = new File(dir,"filelist.txt");
writetofile(list ,filename.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(".rar"))
list.add(file);
}
}

public static void writetofile(List<File> list , String filename) throws IOException{
BufferedWriter bufw = null;
try {
bufw = new BufferedWriter(new FileWriter(filename));
for(File file : list){
String path = file.getAbsolutePath();
bufw.write(path);
bufw.newLine();
bufw.flush();
}
} catch (IOException e) {
throw e;
} finally{
try {
if(bufw != null)
bufw.close();
} catch (IOException e) {
throw e;
}
}
}
}
例十一:删除一个带内容的目录,Java删除不走回收站,测试时注意文件备份
/*
* 删除一个带内容的目录。
* 原理:必须从最里面往外删。
* 需要进行深度遍历。递归
*/
public class RemoveDirTest {

public static void main(String[] args) {
File dir = new File("e:\\demodir");
//dir.delete();
removeDir(dir);
}

public static void removeDir(File dir) {

File[] files = dir.listFiles();
for(File file : files){
if(file.isDirectory()){
removeDir(file);
}else{
System.out.println(file+":"+file.delete());
}
}
System.out.println(dir+":"+dir.delete());
}
}

十、Properties类实例

例十二:定义功能,获取一个应用程序运行的次数,如果超过5次,给出使用次数已到请注册的提示。并不要再运行程序。
思路:
1,应该有计数器。 每次程序启动都需要计数一次,并且是在原有的次数上进行计数。
2,计数器就是一个变量。 突然冒出一想法,程序启动时候进行计数,计数器必须存在于内存并进行运算。可是程序一结束,计数器消失了。那么再次启动该程序,计数器又重新被初始化了。而我们需要多次启动同一个应用程序,使用的是同一个计数器。这就需要计数器的生命周期变长,从内存存储到硬盘文件中。
3,如何使用这个计数器呢?首先,程序启动时,应该先读取这个用于记录计数器信息的配置文件。获取上一次计数器次数。 并进行试用次数的判断。其次,对该次数进行自增,并自增后的次数重新存储到配置文件中。 
4,文件中的信息该如何进行存储并体现。直接存储次数值可以,但是不明确该数据的含义。 所以起名字就变得很重要。这就有了名字和值的对应,所以可以使用键值对。可是映射关系map集合搞定,又需要读取硬盘上的数据,所以map+io = Properties.

public class PropertiesTest {

public static void main(String[] args) throws IOException {
getAppCount();
}

public static void getAppCount() throws IOException{

//将配置文件封装成File对象。
File confile = new File("count.properties");
if(!confile.exists()){
confile.createNewFile();
}
FileInputStream fis = new FileInputStream(confile);
Properties prop = new Properties();
prop.load(fis);
//从集合中通过键获取次数。
String value = prop.getProperty("time");
//定义计数器。记录获取到的次数。
int count =0;
if(value!=null){
count = Integer.parseInt(value);
if(count>=5){
//System.out.println("使用次数已到,请注册,给钱!");
//return;
throw new RuntimeException("使用次数已到,请注册,给钱!");
}
}
count++;
//将改变后的次数重新存储到集合中。
prop.setProperty("time", count+"");
FileOutputStream fos = new FileOutputStream(confile);
prop.store(fos, "");
fos.close();
fis.close();
}
}