9、 数据流
DataInputStream
数据输入流允许应用程序以与机器无关方式从底层输入流中读取基本Java 数据类型。 应用程序可以使用数据输出流写入稍后由数据输入流读取的数据。DataInputStream 对于多线程访问不一定是安全的。线程安全是可以选择的,
DataOutputStream
数据输出流允许应用程序以适当方式将基本Java 数据类型写入输出流中。然后,应用程序可以使用数据输入流将数据读入。
public class DataStreamDemo {
private static void read() throws IOException {
File file = new File("11.txt");
InputStream in = new FileInputStream(file);
BufferedInputStream bis = new BufferedInputStream(in);
DataInputStream dis = new DataInputStream(bis);
int num = dis.readInt();
byte b = dis.readByte();
String s = dis.readUTF();
System.out.println(""+num + b + s);
dis.close();
}
private static void write() throws IOException {
File file = new File("11.txt");
OutputStream out = new FileOutputStream(file);
BufferedOutputStream bos = new BufferedOutputStream(out);
DataOutputStream dos = new DataOutputStream(bos);
dos.writeInt(10); // 写入4个字节
dos.writeByte(1); // 写入1个字节
dos.writeUTF("中");
dos.close();
}
public static void main(String[] args) throws IOException {
write();
read();
}
}
案例: 实现文件分割并和
public class FileDivisionMergeDemo {
private static void division(File targetFile, long cutSize) throws IOException {
if (targetFile == null) return;
// 计算文件数
int num = targetFile.length() % cutSize == 0 ?
(int) (targetFile.length() / cutSize) : (int) (targetFile.length() / cutSize + 1);
BufferedInputStream in = new BufferedInputStream(new FileInputStream(targetFile));
BufferedOutputStream out ;
byte[] bytes ; // 每次要读取的字节数
int len ; // 每次实际读取的长度
int count ; // 每个文件读取的次数 = cutSize / 1024
for (int i = 0; i < num; i++) { // 循环文件个数次
out = new BufferedOutputStream(new FileOutputStream(new File((i + 1) + targetFile.getName())));
// 如果每个文件大小小于等于1024, 只要读一次
if (cutSize <= 1024) {
// 只读一次,大小为文件大小
bytes = new byte[(int) cutSize];
count = 1;
} else {
// 每次读1024byte
bytes = new byte[1024];
// 读count 次
count = (int) cutSize / 1024; // 是所需要读的次数 -1次,这里没有考虑余数
}
// 要先判断是否读完, 在读(先看次数,读完了没,如果没有读完,在读)
while (count > 0 && (len = in.read(bytes)) != -1) {
out.write( bytes,0, len);
out.flush();
count--;
}
// 计算每个文件大小除于1024余数, 决定是否要读一次,如果余数不为零
if (cutSize % 1024 != 0) {
bytes = new byte[(int) cutSize % 1024];
len = in.read(bytes);
out.write(bytes, 0, len);
out.flush();
out.close();
}
}
in.close();
}
private static void merge(Enumeration<InputStream> es) throws IOException {
SequenceInputStream sis = new SequenceInputStream(es);
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("第08章 文件与IO_01_File类的使用.mp4"));
byte[] bytes = new byte[1024];
int len ;
while((len = sis.read(bytes))!= -1){
bos.write(bytes,0,len);
bos.flush();
}
bos.close();
sis.close();
System.out.println("合并完成");
}
public static void main(String[] args) throws IOException {
File file = new File("F:\\第08章 文件与IO_01_File类的使用.mp4");
division(file,1024*1024*20);
InputStream in1 = new FileInputStream(new File("1第08章 文件与IO_01_File类的使用.mp4"));
InputStream in2 = new FileInputStream(new File("2第08章 文件与IO_01_File类的使用.mp4"));
// 所有文件
Vector<InputStream> v = new Vector<>();
v.add(in1);
v.add(in2);
Enumeration<InputStream> es = v.elements();
merge(es);
}
}
10、 字符串流、 管道流、 合并流
SequenceInputStream
表示其他输入流的逻辑串联。他从输入流的有序集合开始,并从第一个输入流开始读取,直到文件末尾,并接着从第二个输入流读取,以此类推,直到到达包含的最后一个输入流的文件末尾为止。
StringReader
其源为一个字符串的字符流,可以以一个字符串为一个数据源,构造一个字符流(在WEB开发中,我们经常要从服务器上获取数据,数据的返回格式通过是一个字符串(XML, JSON),我们需要把这个字符串构造成一个字符流,然后再用第三方数据解析器来解析数据)
StringWriter
一个字符流,可以用其回收在字符流缓冲区中的输出来构造字符串。关闭StringWriter无效。此类中的方法在关闭该流后仍可被调用,不会产生任何IOException
管道流
管道输入流应该连接到管道输出流:管道输入流提供要写入管道输出流的所有数据字节。通常,数据由某个线程从PipedInputStream 对象读取,并由其他线程将其写入到相应的PipedOutputStream。 不建议对这两个对象尝试使用单个线程,因为这样可能死锁线程。管道输入流包含一个缓冲区,可在缓冲区限定的范围内将读操作和写操作分开。如果向连接管道输入流提供数据字节的线程不再存在,则认为该管道已损坏。
线程之间的数据通讯
11、RandomAccessFile
RandomAccessFile
是IO包的类,从Object 类直接继承而来,只可以对文件进行操作,可以读取和写入。当模式为r时,当文件不存在会报异常,当模式为rw时,当文件不存在时,会自动创建文件,当文件已经存在时不会对原有文件进行覆盖。它有强大的文件读写功能,其内部是大型byte[], 可以通过seek(), getFilePointer() 等方法操作的指针,方便对数据进行写入与读取。还可以对基本数据类型进行直接的读和写操作。它的大多数共功能,已经被内存映射文件(memory-mapped files) 给取代了,应该考虑一下是不是用“内存映射文件” 来代替RandomAccessFile了
12、 Propertise 文件操作
Properties
(Java.util.Properties), 主要用于读取java 的配置文件,各种语言都有自己所支持的配置文件,配置文件中很多变量是经常改变的,这样做也是为了方便用户,让用户能够脱离程序本身去修改相关的变量设置
- getProperty(String key) 用指定的键在此属性列表中搜索属性。也就是通过参数key, 得到key 所对应的value。
- load(InputStream inStream) 从输入流中读取属性列表(键和元素对)。通过对指定的文件(比如说上面的test.properties 文件) 进行装在来获取该文件中的所有键-值对。 以供getProperty(String key) 搜索
- setProperty(String key, String value) 调用Hashtable 中方法put。 他通过基类的put方法来设置键-值对
- store(OutputStream out, String comments) 以适合使用load 方法加载到Properties 表中的格式,将此Properties 表中的属性列表(键和元素对)写入输出流。与load方法相反,该方法将键-值对写入到指定的文件中去
- clear() 清除所有装在的键-值对。该方法在基类中提供
public class PropertiesDemo {
public static String version = "";
public static String username = "";
public static String password = "";
static {
try {
readConfig();
} catch (IOException e) {
}
}
private static void readConfig() throws IOException {
Properties p = new Properties();
// InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream("array/src/code/config.properties");
InputStream in = new FileInputStream("array/src/code/config.properties");
p.load(in);
version = p.getProperty("app.version");
username = p.getProperty("db.username");
password = p.getProperty("db.password");
in.close();
}
private static void writeConfig(String version, String username, String password) throws IOException {
Properties p = new Properties();
p.put("app.version", version);
p.put("db.username", username);
p.put("db.password", password);
OutputStream out = new FileOutputStream("array/src/code/config.properties");
p.store(out, "update config");
out.close();
}
public static void main(String[] args) throws IOException {
System.out.println(version);
System.out.println(username);
System.out.println(password);
writeConfig("2", "admin", "234");
}
}
13、 文件压缩与解压缩
ZipOutputStream
ZipOutputStream(OutputStream out) 创建新的zip输出流
void putNextEntry(ZipEntry e) 开始写入新的zip 文件条目并将流定位到条目数据的开始处
ZipEntry(String name) 使用指定名称创建新的ZIP条目
ZipInputStream
ZipInputStream 创建新的zip 输入流
ZipEntry getNextEntry() 读取下一个zip 文件条目并将流定位到该条目数据的开始处
public class CompressionAndDecompresDemo {
private static void compression(String zipFileName, File targetFile) throws IOException {
System.out.println("正在压缩");
//要生成的压缩文件的流
ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipFileName));
BufferedOutputStream bos = new BufferedOutputStream(out);
zip(out, targetFile, targetFile.getName(), bos);
bos.close();
out.close();
}
private static void zip(ZipOutputStream zout, File targetFile, String name, BufferedOutputStream bos) throws IOException {
if (targetFile.isDirectory()) {
File[] files = targetFile.listFiles();
if (files.length == 0) {// 空文件夹
zout.putNextEntry(new ZipEntry(name + "/")); //处理空文件夹
}
for (File f : files) {
zip(zout, f, name + "/" + f.getName(), bos);
}
} else {
zout.putNextEntry(new ZipEntry(name));
FileInputStream in = new FileInputStream(targetFile);
BufferedInputStream bis = new BufferedInputStream(in);
byte[] bytes = new byte[1024];
int len = -1;
while ((len = bis.read(bytes)) != -1) {
bos.write(bytes, 0, len);
}
}
}
private static void decompression(String targetFileName, String parent) throws IOException {
ZipInputStream zIn = new ZipInputStream(new FileInputStream(targetFileName));
ZipEntry entry = null;
File file = null;
while ((entry = zIn.getNextEntry()) != null && !entry.isDirectory()) {
file = new File(parent, entry.getName());
if (!file.exists()) {
new File(file.getParent()).mkdirs(); // 创建此文件的上级目录
}
FileOutputStream out = new FileOutputStream(file);
BufferedOutputStream bos = new BufferedOutputStream(out);
byte[] bytes = new byte[1024];
int len = -1;
while ((len = zIn.read(bytes)) != -1) {
bos.write(bytes, 0, len);
}
bos.close();
}
System.out.println("解压成功");
}
public static void main(String[] args) throws IOException {
compression("F:\\Algorithm\\array.zip", new File("F:\\Algorithm\\array"));
decompression("F:\\Algorithm\\array.zip", "F:\\");
}
}
14、 装饰者模式
意图:
动态地给一个对象添加一些额外的职责,就增加功能来说,Decorator 模式相比生成子类更加灵活,该模式以对客户端透明的方式扩展对象的功能
使用环境
在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责
处理哪些可以撤销的职责
当不能使用生成子类的方法进行扩充时,一种情况是,可能有大量独立的扩展,为支持每一种组合产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。
小结
动态地将责任附加到对象上,想要扩展功能,装饰者提供了有别于继承的另一种选择
要点
- 继承属于扩展形式之一,但不见得是达到弹性设计的最佳方案
- 在我们的设计中,应该允许行为可以被扩展,而不须修改现有代码
- 组合和委托可用于在运行时动态地加上新的行为
- 除了继承,装饰者模式也可以让我们扩展行为
- 装饰者模式意味着一群装饰着类,这些类用来包装具体组件。
- 装饰者类反映出被装饰的组建类型(实际上,它们具有相同的类型,都经过接口或继承实现)
- 装饰者可以在被装饰者的行为前面或后面加上自己的行为(甚至将被装饰者行为全部取代掉,以达到特定的目的)
- 可以有无数个装饰者包装一个组件
- 装饰者一般对组建的客户是透明的,除非客户程序依赖于组建的具体类型。
15、 常见字符编码
常见的编码有:ISO8859-1 GBK/GB2312 unicode UTF
iso8859-1: 编码属于单字节编码,最多只能表示0 到255的字符范围,主要在英文上应用(单字节)
GBK/GB2312 : 中文的国际编码, 专门用来表示汉字,是双字节编码
Unicode
Java 中使用这个编码方式,也是最标准的一种编码方式,使用16进制表示的编码,不兼容iso8859-1
UTF
由于unicode 不支持iso8859-1,容易占用更多的空间,而且对于英文字母也需要使用两个字节编码,这样使用unicode 不便于传输和存储,因此产生了UTF编码,UTF编码兼容了iso8859-1编码,也可以用来表示所有语言字符,不过UTF是不定长编码,每个字符的长度从1 到 6 字节不等,一般在中文网页中使用此编码,这样可以节省空间
造成乱码的根本原因
- 程序使用的编码与本机的编码不统一
- 在网络中,客户端与服务端不统一
16、 New IO
NIO 是JDK1.4 加入的新包,NIO 创建的目的是为了 让Java 程序员可以实现高速I/O而无需编写自定义的本机代码。NIO将最耗时的I/O操作(填充和提取缓冲区)转移回操作系统,因为可以极大提高速度
流与块的比较
原来的I/O库与NIO最重要的区别是数据打包和传输方式,原来的I/O 以流的方式处理数据,而NIO以块的方式处理数据。
面向流的I/O系统一次一个字节地处理数据。一个输入流产生一个字节地数据,一个输出流消费一个字节地数据。面向流地I/O通常相当慢。
面向块地I/O系统以块的形式处理数据,每一个操作都在一步中产生或者消费一个数据块。按块处理数据比按(流)字节处理数据要快得多,但是面向块地I/O缺少一些面向流地I/O所具有地优雅性和简单性
缓冲区
在NIO库中,所有数据都是用缓冲区处理的,在读取数据时,它是直接读到缓冲区地。在写数据时,它是写入缓冲区地。任何时候访问NIO中地数据,都是将它放在缓冲区中。
缓冲区实质上是一个数组,通常它是一个字节数组,但是也可以使用其他种类地数组,但是一个缓冲区不仅仅是一个数组。缓冲区提供了对数据的结构化访问,而且可以跟踪系统的读/写进程
缓冲区类型
最常用的缓冲区类型是ByteBuffer。 一个ByteBuffer 可以在其底层字节数组上进行get/ set 操作,(字节的获取和设置)。ByteBuffer 不是NIO中唯一的缓冲区类型,事实上,对于每一种基本Java 类型都有一种缓冲区类型
ByteBuffer CharBuffer ShortBuffer IntBuffer LongBuffer FloatBuffer DoubleBuffer
flip
做了两件事,limit = position; position = 0;
hasRemaining
告知当前位置和限制之间是否有元素
通道
Channel 是一个对象,可以通过它读取和写入数据。拿NIO与原来的I/O做个比较,通道就像是流。
正如前面提到的,所有数据都通过Buffer对象来处理。永远不会将字节直接写入通道中,相反,我是将数据写入包含一个或者多个字节的缓冲区。同样,不会直接从通道中读取字节,而是将数据从通道读入缓冲区,再从缓冲区获取这个字节
比较IO操作的性能比较
- 内存映射最快
- NIO读写文件
- 使用了缓存的IO流
- 无缓存的IO流
Path 和 Files
JDK1.7 引入了新的IO 操作类, 位于Java.nio.file 包下,java NIO Path 接口和Files 类
Path 接口
- path 表示的是一个目录名序列,其后还可以跟着一个文件名,路径中第一个部件是根部件时就是绝对路径,如 / C:\ 而允许访问的根部件取决于文件系统
- 以根部件开始的路径是绝对路径,否则就是相对路径
- 静态的Paths.get 方法接收一个或多个字符串,字符串之间的自动使用默认文件系统的路径分隔符连接起来(Unix 是/, Windows 是\ ), 这就解决了跨平台的问题,接着解析连接起来的结果,如果不是合法路径就抛出InvalidPahException 异常,否则就返回一个Path 对象
Files
读写文件
static path write (Path path, byte[] bytes, OpenOption… options) 写入文件
static byte[] readAllBytes(Path path) 读取文件中的所有字节
static path copy(Path source, path target, CopyOption… options) 复制
static path move(Path source, Path target, CopyOption… options) 移动
static void delete (Path path) // 如果Path 不存在文件将抛出异常,此时调用下面的比较好
static boolean deleteIfExists(Path path) 删除如果存在
public class PathFilesDemo {
public static void main(String[] args) throws IOException {
File file = new File("1.txt");
Path p1 = Paths.get("F:\\Algorithm","1.txtt");
System.out.println(p1);
Path p2 = file.toPath();
System.out.println(p2);
Path p3 = FileSystems.getDefault().getPath("F:\\Algorithm","1.txt");
Path p4 = Paths.get("F:\\Algorithm\\1.txt");
String info = "小河流水哗啦啦";
Files.createFile(p4);
Files.createDirectories(p1);
Files.write(p4,info.getBytes("gb2312"), StandardOpenOption.APPEND);
byte[] bytes = Files.readAllBytes(p4);
System.out.println(new String(bytes));
Files.copy(p4,Paths.get("F:\\1.txt"),StandardCopyOption.REPLACE_EXISTING);
Files.move(p4,Paths.get("F:\\1.txt"), StandardCopyOption.REPLACE_EXISTING);
Files.delete(Paths.get("F:\\1.txt"));
System.out.println(Files.deleteIfExists(Paths.get("F:\\1.txt")));
}
}