『黑马程序员』第六话

时间:2023-02-16 13:07:34

『黑马程序员』第六话<IO流>

18.6  IO流就是(Input Output)流
 IO流用来处理设备之间的数据传输
 Java对数据的操作是通过流的方式
 Java用于操作流的对象都在IO包中
 流按操作分为两种:字节流与字符流
 流按流向分为:输入流,输出流
四个基类
 字节流的抽象基数:InputStrean, OutStream.
 字符流的抽象基类:Reader,Write.
 注:由这四个类派生出来的子类名称都是以其父类名作为子类名的后缀
     InputStream的子类 FileInputStream
Reader子类FileReader


字符流:
数据的最常见体现形式就是:文件。
FileWriter:
FileWriter fw = new FileWriter();       throws IOException要抛出异常
创建一个FileWriter对象,该对象一被初始化就必须要明确被操作的文件
而且该文件会创建到指定目录下,如有同名文件会被覆盖,
Fw.writer(“aaa”);
Fw.flush();//将数据刷到目的地去。
Fw.close();先刷新一次内部缓冲中的数据到目的地中再关闭流,之后就不能再使用


18.8
IO流异常处理

FileWriter fw=null;//先创建,后面再复写,不然下面的fw.close中的fw不存在
try
{  
FileWriter fw=new FileWriter(“c:\\demo.txt”); //建一个文件,记得路径要写两个\\,一个是转义字符
        fw.write("aaddffg");  //写入字符流
}
catch(IOException e) 
{
System.out.println(e.toString());
}
finally
{
try
{
if(fw!=null) //一定要写这个
fw.colse();
}
catch(IOException e)
{
System.out.println(e.toString());
}


18.9文件续写
FileWriter fw=new FileWriter("demo.txt",true)//如果目录下没有demo.txt那就创建一个,如有就从末尾续写
     //    \r\n换行
----------------------------------------------------
18.10 文件读取
方法一(单个字符取出)
FileReader fw =new FileReader("demo.txt");//创建一个文件读取流对象,和指定名称的文件相关联。
//要保证文件存在,如果文件不存在会发生异常FileNotFoundException
int ch1=fw.read();//读第一个字符
int ch2=fw.read();//自动读第二个字符    返回整数,到达结束时返回-1

/*while(ture)
{
int ch=fw.read();
if(ch!=-1)
sop((char)ch);
}*/
while((ch=fr.read())!=-1)
{
sop();
}

------------------------------------------------------------------------------
方法二(字符数组取出)
FileReader fr=new FileReader("demo.txt")
 char[] buf=new char[1024]; 
int num=0;
while((num=fr.read(buf))!=-1)
{
System.out.println(new String(buf,0,num))//String(数组,始,终)
}
fr.close;

---------------------------------------------------------------------------------
18.13
复制文件,从d盘一个文本文件复制到d盘。
思路:1.在d盘创建一个文本文件,用于存储
     2.定义读取流和c盘文件关联
 3.通过不断的读写完成数据存储
 4.关闭资源。


方法一: public static void copy1()throw IOException
{
FileWriter fw=new FileWriter(RuntimeDemo_copy.txt);
   FileReader fr=new FileReader("RuntimeDemo.java");
int ch=0;
while((ch=fr.read())!=-1)
{
fw.write(ch);    
}
}

方法二:(加异常处理)
public static void copy2()
{
FileWriter fw=null;
FileReader fr=null;
try
{
fw=new FileWriter("xx.txt");
fr=new FileReader("xx.java");
char[] buf =new char[1024];
int len=0;
while((len=fr.read(buf))!=-1)
{
fw.write(buf,0,len);
}
}
catch (Exception e)
{
throw new RuntimeException("读写失败");
}
finally
{
if(fr!=null)
try
{
fw.close();
}
catch (IOException e)
{
}

if(fw!=null)
try
{
fw.close()
}
catch (IOException e)
{
}
}
}

BufferWriter
写:缓冲区的出现是为了提高流的操作效率而出现的,
所以在创建缓冲区之前,必须要先有流对象。
该缓冲区中提供了一个跨平台的换行符,newLine(),只有缓冲区有
FileWriter fw = new FileWrter("buf.txt");//必须先有流对象,才能建缓冲区
BufferedWriter bufw =new BufferedWriter(fw);
...bufw.write...//写入要写的数据
bufw.flush();//记得要刷新,不然停电就没了
bufw.flush();

读:缓冲区提供了一个一次读一行的方法,返回String,如到末尾返回null    readLine(),只返回回车符之前数据,不包括回车符
19.04 readLine原理
无论是读一行,获取读取多个字符。其实最终都是在硬盘上一个一个读取,所以最终使用的还是rdad方法一次读一个的方法
图。
19.05 自己做一个BufferedReader
class MyBuf
{
private FileReader r;
MyBuf(FileReader r)  //构造函数,,初始化r
{
this.r=r;
}
public String myRl() throws IOException
{
StringBuilder sb=new StringBuilder();  //用StringBuilder 来做缓冲容器
int ch=0; 
while((ch=r.read())!=-1)//返回值不为-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 myClose() throws IOException
{
r.close();
}
}


public class MybufferedReader {



public static void main(String[] args) throws IOException
{
FileReader fr =new FileReader("F:\\aa.txt");
MyBuf mybuf=new MyBuf(fr);
String line=null;
while((line=mybuf.myRl())!=null)
{
System.out.println(line);
}
mybuf.myClose();
}}


19.6装饰设计模式

当想要对已有的对象进行功能增强时,可以定义类,将已有对象传入,基于已有的功能,并提供功能
那么自定义的该类称为装饰类。

装饰模式与继承的区别
1.装饰模式比继承要灵活,避免了继承体系臃肿。而且降低了类与类之间的关系
2.装饰类因为增强已有对象,具备的功能和已有的是相同的,只不过提供了更强功能,所以装饰类和被装饰类通常是都属于一个体系中的
19.8自定义装饰类
先继承,然后要覆盖抽象方法

19.9带行号的装饰类  LineNumberReader
.setLineNumber(int i)
.getLineNumber()
19.10 //练习:模拟一个带行号的缓冲区对象

19.11 字节流
FileOutputStream  输入
不用刷新,因为是最小单位,
FileInputStream 输出
    ***** 理解输出的三种方法。。。。
19.12
复制图片
思路:1.字节流
 2.创建图片文件

19.14
自定义字节流缓冲区
1.先清楚字节流缓冲区的工作过程
2.为什么没复制成功?
3.当读到11111111时提升到int时返回-1
---------------------------------------------
19.15 读取键盘录入
System.in 设备:键盘

InputStream in=System.in;
int by=in.read();
System.out.println(by)//问题出现:回车后\r \n也被打印


19.16读取转换流
键盘录入中要是用readLine()方法很方便,
所以要把字节流对象转成字符流对象,使用转换流InputStreamReader
InputStream in =System.in;
InputStream isr=new InputStreamReader(in)
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();
}

----------------------------------------------------
19.17写入转换流(字节转字符)
OutputStreamWriter


OutputStream out =;
OutputStreamWriter osw =new OutputStreamWriter(out)
BufferedWriter bufw=new BufferedWriter(osw)
//BufferedWriter bufw=BufferedWriter(new OutputStreamWriter(System.out ) )
//键盘输入最常见写法  


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

---------------------------------------------------------------
 ***重点*** 19.18 流操作规律
  源:键盘录入
  目的:控制台
  需求:想把键盘录入的数据存储到一个文件中;(源:键盘,目的:文件)
  BufferedReader bufr=new BufferedReader(new InputStreamReader(System.in));//键盘输入
  BufferedWriter bufw=BufferedWriter(new OutputStreamWriter(new FileOutputStream("out.txt") ) );//目的:文件
   需求:想要将一个文件的数据打印在控制台上。(源:文件。目的:控制台)
BufferedReader bufr=new BufferedReader(new InputStreamReader(new FileInputStream("xx.java")));//源:文件
  BufferedWriter bufw=BufferedWriter(new OutputStreamWriter(System.out) ) );//目的:控制台

 
 流操作的基本规律:
 最痛苦的是流对象太多,不知道要用哪一个。
 通过三个明确来完成:
 1.明确源和目的
 源:输入流。InputStream Reader
 目的:输出流  OutputStream Writer
 2.操作的数据是否是纯文本。
