JAVA 中文件的读写 I/O 输入输出流
阅读 19
收藏 0
2018-02-17
主要内容
1. 编码问题
2.File 类的使用
3.RandomAccessFile 的使用
4.I/O 输入输出流
编码问题:
import java.io.UnsupportedEncodingException;
public class 编码问题 {
- public static void main(String[] args) {
- // 我们项目的默认编码是 GBK
- String s = "测试 ABC";
- byte[] byte1 = s.getBytes(); // 转换成的字节序列用的是项目默认的编码 gbk
- for (byte b: byte1) {
- // 1 byte = 8 位 //toHexString 这个函数是把字节 (转换成了 Int) 以 16 进制的方式显示
- System.out.print(Integer.toHexString(b & 0xff) + " "); // &
- // 0xff 是为了把前面的 24 个 0 去掉只留下后八位
- }
- try {
- // 也可以转换成指定的编码
- byte[] bytes1 = s.getBytes("gbk");
- System.out.println(new String(bytes1));
- } catch(UnsupportedEncodingException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- /**
- * gbk 编码: 中文占用两个字节, 英文占用一个字节 utf-8 编码: 中文占用三个字节, 英文占用一个字节
- * java 是双字节编码, 是 utf-16be 编码 utf-16be 编码: 中文占用两个字节, 英文占用两个字节
- * 当你的字节序列是某种编码时, 这个时候想把字节序列变成字符串, 也需要用这种编码方式, 否则会出现乱码
- */
- try {
- byte[] byte2 = s.getBytes("utf-16be");
- String s2 = new String(byte2, "utf-16be");
- System.out.println(s2);
- } catch(UnsupportedEncodingException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- /**
- * 文本文件就是字节序列, 可以是任意编码的字节序列
- * 如果我们在中文机器上直接创建文本文件, 那么该文件只认识 ANSI 编码(例如直接在电脑中创建文本文件)
- */
- /**
- * byte 转 int 的时候为什么非要先 & 0xff 计算出来才是正确答案?
- * 首先, java 中的二进制采用的是补码形式, 并非原码或反码, 这 3 个概念要搞清楚;
- * 其次, byte 占 8 位, int 占 32 位, 将 byte 强制转换为 int 型时, 如果没有做 &
- * 0xff 运算, 且 byte 对应的值为负数的话, 就会对高位 3 个字节进行补位, 这样就有可能出现补位误差的错误
- * 举例来说, byte 型的 - 1, 其二进制 (补码) 为 11111111(即 0xff), 转换成 int 型, 值也应该为 - 1, 但经过补位后,
- * 得到的二进制为 11111111111111111111111111111111(即 0xffffffff), 这就不是 - 1 了, 对吧?
- * 而 0xff 默认是 int 型, 所以, 一个 byte 跟 0xff 相与, 会先将那个 byte 转化成 int 型运算, 这样, 结果中的高位 3 个字节就总会被清 0,
- * 于是结果就是我们想要的了~
- */
- }
- }
- View Code
File 类的使用:
JAVA.io.File 类用于表示文件 (目录) File 类只用于表示文件( 目录) 的信息 (名称大小等), 不能用于文件内容的访问 File 类的常用 API: 1. 创建 File 对象: File file=new File(String path); 注意: File.seperater(); 获取系统分隔符, 如:\. 2.boolean file.exists(); 是否存在. 3.file.mkdir(); 或者 file.mkdirs(); 创建目录或多级目录 4.file.isDirectory() 或者 file.isFile()判断是否是目录或者是否是文件 5.file.delete(); 删除文件或目录 6.file.createNewFile(); 创建新文件 7.file.getName()获取文件名称或目录绝对路径 8.file.getAbsolutePath()获取绝对路径 9.file.getParent(); 获取父级绝对路径 10.file.getSize(); 获取文件大小 11.file.getFormat(); 获取文件格式名
- import java.io.File;
- import java.io.IOException;
- public class FileDemo {
- /**
- * @param args
- */
- public static void main(String[] args) {
- // 了解构造函数的情况 查帮助 ALT+/
- File file = new File("E:\\javaio\\imooc");
- // 判断文件 / 文件夹是否存在
- // System.out.println(file.exists());
- if (!file.exists())
- file.mkdir(); // file.mkdirs()如果文件不存在, 直接创建文件夹
- // mkdir 创建的一级目录, 如果需要创建多级目录可以使用 mkdirs()
- else
- file.delete();
- // 是否是一个目录 如果是目录返回 true, 如果不是目录 or 目录不存在返回的是 false
- System.out.println(file.isDirectory());
- // 是否是一个文件
- System.out.println(file.isFile());
- // File file2 = new File("e:\\javaio\\ 日记 1.txt");
- File file2 = new File("e:\\javaio", "日记 1.txt");
- if (!file2.exists())
- try {
- file2.createNewFile();
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- else
- file2.delete();
- // 常用的 File 对象的 API
- System.out.println(file);// file.toString()的内容
- System.out.println(file.getAbsolutePath());
- System.out.println(file.getName());
- System.out.println(file2.getName());
- System.out.println(file.getParent());
- System.out.println(file2.getParent());
- System.out.println(file.getParentFile().getAbsolutePath());
- }
- }
- FileDemo
遍历目录
- import java.io.File;
- import java.io.IOException;
- // 列出 File 的一些常用操作比如过滤, 遍历等操作
- public class FileUtils {
- /**
- * 列出指定指定目录下 (包括其子目录) 的所有文件
- *
- * @param dir
- * @throws IOException
- */
- public static void listDirectory(File dir) throws IOException {
- if (!dir.exists()) {
- throw new IllegalArgumentException("目录:" + dir + "不存在");
- }
- if (!dir.isDirectory()) {
- throw new IllegalArgumentException(dir + "不是目录");
- }
- // String[] fileNames =
- // dir.list();// 返回的是字符串数组, list()方法用于列出当前目录下的子目录和文件, 直接子的名称, 不包含子目录下的内容
- // for (String string : fileNames) {
- // System.out.println(dir+"\\"+string);
- // }
- //
- // 如果要遍历子目录下的内容就需要构造成 File 对象做递归操作, File 提供了直接返回 File 对象的 API
- File[] files = dir.listFiles(); // 返回的是直接子目录 (文件) 的抽象
- if (files != null && files.length > 0) { // 确定存在子目录
- for (File file: files) {
- if (file.isDirectory()) {
- // 递归
- listDirectory(file);
- } else {
- System.out.println(file);
- }
- }
- }
- }
- }
- FileUtils
RandomAccessFile 的使用
RandomAccessFile JAVA 提供的对文件内容的访问, 既可以读文件, 也可以写文件 RandomAccessFile 支持随机访问文件, 可以访问文件的任意位置 (1)JAVA 文件模型 在硬盘上的文件是 byte byte byte 存储的, 是数据的集合 (2)打开文件 有两种模式 "rw"(读写) "r"(只读) RandomAccessFile raf = new RandomeAccessFile(file,"rw") 文件指针, 打开文件时指针在开头 pointer = 0; (3) 写方法 raf.write(int)--->只写一个字节 (后 8 位), 同时指针指向下一个位置, 准备再次写入 提供和很多方法能够一次读写一个基本类型的数据 (4) 读方法 int b = raf.read()--->读一个字节 (5)文件读写完成以后一定要关闭(Oracle 官方说明)
- import java.io.File;
- import java.io.IOException;
- import java.io.RandomAccessFile;
- import java.util.Arrays;
- public class RafDemo {
- /**
- * @param args
- */
- public static void main(String[] args) throws IOException {
- File demo = new File("demo");
- if (!demo.exists()) demo.mkdir();
- File file = new File(demo, "raf.dat");
- if (!file.exists()) file.createNewFile();
- RandomAccessFile raf = new RandomAccessFile(file, "rw");
- // 指针的位置
- System.out.println(raf.getFilePointer());
- raf.write('A'); // 只写了一个字节
- System.out.println(raf.getFilePointer());
- raf.write('B');
- int i = 0x7fffffff;
- // 用 write 方法每次只能写一个字节, 如果要把 i 写进去就得写 4 次
- raf.write(i >>> 24); // 高 8 位
- raf.write(i >>> 16);
- raf.write(i >>> 8);
- raf.write(i);
- System.out.println(raf.getFilePointer());
- // 可以直接写一个 int
- raf.writeInt(i);
- String s = "中";
- byte[] gbk = s.getBytes("gbk");
- raf.write(gbk);
- System.out.println(raf.length());
- // 读文件, 必须把指针移到头部
- raf.seek(0);
- // 一次性读取, 把文件中的内容都读到字节数组中
- byte[] buf = new byte[(int) raf.length()];
- raf.read(buf);
- System.out.println(Arrays.toString(buf));
- for (byte b: buf) {
- System.out.println(Integer.toHexString(b & 0xff) + " ");
- }
- raf.close();
- }
- }
RafDemo
- import java.io.IOException;
- import java.io.RandomAccessFile;
- public class RafReadDemo {
- /**
- * @param args
- */
- public static void main(String[] args) throws IOException {
- // TODO Auto-generated method stub
- RandomAccessFile raf = new RandomAccessFile("demo/raf.dat", "r");
- raf.seek(2);
- int i = 0;
- int b = raf.read(); // 读取到一个字节
- System.out.println(raf.getFilePointer());
- i = i | (b << 24);
- b = raf.read();
- i = i | (b << 16);
- b = raf.read();
- i = i | (b << 8);
- b = raf.read();
- i = i | b;
- System.out.println(Integer.toHexString(i));
- raf.seek(2);
- i = raf.readInt();
- System.out.println(Integer.toHexString(i));
- raf.close();
- }
- }
RafReadDemo 序列化与基本类型序列化 1)将类型 int 转换成 byte 或将其他数据类型转换成 byte 的过程叫序列化 数据 ---->n byte 2)反序列化 将 n 个 byte 转换成一个数据的过程 nbyte ---> 数据 3)RandomAccessFile 提供基本类型的读写方法, 可以将基本类型数据 序列化到文件或者将文件内容反序列化为数据
- import java.io.File;
- import java.io.IOException;
- import java.io.RandomAccessFile;
- public class RandomAccessFileSeriaDemo {
- /**
- * @param args
- */
- public static void main(String[] args) throws IOException {
- // TODO Auto-generated method stub
- File demo = new File("demo1");
- if (!demo.exists()) demo.mkdir();
- File file = new File(demo, "raf.dat");
- if (!file.exists()) file.createNewFile();
- // 打开文件, 进行随机读写
- RandomAccessFile raf = new RandomAccessFile(file, "rw");
- /* 序列化 */
- int i = 0x7ffffff;
- raf.write(i >>> 24);
- raf.write(i >>> 16);
- raf.write(i >>> 8);
- raf.write(i);
- System.out.println(raf.getFilePointer());
- /* 反序列化 */
- raf.seek(0);
- int b = raf.read();
- i = i | (b << 24);
- b = raf.read();
- i = i | (b << 16);
- b = raf.read();
- i = i | (b << 8);
- b = raf.read();
- i = i | b;
- System.out.println(Integer.toHexString(i));
- raf.close();
- }
- }
- RandomAccessFileSeriaDemo RandomAccessFileSeriaDemo
I/O 输入输出流
流的定义:
流就是程序和设备之间嫁接起来的一根用于数据传输的管道, 这个管道上有很多按钮, 不同的按钮可以实现不同的功能
这根用于数据传输的管道就是流, 流就是一根管道
输入时, 程序在源 (文件, 网络, 内存) 上打开一个流, 然后如图一个一个顺序读写也一样
流的分类和使用:
四大基本抽象流, 文件流, 缓冲流, 转换流, 数据流 ,Print 流, Object 流
JAVA.io 包中定义了多个流类型 (类或抽象类) 来实现输入 / 输出功能; 可以从不同角度对其进行分类:
* 按数据流的方向不用可以分为输入流和输出流
* 按处理数据单位不同可以分为字节流和字符流
* 按照功能不同可以分为节点流和处理流
JAVA 中所提供的的所有流类型位于包 JAVA.io 内, 都分别继承自以下四种抽象流类型:
节点流与处理流:
节点流可以从一个特定的数据源 (节点) 读取数据(如: 文件, 内存)
处理流是连接在已存在的流 (节点流或处理流) 之上, 通过对数据的处理为程序提供更为强大的读写功能
节点流也叫原始流, 处理流也叫包裹流
流与类的关系:
如果一个类是用作设备和程序之间的数据传输, 则这个类有一个新的名字叫做流
流一定是类, 但类不一定是流
四大基本流的介绍
输入流, 输出流, 字节流, 字符流
InputStream 和 OutputStream 读写数据的单位是一个字节
Reader 和 Writer 读写数据的单位是一个字符
在 JAVA 中一个字符占两个字节
InputStream,OutputStream,Reader,Writer 都是抽象类, 或者说都是抽象流, 通常我们使用的都是它们的子类, 凡是以 Stream 结尾的都是字节流
InputStream 流中的常用方法:
OutputStream 流中的常用方法:
Reader 流中的常用方法:
Writer 流中的常用方法:
文件流
文件流包括:
FileInputStream FileOutputStream -- 字节流
FileReader FileWriter -- 字符流
实例: 读取一个文件的内容并将其输出到显示器上, 并统计读取的字节个数
- /*
- 利用 FileReader 流来读取一个文件中的数据, 并在显示器上输出!
- */
- import java.io. * ;
- public class TestFileReader {
- public static void main(String[] args) {
- FileReader fr = null;
- try {
- fr = new FileReader("C:\\Documents and Settings\\others\\ 桌面 \\java\\TestFileReader.java");
- int cnt = 0;
- int ch;
- while ( - 1 != (ch = fr.read())) // 20 行
- {
- System.out.print((char) ch); // System.out.print(int ch);
- // 这是在显示器上输出 ch 的整数值, 所以必须的进行类型转化, 我们需要输出的是 ch 所代表的整数对应的字符
- ++cnt;
- }
- System.out.printf("总共从 TestFileReader.java 文件中读取了 %d 个字符", cnt);
- } catch(FileNotFoundException e) {
- System.out.println("找不到文件!");
- System.exit( - 1);
- } catch(IOException e) {
- System.out.println("文件读取失败!");
- System.exit( - 1);
- }
- }
- }
- View Code
FileInputStream 的使用
FileReader 的使用
字节流与字符流的区别:
FileInputStream 和 FileOutputStream 可以完成所有格式文件的复制
FileReader 和 FileWriter 只可以完成文本文件的复制, 却无法完成其他格式文件的复制
因为字节是不需要解码和编码的, 将字节转化为字符才存在解码和编码的问题
字节流可以从所有格式的设备中读写数据, 但字符流只能从文本格式的设备中读写数据
实例: 编程实现文件的复制
- /*
- 利用 FileInputStream 和 FileOutputStream 可以完成所有格式文件的赋值
- 因为字节是不需要解码和编码的, 将字节转化为字符才存在解码的问题
- 本程序完成了音频文件的复制
- */
- import java.io. * ;
- public class TestFileInputStreamOutputStreamCopy {
- public static void main(String[] args) {
- FileInputStream fi = null;
- FileOutputStream fo = null;
- try {
- fi = new FileInputStream("E:\\ 综艺 \\ 歌曲 \\ 卡农. mp3");
- fo = new FileOutputStream("d:/share/Output.txt"); // 使用播放器可正常播放该文件
- int ch;
- while ( - 1 != (ch = fi.read())) {
- fo.write(ch);
- }
- } catch(FileNotFoundException e) {
- System.out.println("文件没有找到!");
- System.exit( - 1);
- } catch(IOException e) {
- System.out.println("文件读写错误!");
- System.exit( - 1);
- } finally {
- try {
- if (null != fi) {
- fi.close();
- fi = null;
- }
- if (null != fo) {
- fo.close();
- fo = null;
- }
- } catch(Exception e) {
- e.printStackTrace();
- System.exit( - 1);
- }
- }
- System.out.println("文件复制成功!");
- }
- }
TestFileInputStreamOutputStreamCopy
- /*
- 本程序证明了 FileReader 和 FileWriter 只可以完成文本文件的复制,
- 却无法完成音频格式文件的复制
- */
- import java.io. * ;
- public class TestFileReaderWriterCopy {
- public static void main(String[] args) {
- FileReader fi = null;
- FileWriter fo = null;
- try {
- fi = new FileReader("E:\\ 综艺 \\ 歌曲 \\ 卡农. mp3");
- fo = new FileWriter("d:/share/Output.txt"); // Output.txt 使用播放器打开失败!
- // 本程序证明了 FileWriter 和
- // FileReader
- // 无法完成音频文件的复制, 实际上 FileWriter
- // 和 FileReader
- // 只能完成文本文件的复制
- int ch;
- while ( - 1 != (ch = fi.read())) {
- fo.write(ch);
- }
- } catch(FileNotFoundException e) {
- System.out.println("文件没有找到!");
- System.exit( - 1);
- } catch(IOException e) {
- System.out.println("文件读写错误!");
- System.exit( - 1);
- } finally {
- try {
- if (null != fi) {
- fi.close();
- fi = null;
- }
- if (null != fo) {
- fo.close();
- fo = null;
- }
- } catch(Exception e) {
- e.printStackTrace();
- System.exit( - 1);
- }
- }
- System.out.println("文件复制成功!");
- }
- }
- TestFileReaderWriterCopy
缓冲流
缓冲流就是带有缓冲区的输入输出流
缓冲流可以显著的减少我们对 IO 访问的次数, 保护我们的硬盘
缓冲流本事就是处理流(包裹流), 缓冲流必须得依附于节点流(原始流)
处理流包裹在原始节点流上的流, 相当于包裹在管道上的管道
缓冲流要 "套接" 在相应的节点流之上, 对读写的数据提供了缓冲的功能, 提高了读写的效率, 同时增加了一些新的方法 JAVA 提供了四种缓冲流, 其常用的构造方法为:
BufferedOutputStream 和 BufferedInputStream
BufferedOutputStream : 带有缓冲的输出流, 允许一次向硬盘写入多个字节的数据
BufferedInputStream: 带缓冲的输入流, 允许一次向程序中读入多个字节的数据
BufferedOutputStream 和 BufferedInputStream 都是包裹流, 必须依附于 OutputStream 和 InputStream
例子: 利用 BufferedOutputStream 和 BufferedInputStream 完成大容量文件的复制, 这远比单纯利用 FileInputStream 和 FileOutputStream 要快的多
- /*
- 利用 BufferedOutputStream 和 BufferedInputStream 完成大容量文件的复制
- 这远比单纯利用 FileInputStream 和 FileOutputStream 要快得多
- BufferedOutputStream 和 BufferedInputStream 都是包裹流, 必须的依附于
- InputStream 和 OutputStream
- */
- import java.io. * ;
- public class TestBufferedInputStreamOutputStreamCopy {
- public static void main(String[] args) {
- BufferedOutputStream bos = null;
- BufferedInputStream bis = null;
- try {
- bos = new BufferedOutputStream(new FileOutputStream("e:/OutputView.txt")); // bos
- // 输出流有个默认的缓冲区, 大小为 32 个字节
- bis = new BufferedInputStream(new FileInputStream("c:\\[高清在线 www.66ys.cn]海底总动员 DVD 中英字幕. rmvb")); // bis
- // 输入流有个默认的缓冲区, 大小为 32 个字节
- byte[] buf = new byte[1024];
- int len = bis.read(buf, 0, 1024); // 一定要注意, 这不是从 buf 中读数据, 而是从 bis 所关联到的 D:\\ 综艺 \\ 电影 \\ 猫和老鼠 \\CD4.rmvb 文件中读取数据, 并将读取的数据写入 bis 自己的默认缓冲区中, 然后再将缓冲区的内容写入 buf 数组中, 每次最多向 buf 数组中写入 1024 个字节, 返回实际写入 buf 数组的字节个数, 如果读到了文件的末尾, 无法再向 buf 数组中写入数据, 则返回 - 1
- while ( - 1 != len) {
- bos.write(buf, 0, len); // 不是写入 buf 数组, 而是将 buf 数组中下标从 0 开始的到 len-1 为止的所有数据写入 bos 所关联到的 "d:/share/OutputView.txt" 文件中
- len = bis.read(buf); // bis.read(buf); 等价于 bis.read(buf, 0,
- // buf.length);
- }
- bos.flush();
- bis.close();
- bos.close();
- } catch(FileNotFoundException e) {
- System.out.println("没有找到文件!");
- System.exit( - 1);
- } catch(IOException e) {
- System.out.println("文件读写错误!");
- System.exit( - 1);
- }
- System.out.println("文件复制成功!");
- }
- }
TestBufferedInputStreamOutputStreamCopy
- /*
- 本程序读写速度要慢于 "TestBufferedInputStreamOutputStreamCopy.java" 程序
- 即:
- 利用 BufferedOutputStream 和 BufferedInputStream 完成大容量文件的复制
- 这远比单纯利用 FileInputStream 和 FileOutputStream 要快得多
- BufferedOutputStream 和 BufferedInputStream 都是包裹流, 必须的依附于
- OutputStream 和 OutputStream
- */
- import java.io. * ;
- public class TestBufferedInputStreamOutputStreamCopy_2 {
- public static void main(String[] args) {
- FileOutputStream bos = null;
- FileInputStream bis = null;
- try {
- bos = new FileOutputStream("e:/OutputView.txt");
- bis = new FileInputStream("c:\\[高清在线 www.66ys.cn]海底总动员 DVD 中英字幕. rmvb");
- byte[] buf = new byte[1024];
- int len = bis.read(buf, 0, 1024);
- while ( - 1 != len) {
- bos.write(buf, 0, len);
- len = bis.read(buf);
- }
- bos.flush();
- bis.close();
- bos.close();
- } catch(FileNotFoundException e) {
- System.out.println("没有找到文件!");
- System.exit( - 1);
- } catch(IOException e) {
- System.out.println("文件读写错误!");
- System.exit( - 1);
- }
- System.out.println("文件复制成功!");
- }
- }
- TestBufferedInputStreamOutputStreamCopy_2
一定要注意, bis.read(buf,0,1024); 这不是从 buf 中读数据, 而是从 bis 所关联到的 D:\\ 综艺 \\ 电影 \\ 猫和老鼠 \\CD4.rmvb 文件中读取数据, 并将读取的数据写入 bis 自己的默认缓冲区中, 然后再将缓冲区的内容写入 buf 数组中, 每次最多向 buf 数组中写入 1024 个字节, 返回实际写入 buf 数组的字节个数, 如果读到了文件的末尾, 无法再向 buf 数组中写入数据, 则返回 - 1
BufferedInputStream 流中有 public int read(byte[] b)方法用来把从当前流关联到的设备中读取出来的数据存入一个 byte 数组中
BufferedOutputStream 流中有 public int write(byte[] b)方法用来把 byte 数组中的数据输出来当前流所关联到的设备中
如果我们希望用 BufferedInputStream 和 BufferedOutputStream 完成将一个设备中的数据导入另一个设备中, 我们就应该定义一个临时的 byte 类型的数据, 用这个临时数组作为输入流和输出流进行交互的中转枢纽
BufferedReader 和 BufferedWriter
实例: 利用 BufferedReader 和 BufferedWriter 完成文本文件的复制
- /*
- 利用 BufferedReader 和 BufferedWriter 完成文本文件的复制
- */
- import java.io. * ;
- public class TestBufferedReaderWriterCopy {
- public static void main(String[] args) {
- BufferedReader br = null;
- BufferedWriter bw = null;
- try {
- br = new BufferedReader(new FileReader("C:\\Documents and Settings\\others\\ 桌面 \\java\\TestBufferedReaderWriterCopy.java"));
- bw = new BufferedWriter(new FileWriter("d:/share/Writer.txt"));
- String str = null;
- while (null != (str = br.readLine())) // br.readLine()读取一行字符, 但会将读取的换行符自动丢弃, 即返回的 String 对象中并不包括换行符
- {
- bw.write(str);
- bw.newLine(); // 写入一个换行符 这行不能省
- }
- bw.flush();
- } catch(FileNotFoundException e) {
- e.printStackTrace();
- System.exit( - 1);
- } catch(IOException e) {
- e.printStackTrace();
- System.exit( - 1);
- } finally {
- try {
- bw.close();
- br.close();
- } catch(IOException e) {
- e.printStackTrace();
- System.exit( - 1);
- }
- }
- }
- }
- TestBufferedReaderWriterCopy
数据流 DataInputStream DataOutputStream
DataInputStream 能够以一种与机器无关的方式, 直接从底层字节输入流读取 JAVA 基本类型和 String 类型的数据常用方法包括:
DataInputStream 是包裹流, 必须依附于 InputStream
DataOutputStream 能够以一种机器无关的方式, 直接将 JAVA 基本类型和 String 类型数据写出到其他的字节输出流常用方法包括:
DataOutputStream 是包裹流, 它必须依附于 OutputStream
数据流实例:
编程实现将 long 类型数据写入 byte 数组, 然后再从 byte 数组中吧该数据读出来{
* 这是 Socket 编程中经常要完成的功能
* 因为网络编程中经常要把数据存入 byte 数组中, 然后把 byte 数组打包成数据包(DatagramPacket), 再把数据包经过网络传输到目的机, 目的机再从 byte 数组中把原数值型数据还原回来
}
本程序要使用到:
- DataInputStream
- DataOutputStream
- ByteArrayInputStream
- ByteArrayOutputStream
- /*
- 功能: 把一个 long 类型的数据写入 byte 数组中, 然后再从 byte 数组中读取出
- 这个 long 类型的数据
- 因为网络编程中经常要把数值型数据存入 byte 数组中然后打包成
- DatagramPacket 经过网络传输到目的机, 目的机再从 byte 数组中
- 把原数值型数据还原回来
- 目的: ByteArrayOutputStream DataOutputStream ByteInputStream DataInputStream 流的使用
- 记住: DataOutputStream 流中的 writeLong(long n)是把 n 变量在内存
- 中的二进制代码写入该流所连接到的设备中
- 注意: 查 API 文档得知:
- 构造 ByteArrayOutputStream 对象时不需要也不能指定缓冲数组, 因为缓冲数组默认已经内置好了
- 构造 ByteArrayInputStream 对象时必须的指定缓冲数组是谁!
- */
- import java.io. * ;
- public class TestByteArrayOutputStream1 {
- public static void main(String args[]) throws Exception {
- long n = 9876543210L;
- ByteArrayOutputStream baos = new ByteArrayOutputStream(); //9 行 API:"public ByteArrayOutputStream(): 创建一个新的 byte 数组输出流缓冲区的容量最初是 32 字节, 如有必要可增加其大小"
- //9 行代码一旦执行完毕, 意味着两点: 1 在内存中生成了一个大小为 32 个字节的 byte 数组 2 有一根叫做 baos 的管道已链接到了该 byte 数组中, 并且可以通过这个管道向该 byte 数组中写入数据
- // 虽然此时可以通过 baos 向 baos 所连接到的在内存中分配好的 byte 数组中写入数据, 但是 ByteArrayOutputStream 流并没有提供可以直接把 long 类型数据直接写入 ByteArrayOutputStream 流所连接到的 byte 数组中的方法, 简单说我们没法通过 baos 向 baos 所连接到的 byte 数组中写入 long 类型的数据, 查 API 文档可以发现: ByteArrayOutputStream 流中并没有类似 writeLong()这样的方法, 但是 DataOutputStream 流中却有 writeLong() writeFloat()等方法
- DataOutputStream dos = new DataOutputStream(baos);
- dos.writeLong(n); // 把 n 变量所代表的 10000L 在内存中的二进制代码写入 dos 所依附的 baos 管道所连接到的内存中的大小为 32 字节的 byte 数组中, 由运行结果来看, 这是二进制写入, 既不是把 10000L 转化为字符'1' '0' '0' '0' '0'写入 byte 数组中, 而是把 10000L 在内存中的总共 8 个字节的二进制代码写入 byte 数组中
- dos.flush();
- byte[] buf = baos.toByteArray(); //DataOutputStream 流中并没有 toByteArray()方法, 但是 ByteArrayOutputStream 流中却有 toByteArray()方法, 所以不可以把 baos 改为 dos, 否则编译时会出错! ByteArrayOutputStream 流中 toByteArray()方法的含义, 摘自 API 创建一个新分配的 byte 数组其大小是此输出流的当前大小, 并且缓冲区的有效内容已复制到该数组中
- // 利用 ByteArrayInputStream 和 DataInputStream 可以从 byte 数组中得到原 long 类型的数值 10000L
- ByteArrayInputStream bais = new ByteArrayInputStream(buf);
- DataInputStream dis = new DataInputStream(bais);
- long l = dis.readLong();
- System.out.println("l =" + l);
- dos.close();
- }
- }
- /*
- 在 JDK 1.6 中的运行结果是:
- ----------------
- l = 9876543210
- ----------------
- */
TestByteArrayOutputStream1
- /*
- 功能:
- 将 long 类型数据写入 byte 数组, 然后在从 byte 数组中把该数据读出来
- */
- import java.io. * ;
- public class TestByteArrayOutputStream2 {
- public static void main(String[] args) throws Exception {
- long n = 1234567;
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- DataOutputStream dos = new DataOutputStream(baos);
- dos.writeLong(n);
- byte[] buf = baos.toByteArray();
- ByteArrayInputStream bis = new ByteArrayInputStream(buf);
- DataInputStream dis = new DataInputStream(bis);
- long n2 = dis.readLong();
- System.out.println("n2 =" + n2);
- dos.close();
- dis.close();
- }
- }
- TestByteArrayOutputStream2
转换流: OutputStreamWriter InputStreamReader
OutputStreamWriter 流是把 OutputStream 流 转化成 Writer 流的流
InputStreamReader 流是把 InputStream 流转化为 Reader
OutputStreamWriter 和 InputStreamReader 都是包裹流
实例: 如何将键盘输入的字符组成字符串直接赋给 String 对象
- /*
- 如何将键盘输入的字符组成字符串直接赋给 String 对象
- 预备知识:
- --------------------------------
- Reader FileReader InputStream FileInputStream BufferedInputStream
- 流中都没有 readLine 方法
- DataInputStream 流中有 readLine 方法, 但已经 被标记为过时
- BufferedReader 流中有 readLine 方法, 并且该方法是可以正确被使用的
- --------------------------------
- */
- import java.io.*;
- public class TestStringInput
- {
- public static void main(String[] args)
- {
- String str = null;
- BufferedReader br = new BufferedReader ( //21 行
- new InputStreamReader(System.in)
- ); //23 行 查 API: 从 21 行到 23 行的代码是不会抛出任何异常的
- try
- {
- str = br.readLine(); // 会抛出 IOException 异常
- }
- catch (IOException e)
- {
- e.printStackTrace();
- System.exit(-1);
- }
- System.out.println("str =" + str);
- try
- {
- br.close(); // 会抛出 IOException 异常
- }
- catch (IOException e)
- {
- e.printStackTrace();
- System.exit(-1);
- }
- }
- }
- /*
- 在 JDK 1.6 中的运行结果是:
- --------------------------------
- sadd 行政村 123Asd?asd 撒旦
- str = sadd 行政村 123Asd?asd 撒旦
- --------------------------------
- */
- TestStringInput
readLine()与回车符的问题:
Print 流 PrintWriter PrintStream
Print 流只有输出, 没有输入
分类:
PrintWriter 输入字符
PrintStream 输出字符
PrintWriter 在 OutputStream 基础之上提供了增强的功能, 既可以方便地输出各种类型数据 (而不仅限于 byte 型) 的格式化表示形式
PrintStream 重载了 print 和 println 方法, 用于各种不同类型数据的格式化输出
格式化输出是指将一个数据用其字符串格式输出
DataOutputStream 中的 WriteXXX(data)方法是把 data 在内存中的二进制数据写入文件
PrintStream 中的 println(data)是该数据格式化后的字符串写入文件
- /*
- DataOutputStream 中的 writeXXX(data)方法
- 与
- PrintStream 中的 println(data)的区别
- 总结:
- DataOutputStream 中的 writeXXX(data)方法是把 data 在内存中的二进制数据写入文件
- PrintStream 中的 println(data)写出的是该数据的格式化后的字符串
- */
- import java.io. * ;
- public class TestPrintStream_1 {
- public static void main(String[] args) throws Exception {
- DataOutputStream dos = new DataOutputStream(new FileOutputStream("d:/share/kk.txt"));
- dos.writeLong(12345); // 实际写入文件的是 00 00 00 00 00 00 30 39
- dos.close();
- System.out.printf("%#X\n", 12345);
- PrintStream ps = new PrintStream(new FileOutputStream("d:/share/kk2.txt"), true);
- ps.println(12345); // 实际写入文件的是'1' '2' '3' '4' '5'
- ps.close();
- }
- }
- TestPrintStream_1
PrintWriter 提供了 PrintStream 的所有打印方法, 其方法也从不抛出 IOException
与 PrintStream 的区别:
标准输入输出的重定向:
实例: 编程实现将键盘输入的数据输入 A 文件中, 如果输入有误, 则把出错信息输出到 B 文件
- import java.io. * ;
- public class TestSetSystemOut {
- public static void main(String[] args) {
- PrintStream ps_out = null;
- try {
- ps_out = new PrintStream(new FileOutputStream("d:/share/ww.txt"));
- System.setOut(ps_out); // 将 System.out 的值重新设置为 ps_out, 即 System.out 不在关联到显示器, 而是关联到 "d:/share/ww.txt" 文件
- System.out.println(12); // 这实际上是把 12 输出到了 System.out 所关联的 d:/share/ww.txt 中
- System.out.println(55.5); // 同上
- } catch(Exception e) {
- e.printStackTrace();
- } finally {
- try {
- ps_out.close();
- } catch(Exception e) {
- e.printStackTrace();
- }
- }
- }
- }
TestSetSystemOut
- /*
- 功能: 将键盘输入的数据输入 A 文件中, 如果输入有误,
- 则把出错信息输出到 B 文件中
- 标准输入输出流的重定向
- */
- import java.io. * ;
- import java.util. * ;
- public class TestSetOutErr {
- public static void main(String[] args) {
- PrintStream psOut = null;
- PrintStream psError = null;
- Scanner sc = null;
- try {
- psOut = new PrintStream("d:/Out.txt");
- psError = new PrintStream("d:/error.txt");
- sc = new Scanner(System. in );
- int num;
- System.setOut(psOut);
- System.setErr(psError);
- while (true) {
- num = sc.nextInt();
- System.out.println(num);
- }
- } catch(Exception e) {
- System.err.println("出错的信息是:"); // 不可以写成 System.out.println("出错的信息是:");
- e.printStackTrace(); // e.printStackTrace(); 默认是输出到 System.err 所关联的设备中
- }
- }
- }
TestSetOutErr 对象的序列化 ObjectOutputStream ObjectInputStream 所谓序列化是指: 把一个 Object 对象直接转化为字节流, 然后把这个字节流直接写入本地硬盘或网络中 序列化流(ObjectOutputStream)----writeObject 反序列化流(ObjectInputStream)---readObject 如果想把某个对象序列化, 则必须实现 Serializable 接口
transient 关键字用用它修饰变量后该变量不会进行 jvm 默认的序列化 当我们要把一个对象在网络上传输转化成字节序列, 我们有些变量没必要使用, 放在网络上传输会浪费资源, 这个时候我们就会用 transient 关键字 transient 修饰的属性我们可以自己完成序列化 transient 关键字在有些情况下可以提高性能
实例:
- import java.io.*;
- public class TestObjectIO
- {
- public static void main(String[] args)
- {
- ObjectOutputStream oos = null;
- ObjectInputStream ois = null;
- Student ss = new Student("zhansan", 1000, 88.8f); // 注意 88.8f 不能改为 88.8
- Student ss2 = null;
- try
- {
- FileOutputStream fos = new FileOutputStream("d:/share/java/ObjectOut.txt");
- oos = new ObjectOutputStream(fos);
- oos.writeObject(ss);
- ois = new ObjectInputStream(new FileInputStream("d:/share/java/ObjectOut.txt"));
- ss2 = (Student)ois.readObject(); //(Student)不能省 ois.readObject(); 如果 ois 中的某个成员是 transient, 则该成员是不会被读取的, 因为该成员不会被保存, 何来读取之说?!
- System.out.println("ss2.sname =" + ss2.sname);
- System.out.println("ss2.sid =" + ss2.sid);
- System.out.println("ss2.sscore =" + ss2.sscore);
- }
- catch (FileNotFoundException e)
- {
- System.out.println("文件没有找到!");
- System.exit(-1);
- }
- catch (Exception e)
- {
- e.printStackTrace();
- System.exit(-1);
- }
- finally
- {
- try
- {
- oos.close();
- ois.close();
- }
- catch (Exception e)
- {
- e.printStackTrace();
- System.exit(-1);
- }
- }
- }
- }
- class Student implements Serializable // 如果将 implements Serializable 注释掉, 则程序编译时就会报错
- {
- public String sname = null;
- public int sid = 0;
- transient public float sscore = 0; // 表示 sscore 成员不能被序列化, 所谓不能被序列化就是指: 该成员调用 ObjectOutputStream 的 writeOnbject()时不会被保存, 调用 ObjectInputStream 的 readObject()方法时不会被读取
- public Student(String name, int id, float score)
- {
- this.sname = name;
- this.sid = id;
- this.sscore = score;
- }
- }
TestObjectIO 分析 ArrayList 源码中序列化和反序列化的问题 ArrayList 的底层虽然是一个数组, 但是这个数组不一定放满, 没有放满的数组元素是不需要进行序列化的, 我们必须自己完成序列化, 把有效元素一个一个自己完成序列化 , 反序列化也一样, 所以 ArrayList 源码中的序列化和反序列化的作用就是把 ArrayList 中的有效元素进行序列化 无效元素不进行序列化, 可以提高性能 对 ArrayList 的源码有个初步的了解, 能够进行序列化的优化问题 序列化中 子类和父类构造函数的调用问题 当一个子类的父类实现了序列化接口, 子类可以直接进行序列化 , 子类序列化时会递归调用父类的构造方法 对子类对象进行反序列化操作时, 如果其父类没有实现序列化接口, 那么其父类的构造方法会被调用
来源: https://juejin.im/entry/5a8664ec6fb9a0633c66167f