- 输入流和输出流相对于内存设备而言.
- 将外设中的数据读取到内存中:输入
- 将内存的数写入到外设中:输出
- 字节流的两个顶层父类:
- InputStream
- OutputStream.
- 字符流的两个顶层父类:
- Reader
- Writer
- 这些体系的子类都以父类名作为后缀。
- 而且子类名的前缀就是该对象的功能。
字符流
- 字符流的由来:
- 其实就是:字节流读取文字字节数据后,不直接操作而是先查指定的编码表。获取对应的文字。
- 在对这个文字进行操作。简单说:字节流+编码表
FileWriter
使用方法
- 需求:将一些文字存储到硬盘一个文件中。流程在代码注释中。
- 创建一个可以往文件中写入字符数据的字符输出流对象,如第14行代码
- 既然是往一个文件中写入文字数据,那么在创建对象时,就必须明确该文件(用于存储数据的目的地)。
- 如果文件不存在,则会自动创建;如果文件存在,则会被覆盖。
- 为什么调用Writer对象中的write(string)方法,写入数据后,文件并没有显示写入的数据?
- 其实数据只是写入到临时存储缓冲区中,还需要进行刷新 flush();
- close();关闭流,关闭资源。在关闭前会先调用flush刷新缓冲中的数据到目的地。
1 | public class FileWriterDemo { |
处理异常
- 最好使用try,catch。在外部初始化创建 fileWriter,new在try代码块内部
- finally中判断是否为空,然后写关闭的语句,给它也进行try,catch
1 | public class IOExceptionDemo { |
FileReader
- 需求:读取一个文本文件。将读取到的字符打印到控制台
1 | FileReader fr = new FileReader("demo.txt"); |
- 第二种读取方式:
- 以数组为单位的读取
1 | 假设demo.txt中有 abcde 五个字母 |
- 为什么一共只有五个字母,第二次读取时却是 ”dec“ ?
- 因为是存在缓冲区,第一次abc没有清除,第二次是de覆盖了ab
1 | FileReader fr = new FileReader("demo.txt"); |
练习:复制文件
- 练习:将c盘的一个文本文件复制到d盘
1 | public class CopyTextTest_2 { |
缓冲区
- 这就好比去超市,没有缓冲区就是买一样东西一结帐,有缓冲区就是拿着筐或者推着车,买完再结帐,可以大幅度提高效率
- 不过如果车过大会堵塞出口,那样的话就可以在车子里面放几个筐,结账用筐,也会提高效率
BufferWriter
1 | public static void main(String[] args) throws IOException { |
BufferReader
1 | FileReader fr = new FileReader("buf.txt"); |
自定义MyBufferReader
1 | /** |
装饰设计模式
- 对一组对象的功能进行增强时,就可以使用该模式进行问题的解决。
- 缓冲区的设计就是基于装饰设计模式实现的
- 然而装饰和继承都能实现一样的特点:进行功能的扩展增强。哪个好那?
装饰和继承对比
- 假设有一个继承体系
1 | Writer |
- 想要对操作的动作进行效率的提高,需要加入缓冲技术。
- 按照面向对象,可以通过继承对具体的进行功能的扩展。
1 | Writer |
- 既然加入的都是同一种技术–缓冲。前一种是让缓冲和具体的对象相结合。
- 可不可以将缓冲进行单独的封装,哪个对象需要缓冲就将哪个对象和缓冲关联。
1 | Writer |
- 装饰比继承灵活
- 特点:装饰类和被装饰类都必须所属同一个接口或者父类。
LineNumberReader
- 可以增添行号
1 | public static void main(String[] args) throws IOException { |
字节流
1 | public static void demo_read() throws IOException { |
练习
- 复制一个Mp3文件
1 | public class CopyMp3Test { |
键盘录入
- 运行下面代码后,在控制台输入一个字母,按回车运行,是不是发现多出来一个 10 ?
- 是的,这个是“ \n ”,也就是刚才的回车。
1 | public static void readKey() throws IOException { |
练习
- 获取用户键盘录入的数据,并将数据变成大写显示在控制台上
- 如果用户输入的是over,结束键盘录入。
1 | public static void readKey2() throws IOException { |
转换流
1 | /* 这是方便理解的做法 |
- 1、需求:将键盘录入的数据写入到一个文件中。
1 | BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in)); |
- 2、需求:将一个文本文件内容显示在控制台上。
1 | BufferedReader bufr = new BufferedReader(new InputStreamReader(newFileInputStream("a.txt"))); |
- 3、需求:将一个文件文件中的内容复制到的另一个文件中。
1 | BufferedReader bufr = new BufferedReader(new InputStreamReader(newFileInputStream("a.txt"))); |
- 根据不同的需求,改变源的不同位置即可
编码解码
- file 流中的read write都是默认编码,如果需要指定编码,则选择其父类
- InputStreamReader 、OutputStreamWriter
- OutputStreamWriter接收一个字节输出流对象,既然是操作文件,那么该对象应该是FileOutputStream
1 | OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("uu.txt"),"UTF-8"); |
- 什么时候使用转换流呢?
- 1、源或者目的对应的设备是字节流,但是操作的却是文本数据,可以使用转换作为桥梁。提高对文本操作的便捷。
- 2、一旦操作文本涉及到具体的指定编码表时,必须使用转换流 。
流的操作规律
- 之所以要弄清楚这个规律,是因为流对象太多,开发时不知道用哪个对象合适。
- 想要知道开发时用到哪些对象。只要通过四个明确即可。
- 1、明确源和目的(汇)
1 | 源:InputStream Reader |
- 2、明确数据是否是纯文本数据。
1 | 源:是纯文本: Reader |
到这里,就可以明确需求中具体要使用哪个体系。
- 3、明确具体的设备。
1 | 源设备: |
1 | 目的设备: |
- 4、是否需要其他额外功能。
1 | 1、是否需要高效(缓冲区); |
File对象
- 可以将一个已存在的,或者不存在的文件或者目录封装成file对象。
1 | File f1 = new File("c:\\a.txt"); |
方法
- 1、获取
- 获取文件名称
- 1.2 获取文件路径
- 1.3 获取文件大小
- 1.4 获取文件修改时间
1 | File file = new File("a.txt"); |
- 2、创建与删除
1 | //和输出流不一样,如果文件不存在,则创建,如果文件存在,则不创建。 |
- 3、判断
1 | File f = new File("aaa"); |
- 4、重命名
1 | //可以从C到D盘 重命名 |
根目录方法
1 | File[] files = File.listRoots(); |
获取目录内容
- 获取当前目录下的文件以及文件夹的名称,包含隐藏文件
- 调用list方法的File对象中封装的必须是目录,否则会发生NullPointerException
- 如果访问的系统级目录也会发生空指针异常。
- 如果目录存在但是没有内容,会返回一个数组,但是长度为0.
1 | public static void listDemo() { |
过滤器
- 显示后缀.java 的文件,使用 File类的 list方法
1 | public class FilterByJava implements FilenameFilter { |
- 显示未隐藏的文件,使用 listFiles 方法
1 | public class FilterByHidden implements FileFilter { |
- 如果想过滤后缀,但是每次改过滤器太复杂,可以重新设计过滤器
1 | public class SuffixFilter implements FilenameFilter { |
- 这样ListDemo 类中就可以更改想要的后缀
1 | String[] names = dir.list(new SuffixFilter(".txt")); |
练习
深度遍历
- 需求:对指定目录进行所有内容的列出(包含子目录中的内容),也可以理解为 深度遍历。
1 | public class FileTest { |
删除目录(含内容的)
- 删除一个带内容的目录。
- 原理:必须从最里面往外,需要进行深度遍历。
1 | public class Remove { |
Properties
概述
- Properties 是Map 子类Hashtable 的子类
- Properties集合特点:
- 该集合中的键和值都是字符串类型。
- 集合中的数据可以保存到流中,或者从流获取。
- 通常该集合用于操作以键值对形式存在的配置文件。
演示方法
- 倒数第二行注意,大多数时候用于调试
1 | public static void propertiesDemo(){ |
- 想要将这些集合中的字符串键值信息持久化存储到文件中,则需要关联输出流。(使用store方法)
1 | public static void methodDemo_3() throws IOException { |
- 既然可以存在文件中,自然可以在文件中读取(使用load方法)
- 注意:必须要保证该文件中的数据是键值对
1 | public static void methodDemo_4() throws IOException { |
load方法原理
1 | public static void myLoad() throws IOException{ |
修改配置信息
- 流程
- 读取文件
- 创建集合存储配置信息
- 将流信息存储到集合中
- 修改集合
- 将集合信息写回文件中
1 | public static void test(File file) throws IOException{ |
练习一
- 定义功能:获取一个应用程序运行的次数,
- 如果超过5次,给出使用次数已到请注册的提示,并不要在运行程序。
1 | 思路: |
1 | public class PropertiesTest { |
练习二
- 获取指定目录下,指定扩展名的文件(包含子目录中的),将这些文件的绝对路径写入到一个文本文件中。
- 简单说,就是建立一个指定扩展名的文件的列表。
- 思路:
- 进行深度遍历
- 创建过滤器,将符合要求的文件绝对路径写入集合
- 对容器中内容遍历并将绝对路径写入文件中
1 | //过滤器 |
打印流
PrintStream
- 1、提供了打印方法可以对多种数据类型值进行打印。并保持数据的表示形式。
- 2、它不抛IOException
- 构造函数,接收三种类型的值:
- 1、字符串路径
- 2、File对象
- 3、字节输出流
1 | public static void main(String[] args) throws IOException { |
PrintWriter
- 字符打印流
- 构造函数参数:
- 1、字符串路径
- 2、File对象
- 3、字节输出流
- 4、字符输出流
1 | public static void main(String[] args) throws IOException { |
序列流
- 可以将文件合并为一个序列流,对其进行操作
- 需求:将1.txt 2.txt 3.txt文件中的数据合并到一个文件中。
1 | public class SequenceInputStreamDemo { |
文件切割与合并
切割
- 需求:将一个.MP3 文件按照 1M 切割
- 思路:
- 用读取流关联源文件,定义缓冲区
- 创建目的
- 将被切割文件的信息保存到prop集合中
- 将prop集合中的数据存储到文件中
1 | public class SplitFileDemo { |
合并
- 被切割的文件是无法被运行的,需要进行合并(以上面切割代码为示例)
- 思路:
- 获取指定目录下的配置文件对象
- 记录配置文件对象,获取该配置文件中的信息
- 获取该目录下的所有碎片文件(.part)
- 将碎片文件和流对象关联 并存储到集合中
- 将多个流合并成一个序列流,输出
1 | public class MergeFile { |
操作对象流
- 平常创建的对象都是存储在硬盘上,通过对象输出流可以将其存储在硬盘中
1 | /* |
RandomAccessFile
- 特点:
- 1、即可读取,又可以写入。
- 2、内部维护了一个大型的byte数组,通过对数组的操作完成读取和写入。
- 3、通过getFilePointer方法获取指针的位置,还可以通过seek方法设置指针的位置。
- 4、该对象的内容应该封装了字节输入流和字节输出流。
- 5、该对象只能操作文件。
- 注意:
- 通过seek方法操作指针,可以从这个数组中的任意位置上进行读和写,可以完成对数据的修改。
- 但是要注意:数据必须有规律。
1 | public class RandomAccessFileDemo { |
管道流
- 需要和多线程技术相结合的流对象 PipedOutputStream、PipedInputStream
- 如果想指定哪里来的数据读取哪个数据的话,需要将两个流接上,则使用管道流
1 | public class PipedStream { |
操作基本数据类型的流
- 用操作基本数据类型值的对象 DataInputStream 、DataOutputStream
1 | public class DataSteamDemo { |
操作数组的流
- 设备是内存的流对象。
- ByteArrayInputStream ByteArrayOutputStream
- CharArrayReader CharArrayWriter
- StringReader StringWriter
1 | public class ByteArrayStreamDemo { |
编码表
常见编码表
- ASCII:
- 美国标准信息交换码,用一个字节7位表示
- ISO8859-1:
- 拉丁码表,用一个字节8位表示
- GB2312:
- 中文编码表
- GBK:
- 升级的中文编码表
- Unicode:
- 国际标准码,融合多种文字(均用两字节表示)
- UTF-8:
- 国标码(最多三个字节表示一个字符)
简单编码解码
1 | public static void encodeDemo(String str) |
问题1
- 如果编码的时候是 ” GBK“, 解码的时候是 ”iso8859-1“ 怎么解决
1 | public static void main(String[] args) throws UnsupportedEncodingException { |
- 如果编码用GBK,解码用UTF-8,会出现什么问题
1 | public static void main(String[] args) throws UnsupportedEncodingException { |
- 运行一下上面的代码 看看结果是什么样的?
- 只有谢谢成功转回原样,其他的都是类似这样的–>”锟斤拷锟�“
- 我们把printBytes的注释 解掉,发现 ”-17 -65 -67“ ,重复出现,这是什么原因呢?
- 因为”你好“的字节在经过 UTF-8 转义时出现了错误 ,没有能够成功识别,
- 返回的不是“你好”的字节,而是UTF-8特有的 未知字符 的字节
联通问题
- 切换到桌面,新建一个文本文档,在里面输入”联通“ 这两个字,保存后关闭,重新打开,发现什么问题?
- 是否发现不是联通两个字,变成了未知字符那?
1 | public static void main(String[] args) throws UnsupportedEncodingException { |
- 我们试试用GBK解码”联通“,先看一下他的字节,没有发现什么,再转为二进制取后八位
1 | 11000001 |
- 开头分别是110,10 这就导致它被记事本解码时,误以为是UTF-8,于是解码后出现未知字符
练习
- 在java中,字符串“abcd”与字符串“ab你好”的长度是一样,都是四个字符。
- 但对应的字节数不同,一个汉字占两个字节。
- 定义一个方法,按照最大的字节数来取子串
- 如:对于“ab你好”,如果取三个字节,那么子串就是ab与“你”字的半个,
- 那么半个就要舍弃。如果去四个字节就是“ab你”,取五个字节还是“ab你”.
- 思路:
- 我们先从GBK编码的入手,字母是一个字节,汉字是两个字节
- 汉字两个字节开头都是1,所以为负数,如“你”字,-60 -29
- 以”ab你好“为例,97 98 -60 -29 -70 -61 一次取5字节,进行判断
- 指针指向-70,为负数,指针前移,若指向负数,计数器每次加一,直到指针指向正数
- 如果计数器值%2为0,可以全部输出
- 如果%2为1,输出(截取字节数-1)个字节(取5字节就是这种情况)
1 | public static void main(String[] args) throws IOException { |
- UTF-8
1 | public static String cutStringByU8Byte(String str, int len) throws IOException { |