是:字符流
不是:字节流
 .当体系明确后,再明确要使用哪个具体的对象。
通过设备来进行区分:
源设备:内存,硬盘,键盘
目的:内存,硬盘,控制台
                    
  例子:
将一个文本文件中数据存储到另一个文件中,复制文件
源:InputStream Reader
是不是操作文本,是:Reader
明确要使用体系中的对象是硬盘上的一个文件,所以是FileReader
FileReader fr =new FileReader("a.txt");
BufferedReader br=new BufferedReader(fr);


目的:OutputStream Writer
是否为文本:是:所以用Writer
设备:一个文件。FileWriter.
FileWriter fw =new FileWriter("b.txt");
BufferedWriter bw=new BufferedWriter(fw);

19.19 需求:将键盘录入数据保存到一个文件中
源:InputStream Reader
文本?是 Reader
设备:键盘,对应对象是System.in,
System.in是字节流,字符流操作最方便,所以转换成Reader
用了Reader体系中的转换流,InputStreamReader(System.in)
BufferedReader bufr=new BufferedReader(new InputStreamReader(System.in));
目的:OutputStream Writer
是否为文本?是Writer
设备:硬盘,一个文件,使用FileWriter
需要提高 效率吗,要
------------------------------------------------------
扩展一下,想要把录入的数据按照指定编码表gbk(utf-8),将数据存入文件
目的:OutputStream Writer
是否为文本?是Writer
设备:硬盘,一个文件,使用FileWriter(GBK)
但是存储时,需要加入指定编码表,而指定的编码表只有转换流可以指定,
所以要使用的对象是OutputStreamWriter
而该转换流对象要接收一个字节输出流,而且还可以操作的文件的字节输出流,FileOutputStream


