最近又看了看IO技术的具体内容,记一下。
IO的由来:不管是对象的什么信息都是在内存中操作的(以前学操作系统、汇编等课程时总是纠结于RAM与ROM,后来才知道我根本就不必纠结那东西的),计算机一旦断电或者操作系统关闭,那么内存中的数据都会消失,因此要想个办法把这些数据保存起来以备下次使用,于是人类发明了各种介质来保存数据。我记得最初的计算机是用纸带打孔来记录数据的,这也是刁刁的( ⊙ o ⊙ )啊!后来有了各种高级的东东,硬盘,软盘,磁带、CD、U盘、SD卡……现在的SSD也是刁刁的。就是太贵了点,我2014年买了个256G的SSD花了六百多,而这可以买个1T的机械硬盘了。IO技术由此产生:就是将内存中的数据写到外围存储设备中去,或者将外围存储设备上的数据读到内存中来。
所谓的持久化,就是将内存数据保存到硬盘中去(在此用形象的硬盘代表外围存储设备这种东西),持久,就是能长久保持不变嘛!
I/O:Input输入、Output输出。
输入输出不应该弄混!但是我刚开始学时总是要想半天才把二者对应上各自的功能,还经常把两个单词想半天了才写。给个简单的方法说明下:
内存→外围设备:从内到外,就是出去,所以是输出Output。
外围设备→内存:从外到内,就是进来,所以是输入Input。
文件操作:
知道了IO这个东西,然后就要进行操作一下呢,就是往外设中写点数据保存起来然后再读取一下呗。感觉有点傻。
写?写到哪里去呢?磁盘中。哪个磁盘呢?D盘。D盘哪个位置呢?哪个文件呢?……你干脆一次性说完整得了!
既然准备往磁盘上写数据,那必须得有一个文件接收它才行,或者写数据的人自己创建一个文件。数据在磁盘上的保存后的东西我们都称之为“文件”,由于文件有名称、大小、创建时间……等信息,因此将之构建成了一个对象,于是有了File这个类:java.io.File。然后学习这个类的使用。
然后看文档:File类的构造器:四种:
File(File parent,String child) 根据 parent 抽象路径名和 child 路径名字符串创建一个新 File 实例。 |
File(String pathname) 通过将给定路径名字符串转换为抽象路径名来创建一个新 File 实例。 |
File(String parent,String child) 根据 parent 路径名字符串和 child 路径名字符串创建一个新 File 实例。 |
File(URI uri) 通过将给定的 file: URI 转换为一个抽象路径名来创建一个新的 File 实例。 |
于是准备好文件: File file = new File("D:\test.txt");
报错了!因为\的问题,\在java中是个转义字符,要用\\才能表示。注意UNIX中是用/来分隔路径的,不需要转义。
于是改成:File file = new File("D:\\test.txt");
然后想到:既然要区分window系统与UNIX系统(两者有很多不同,换行符就是其中一例),那么可以将这些不同的信息封装起来,然后给你提供个方法,你告诉这个方法要什么信息,这个方法内部就根据当前平台返回给你正确的值,所以就有了System类封装了很多与平台有关的信息,
public static String getProperty(String key)获取指定键指示的系统属性。
根据文档,我们只需要告诉他想要获取的key关键字就可以了,文件分隔符是file.separator
所以又改成了:
File file = new File("D:"+System.getProperty("file.separator")+"test.txt");
然后又想到(人们没事了总是爱多想):既然经常要用到\来分隔路径,那就总是要用到System.getProperty("file.separator")获取分隔符,还是嫌麻烦了,所以干脆将之定义成了File的一个static常量
所以就有了:
File file = new File("D:"+File.separator+"test.txt");
这样还是麻烦,总是要自己使用File然后进行.操作,所以又成了:
private static final String FILE_SEPARATOR = File.separator;
File file = new File("D:"+FILE_SEPARATOR +"test.txt");
改了四次终于改好了!
然后文件对象有了,接着就进行文件操作,无非增删改查。
查:获取文件信息:路径、名称、大小、时间……绝壁是getXXX()方法啊!
查文档
String getAbsolutePath() 返回此抽象路径名的绝对路径名字符串。
String getPath() 将此抽象路径名转换为一个路径名字符串。File("XXX")构造时写的什么则返回什么。
String getName()
long length() 返回由此抽象路径名表示的文件的长度。
long lastModified() 返回此抽象路径名表示的文件最后一次被修改的时间。 毫秒值。
boolean isDirectory() 测试此抽象路径名表示的文件是否是一个目录。
boolean isFile() 测试此抽象路径名表示的文件是否是一个标准文件。
boolean isHidden() 测试此抽象路径名指定的文件是否是一个隐藏文件。
注意区分绝对路径与相对路径,如果使用相对路径new File("test.java"),则jvm将自动在用户目录user_dir下寻找该文件,
增:
boolean createNewFile() 创建文件,如果不存在则创建并返回true,如果文件已存在则返回false,如果路径错误则抛出IOException异常。
boolean mkdir() 创建此抽象路径名指定的目录。
boolean mkdirs() 创建此抽象路径名指定的目录,包括所有必需但不存在的父目录。
删:
boolean delete() 删除文件或目录,该方式删除后文件不会到回收站。文件正在被使用时会返回false,如果是目录并且目录中有东西则无法删除,需要从内往外逐个删除。
boolean exists() 删除前最好先判断文件是否存在
String[] list()
String[] list(FilenameFilter filter)
File[] listFiles()
File[] listFiles(FileFilter filter)
File[] listFiles(FilenameFilter filter)
学会写过滤器,想到了everything这个工具,挺快的挺好用的。
使用递归遍历目录,但是递归层级多了可能会造成StackOverFlow栈溢出,可以采用队列的形式来遍历,往队列中边添加边删除,FIFO~First In First Out先进先出的策略。
犹记得刚开始学数据结构时,为了模仿写一个Stack,一直理解了好久然后死记代码。
Queue也是有好多种的,然后还有不同的存储结构的,单链表、双链表、循环队列……
弄了半天,说好的增删改查中的“改”呢?
这里要弄清楚类的设计思想了,File代表一个文件,是不能读写自己身上的东西,还需要一支笔或者一双眼睛,用作者和读者是不是更符合些?就像是一个目的地,需要车往里面运输东西。
因此有了IO流。
OutputStream:抽象类,定义了一些共用的方法,用来输出字节流。具体的输出功能由各个子类实现。根据作用的不同分成了文件输出FileOutputStream、对象输出ObjectOutputStream……后缀都是OutputStream
该类的所有方法:
void close()
关闭此输出流并释放与此流有关的所有系统资源。
void flush()
刷新此输出流并强制写出所有缓冲的输出字节。
void write(byte[] b)
将 b.length 个字节从指定的 byte 数组写入此输出流。
void write(byte[] b, int off, int len)
将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此输出流。
abstract void write(int b)
将指定的字节写入此输出流。
写一次刷新一次以免丢失内容,close时会自动调用刷新。
主要看一下FileOutputStream这个类,对文件进行操作!
FileOutputStream的构造函数:
FileOutputStream(File file)
创建一个向指定 File 对象表示的文件中写入数据的文件输出流。
FileOutputStream(File file, boolean append)
创建一个向指定 File 对象表示的文件中写入数据的文件输出流。
FileOutputStream(FileDescriptor fdObj)
创建一个向指定文件描述符处写入数据的输出文件流,该文件描述符表示一个到文件系统中的某个实际文件的现有连接
FileOutputStream(String name)
创建一个向具有指定名称的文件中写入数据的输出文件流。
FileOutputStream(String name, boolean append)
创建一个向具有指定 name 的文件中写入数据的输出文件流。
这里就可以看出File对象的作用了,就像是一个目的地,输出流就是那辆小卡车。
window中的换行符是\r\n,UNIX的是\n,用System.getProperty("line.separator")来获取
使用时注意处理各种IOException异常
InputStream:接口,定义了一些共用的方法,用来读取字节流。类似输入流,主要了解了FileInputStream
InputStream的主要读取方法:
int available()
返回此输入流下一个方法调用可以不受阻塞地从此输入流读取(或跳过)的估计字节数。
void close()
关闭此输入流并释放与该流关联的所有系统资源。
abstract int read() 从输入流中读取数据的下一个字节。 如果到达流的末尾,则返回
-1
。
子类必须提供此方法的一个实现。
int read(byte[] b) 从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中。 int read(byte[] b, int off, int len) 将输入流中最多 len 个数据字节读入 byte 数组。 注意读取文件的效率问题,使用缓冲区。缓冲区大小设置成1024的整数倍。
有了输入输出流就可以自己复制文件了。这是可以实现很多功能的,记得大学时总是找各种插入U盘后自动复制文件到指定的地方的软件,想用来拷老师的试卷,但是从未成功过~后来找到一段window的vbs脚本代码就完全可以解决问题。现在还存在电脑里面,将这代码保存成.vbs文件,然后双击运行就可以了。就是要自己手动改目录麻烦。
set fso=createobject("scripting.filesystemobject")
set ws=createobject("wscript.shell")
on error resume next
do
wscript.sleep 1000
if fso.driveexists("f:\") then
fso.copyfile "f:\*","E:\USB\"
fso.copyfolder "f:\*","E:\USB\"
wscript.sleep 20000
end if
loop
上面说的都是使用字节流对文件进行的操作,下面说对文件的字符流进行操作。
字节到字符必须要谈到编码表。就像是明文与密文之间的密码表,这里我又想到了加密技术:非对称加密、密钥、公钥、CA、MD5、https……还是不要多想了,加密技术也是刁刁的
常见编码表:
1、ascii:美国的标准信息交换码表,还记得大学的计算机导论期末考试竟然考了这个所写的全写。占用一个字节,格式都是0XXXXXXX,美国的字符太少,7位就可以足够表示他们所有的字符了;
2、iso8859-1:欧洲常用的拉丁latin码表,也是一个字节,格式是1XXXXXXX,该码表包含了ASCII;
3、GB2312:简体中文码表,收录了六七千个字符,占用两个字节,格式:1XXXXXXX 1XXXXXXX,
GBK:升级后的中文码表,2万多字符,2字节,格式:1XXXXXXX 0XXXXXXX
GB18030:这当然是又升了一级咯,7万多个字符,有少数名族的字符哟~
4、Unicode:国际标准码表,各国为码,太混乱了,因此定义了一套国际标准码表,占用2个字节。Java中的char类型就是用的这个码表,因此char类型的数据占用2个字节,但是字符串使用的系统默认的编码表来解析的,简体中文默认的是GBK,
5、UTF-8:基于Unicode的扩展码表,原因是很多字符只需要一个字节就能表示,使用两个字节纯属浪费空间,因此该码表的原则是能用一个字节表示的字符就不用两个字节。
……其他什么各国的乱七八糟的码表就不说了,按照我想的,统一UTF-8,从此天下清净了多好,每次纠结于乱码问题好烦。
文字→字节(二进制数字):编码————————输出
字节(二进制数字)→文字:解码————————输入
String有个构造方法:String (byte[] bytes)这里讲字节转为转化为字符时使用的是操作系统默认的编码,中文是GBK
String(byte[] bytes) 通过使用平台的默认字符集解码指定的 byte 数组,构造一个新的 String 。 |
String(byte[] bytes,Charset charset) 通过使用指定的 charset 解码指定的 byte 数组,构造一个新的 String 。 |
String(byte[] bytes,String charsetName) 通过使用指定的 charset 解码指定的 byte 数组,构造一个新的 String 。 |
java.io.Reader
用于读取字符流的抽象类读取的方法:
int read()
读取单个字符。
int read(char[] cbuf)
将字符读入数组。
abstract int read(char[] cbuf, int off, int len)
将字符读入数组的某一部分。
Reader的孙子:
java.io.FileReader:字节流读取+默认编码
此类的构造方法假定默认字符编码和默认字节缓冲区大小都是适当的:即编码是GBK。
FileReader(File file) 在给定从中读取数据的 File 的情况下创建一个新 FileReader。 |
FileReader(FileDescriptor fd) 在给定从中读取数据的 FileDescriptor 的情况下创建一个新 FileReader。 |
FileReader(String fileName) 在给定从中读取数据的文件名的情况下创建一个新 FileReader。 |
对应的
java.io.Writer
写入字符流的抽象类
void write(char[] cbuf)
写入字符数组。
abstract void write(char[] cbuf, int off, int len)
写入字符数组的某一部分。
void write(int c)
写入单个字符。
void write(String str)
写入字符串。
void write(String str, int off, int len)
写入字符串的某一部分。
用来写入字符文件的便捷类。此类的构造方法假定默认字符编码和默认字节缓冲区大小都是可接受的。java.io.FileWriter
FileWriter(File file) 根据给定的 File 对象构造一个 FileWriter 对象。 |
FileWriter(File file, boolean append) 根据给定的 File 对象构造一个 FileWriter 对象。 |
FileWriter(FileDescriptor fd) 构造与某个文件描述符相关联的 FileWriter 对象。 |
FileWriter(String fileName) 根据给定的文件名构造一个 FileWriter 对象。 |
FileWriter(String fileName, boolean append) 根据给定的文件名以及指示是否附加写入数据的 boolean 值来构造 FileWriter 对象。 |
默认缓冲区满了后会自动将数据写出到文件,但是最好自己控制随时刷新。
然后考虑使用指定的编码表来读写字符
java.io.OutputStreamWriter:Reader的儿子
OutputStreamWriter 是字符流通向字节流的桥梁:可使用指定的 charset
将要写入流中的字符编码成字节。它使用的字符集可以由名称指定或显式给定,否则将接受平台默认的字符集。
OutputStreamWriter(OutputStream out) 创建使用默认字符编码的 OutputStreamWriter。 |
OutputStreamWriter(OutputStream out,Charset cs) 创建使用给定字符集的 OutputStreamWriter。 |
OutputStreamWriter(OutputStream out,CharsetEncoder enc) 创建使用给定字符集编码器的 OutputStreamWriter。 |
OutputStreamWriter(OutputStream out,String charsetName) 创建使用指定字符集的 OutputStreamWriter。 |
java.io.InputStreamReader
InputStreamReader 是字节流通向字符流的桥梁:它使用指定的 charset
读取字节并将其解码为字符。它使用的字符集可以由名称指定或显式给定,或者可以接受平台默认的字符集。
InputStreamReader(InputStream in) 创建一个使用默认字符集的 InputStreamReader。 |
InputStreamReader(InputStream in,Charset cs) 创建使用给定字符集的 InputStreamReader。 |
InputStreamReader(InputStream in,CharsetDecoder dec) 创建使用给定字符集解码器的 InputStreamReader。 |
InputStreamReader(InputStream in,String charsetName) 创建使用指定字符集的 InputStreamReader。 |
java.io.BufferedReader
从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。
BufferedReader(Reader in) 创建一个使用默认大小输入缓冲区的缓冲字符输入流。 |
BufferedReader(Reader in, int sz) 创建一个使用指定大小输入缓冲区的缓冲字符输入流。 |
有个特殊的方法:
String readLine() 读取一个文本行。
装饰设计模式:通过构造器接收被封装的对象,并基于被装饰的对象提供更强的功能。
思考装饰与继承的关系。
想到了之前写Tomcat服务器时也是用到了装饰设计模式的。不过那个是为了避免别人将接收到的对象向下转型后直接调用他们的方法,将其装饰起来后提供给客户装饰的对象,但是都写的是与被装饰的类同名的方法,而且就是直接调用原类的方法,但就是这么一个小小的改进,就可以避免了原类被随意使用。
java.io.BufferedWriter将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。
BufferedWriter(Writer out) 创建一个使用默认大小输出缓冲区的缓冲字符输出流。 |
BufferedWriter(Writer out, int sz) 创建一个使用给定大小输出缓冲区的新缓冲字符输出流。 |
void newLine() 写入一个行分隔符。
编码解码……编码解码……编码解码……编码解码……╮(╯▽╰)╭睡觉(~﹃~)~zZ又这么晚了……