OutputStreamWriter osw=new OutputStreamWriter(new FileOutputStream("d.txt"),"UTF-8");
需要高效吗?要,BufferedWriter 


什么时候用转换流呢?
字符和字节之间的桥梁,通常,涉及到字符编码转换时,需要用到转换流
默认码表是GBK,如果是utf-8编码的txt,用fileReader不能读

19.20 改变标准输入输出设备
System
System.setIn( 标准输入,比如文件 )
System.setOut(  )
-------------------------------
19.21
需求:把异常信息存入文件
catch(Exception e)
{
System.setOut(new PrintStream("exeception.log"));
e.printStackTrace(System.out)
}

-------------------------------------------------
19.22,系统信息
------------------------------------
20.1File类
用来将文件或者文件夹封装成对象
方便对文件与文件夹的属性信息进行操作
File对象可以作为参数传递给流的构造函数
了解File类的常见方法。


创建对象
File f=new file("c:\\a\\a.txt");//将a.txt封装成file对象,。
File f1=new file("c:\\abc","b.txt")
File f2=new file(d,"c.txt");


sop(f)打印出来是目录
跨平台的目录分割符 File.separator.
-----------------------------------------
20.2 File类常见方法,创建和删除
创建:
createNewFile() ----->boolean在指定位置创建文件,如果文件存在则不创建 ,而输出流会覆盖
有后缀是文件,没后缀是文件夹,但只能创建一级文件夹
//    createTempFile()---临时文件
mkdirs() -------------> boolean        创建文件夹


删除:
boolean delete();删除,成功或失败会返回数据
deleteOnExit();退出时删除,

-------------------------------------------
20.3 判断
.   canExecute()----------->boolean 判断是否能执行
**  exists()----------------->boolean 文件是否存在
isDirectory()是否是文件夹-----//必须先判断该文件是否存在
isFile() 是否是文件------//必须先判断该文件是否存在
isHidden()---------------是否是隐藏
isAbsolute()            是否是绝对
20.4 获取
getName()
getParent()
getPath()   --------封装时用什么路径,就读什么路径
getAbsolutePath()-----绝对路径,显示全部路径
lastModified()最后修改时间
length
            //File f=new File("file.txt")
sop(f.getPath());----------->file.txt
sop(f.getAbsolutePath());---->D:\\javaday\\file.txt
sop(f.getParent());-------->该方法返回的是绝对路径中的父目录,不同创建会返回不同结果
f1.renameTo(f2)

-----------------------------------
20.5文件列表
File[] files=File.listRoots(); 返回的是盘符数组C:\  D:\
String[] name=f.list();列出f目录下所有文件,该目录必须存在
20.6
1.文件名过滤
2.list()与listFiles()的区别

20.7
需求:列出目录下所有内容
(递归)是一个循环
用递归打印出6的toBin()
   递归要求:
1.限定条件,不给死循环
2.注意递归次,避免内存溢出
20.8

需求:列出目录下所有内容,带层次
20.9
需求:删除一个带内容的目录。
删除原理:在windows中,删除目录从里面往外删除:用递归
File[] files=dir.listFiles()
for(int x=0;x<files.length;x++)
{
if(files[x].isDirectory())
removeDir(files[x]);
sop(files[x].toString()+” “+files[x].delete());//这里虽然是打印语句,但有执行删除动作
}
   sop(dir+""+dir.delete);
-
 20.10

 需求:将一个指定目录下的java文件的绝对路径,存储到一个文本文件中,建立一个java文件列表文件
  
  思路:1.对指定的目录进行递归
2.获取递归过程所得的java文件的路径
3.将这些路径存储到集合中
4.将集合中的数据写入到一个文件中
--------------------------------------------------
20.11
Properties 是hashtable的子类。
也就是说它具备map集合的特点,键---值
是集合中的IO技术相结合的集合容器
该对象的特点:可以用于键值对形式的配置文件
---------------------------
20.12
存 setProperty("zhangsan","20")
 
 取:stringPropertyNames();--------->Set集合




Properties pro =new Properties();
prop.setProperty("zs","20");
prop.setProperty("ls","33")//可覆盖修改数据


String value =prop.getProperty("zs");
Set<String>names=prop.stringPropertyName();-------------取出集合
for(String s:names)
{
sop(s+":"+prop.getProperty(s));
}

--------------------------------
20.13存取配置文件
需求,想要点一个info.txt中键值数据存到集合中进行操作
思路:1.用一个流和info.txt文件关联
 2.读取一行数据,将该行数据用=进行切割
 3.等号左边作键,右边为值,存入到properties集合中
---------------------------------------------
20.14 (1.6版本有新方法)
需求:用于记录应用程序运行次数
如果使用次数已到,那么给出注册提示


思路:计数器,但是程序一关闭,下次再启动时就重新计数了
所以要建立一个配置文件,用于记录该软件的使用次数
该配置文件使用键值对的形式,
这样便于阅读数据,并操作数据
键值对数据是map集合
数据是以文件形式存储。使用io技术
那么map.io-->proterties
配置文件可以实现应用程序数据的共享

20.15打印流
该流提供了打印方法,可以将各类型数据都原样打印
PrintStream 字节打印流
构造函数可以接收的参数类型
1.file对象
2.字符串路径:String
3.字符输出流:OutputStream


字符打印流:
PrintWriter
1.file对象
2.字符串路径:String
3.字符输出流:OutputStream
4.字符输出流 Writer
--------------------------------------------
20.16合并流 SequenceInputStream
多个源要操作一个数据,可先变成一个源
Vector<FileInputStream>v=new Vector<FileInputStream>()'
v.add(new FileInputStream("c:\\1.txt"));
v.add(new FileInputStream("c:\\2.txt"));
v.add(new FileInputStream("c:\\3.txt"));
Enumeration<FileInputStream> en = v.elements();
SequenceInputStream sis =new SequenceInputStream();
FilleOutputStream fos =new FileOutput Stream("C:\\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();

----------------------------------------------
20.17切割
FileInputStream fis =new FileInputStream("c:\\1.bpm");
fileOutputStream fos =null;
byte[] buf=new byte[1024*1024]
int len =0;
int count =1;
while((len=fis.read(buf))!=-1)
{
fos=new FileOutputStream("c:\\splitfiles\\"+(count++)+".part")
fos.write(buf,0,len);
fos.close();
}
fis.close();

1、IO流——对象的序列化

操作对象:ObjectInputStream与ObjectOutputStream,被操作的对象需要实现Serializable(标记接口);

import java.io.*;
class Person implements Serializable
{
       //自定义UID值,保证UID值的唯一性
       public static final long serialVersionUID = 42L;
       String name;
       transient int age;//transient可以让age无法序列化,保证其值在堆内存中存在而不再文本文件中存在
       static String country="cn";
       Person(String name,int age,String country)
       {
              this.name = name;
              this.age = age;
              this.country = country;
       }
       public String toString()
       {
              return name+":"+age+"::"+country;
       }
}

2、IO流——管道流

管道流分PipedInputStream和PipedOutputStream

输入输出可以直接进行连接,通过结合线程使用。

管道输入流应该连接到管道输出流;管道输入流提供要写入管道输出流的所有数据字节。通常,数据由某个线程从 PipedInputStream 对象读取,并由其他线程将其写入到相应的 PipedOutputStream。不建议对这两个对象尝试使用单个线程,因为这样可能死锁线程。管道输入流包含一个缓冲区,可在缓冲区限定的范围内将读操作和写操作分离开。如果向连接管道输出流提供数据字节的线程不再存在,则认为该管道已损坏。

集合当中涉及到IO流的是Properties,IO当中涉及到多线程的就是管道流

import java.io.*;
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("读取前。。没有数据,阻塞");
                     intlen = in.read(buf);
                     System.out.println("读到数据。。阻塞结束");
                     Strings = new String(buf,0,len);
 
                     System.out.println(s);
                     in.close();
              }
              catch (IOException e)
              {
                     throw new RuntimeException("管道读取流失败");
              }
       }
}
class Write implements Runnable
{
       private PipedOutputStream out;
       Write(PipedOutputStream out)
       {
              this.out = out;
       }
       public void run()
       {
              try
              {
                     System.out.println("开始写入数据,等待6秒后...");
                     Thread.sleep(6000);
                     out.write("pipedlai le".getBytes());//转换成字节流
                     out.close();
              }
              catch (Exception e)
              {
                     throw new RuntimeException("管道输出流失败");
              }
       }
}
class PipedStreamDemo
{
       public static void main(String[] args) throws IOException
       {
              PipedInputStream in = new PipedInputStream();
              PipedOutputStream out = new PipedOutputStream();
              //将管道流连接起来
              in.connect(out);
 
              Read r = new Read(in);
              Write w = new Write(out);
 
              new Thread(r).start();
              new Thread(w).start();
 
       }
}

3、IO流——RandomAccessFile

RadomAccessFile:

1.随机访问文件,自身具备读写的方法。

2.通过skipBytes(int x),seek(int x)来达到随机访问。

操作基本数据类型:DataInputStream与DataOutputStream

操作字节数组:ByteArrayInputStream与ByteArrayOutputStream

操作字符数组:CharArrayReader与CharArrayWrite

操作字符串:StringReader与StringWriter

RandomAccessFile该类不算是IO体系中的子类,而是直接集成自Object。但是他是IO包中成员,因为它具备读和写功能。内部封装了一个数组,而且通过指针对数组的元素进行操作。可以通过getFilePointer获取指针位置,同时可以通过seek改变指针的位置。其实完成读写的原理就是内部封装了字节输入流和输出流。通过构造函数可以看出,该类只能操作文件。而且操作文件还有模式:只读r,读写rw等。如果模式为只读r,不会创建文件,然后去读取已存在的文件;如果该文件不存在,则会出现异常。如果模式为rw,操作的文件不存在,会自动创建,如果存在则也不会被覆盖。

public class RandomAccessFile extendsObjece implements DataOutput,DataInput,Closeable   此类的实例支持对随机访问文件的读取和写入

4、操作基本数据类型的流对象DataStream

操作基本数据类型:DataInputStream与DataOutputStream

操作字节数组:ByteArrayInputStream与ByteArrayOutputStream

操作字符数组:CharArrayReader与CharArrayWriter

操作字符串数组:StringReader与StringWriter

DataInputStream与DataOutputStream可以用于操作基本数据类型的数据的流对象。

5、用于操作字节数组的流对象ByteArrayStream

ByteArrayInputStream:在构造的时候,需要接收数据源,而且数据源是一个字节数组。

ByteArrayOutputStream:在构造的时候,不用定义数据目的,因为该对象中已经内部封装了可变长度的字节数组。

public class ByteArrayInputStream extendsInputStream     ByteArrayInputStream 包含一个内部缓冲区,该缓冲区包含从流中读取的字节。内部计数器跟踪read方法要提供的下一个字节。关闭ByteArrayInputStreamReader无效。此类中的方法在关闭此流后仍可被调用而不会产生任何IOException。

因为这两个流都操作的是数组,并没有使用系统资源,所以,不用进行close关闭。

在流操作规律讲解时:

源设备:

       键盘:System.in;   硬盘:FileStream;      内存:ArrayStream

目的设备:

       控制台:System.out;    硬盘:FileStream;      内存:ArrayStream

用流的读写思想 来操作数据

6、转换流的字符编码

字符编码

1.字符流的出现为了方便操作字符。

2.更重要的是加入了编码符集。

3.通过子类转换来完成。InputStreamReader和OutputStreamWriter

4.在两个对象进行构造的时候可以加入字符集。

 

编码表的由来:

1.计算机只能识别二进制数据,早起由来是电信号;

2.为了方便应用计算机,让它可以识别各个国家的文字;

3.就将各个国家的文字用数字来表示,并一一对应,形成一张表,这就是编码表。

常见的编码表

ASCII:美国标准信息交换码,用一个字节的7位可以表示。

ISO8859-1:拉丁码表,欧洲码表,用一个字节的8位表示。

GBK2312:中国的中文编码表。

GBK:中国的中文编码表升级,融合了更多的中文文字符号。

Unicode:国际标准码,融合了多种文字。所有文字都用两个字节来表示,Java语言使用的就是unicode

UTF-8:最多用三个字节来表示一个字符。

7、字符编码

编码:字符串变成字节数组

解码:字节数组变成字符串

String     -->byte[]:str.getBytes(charsetName);

byte[]      -->String :new String(byte[],charsetName);

编码解码的时候要是遇到解错码,且都是中文,一定得注意不能使用UTF-8去解码,因为都识别中文

8、字符编码—联通

由于联通的二进制编码表正好也符合UTF-8的编码表,因此在记事本解码联通两个字的时候,就会解析出错

联通二进制最后八位:

11000001

10101010

11001101

10101000

刚好符合UTF-8中的110    10的解码方式,因此就会导致解码出错。