IO操作深入
字符编码
在计算机世界中只认0、1的数据,如果要想描述一些文字的编码就需要对这些二进制的数据进行组合,所以才有了现在可以看见的中文,但是进行编码的时候要想正确显示出内容一定要有解码,所以编码和解码肯定要采用同一套的统一的标准,如果不统一的时候就会出现乱码。
在实践中开发中常用的编码有如下几种:
- GBK/GB2312:国标编码,可以描述中文信息,其中GB2312只描述简体中文,而GBK包含简体与繁体;
- ISO8859-1:国际通用编码,可以用其描述所有的字母信息,如果是象形文字则需要进行编码处理;
- UNICODE编码:采用十六进制的方式存储,可以描述所有的文字;
- UTF编码:象形文字部分使用十六进制编码,而普通字母采用的是ISO8859-1编码,优势在于快速传输节约带宽,也就成为了开发中首先得编码(“UTF-8”)。
如果要想知道当前系统中支持的编码规则,则可以采用如下代码列出全部本机属性:
列出本机属性
public class Char_Encode {
public static void main(String[] args) {
System.getProperties().list(System.out);
}
}
不设置编码时,使用默认编码进行,可通过强制指定编码。
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
public class Char_Encode {
public static void main(String[] args) throws Exception {
OutputStream output = new FileOutputStream("C:"+File.separator+"mld.txt");
output.write("中国人民万岁".getBytes("ISO8859-1")); //指定编码
}
}
项目中出现的乱码问题就是编码和解码标准的不统一,而最好的解决乱码的方式,所有的编码方式都使用“UTF-8”。
内存操作流
之前使用的全部都是文件操作流,文件操作的特点,程序利用InputStream读取文件内容,之后程序利用OutputStream向文件输出内容,所有操作都是以程序为终端的。
假设现在需要实现IO操作,但是又不希望产生文件(临时文件)则就可以以内存为终端进行出,这个时候的流程如下:
在Java中提供两类内存操作流:
- 字节内存操作流:ByteArrayOutputStream(OutputStream子类)、ByteArrayInputStream(InputStream子类)
- 字符内存操作流:CharArrayWriter、CharArrayReader
下面以ByteArrayOutputStream、ByteArrayInputStream类为主进行内存使用分析:
- ByteArrayOutputStream构造方法:
public ByteArrayOutputStream();
- ByteArrayInputStream构造方法:
public ByteArrayInputStream(byte[] buf);
在ByteArrayOutputStream类中有一个重要的方法,这个方法可以获取全部保存在内存流中的数据信息:
- 获取数据:
public byte[] toByteArray();
- 使用字符串形式来获取:
public String toString();
利用内存流时间小写字母转大写字母
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
public class Memory_IO {
public static void main(String[] args) throws Exception {
String str = "test!";
InputStream input = new ByteArrayInputStream(str.getBytes()); //将数据保存在内存流
OutputStream output = new ByteArrayOutputStream();
int data = 0;
while((data = input.read()) != -1) {
//每次读取一个字节
output.write(Character.toUpperCase((char)data));
}
System.out.println(output);
input.close();
output.close();
}
}
如果现在不希望只是以字符串的形式返回,因为存放的可能是其他二进制数据,那么此时就可以利用ByteArrayOutputStream子类的扩展功能获取全部数据。
获取全部字节数据
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
public class Memory_IO {
public static void main(String[] args) throws Exception {
String str = "test!";
InputStream input = new ByteArrayInputStream(str.getBytes()); //将数据保存在内存流
// 必须使用子类来调用子类自己的扩展方法
ByteArrayOutputStream output = new ByteArrayOutputStream();
int data = 0;
while((data = input.read()) != -1) {
//每次读取一个字节
output.write(Character.toUpperCase((char)data));
}
byte result[] = output.toByteArray(); //获取全部数据
System.out.println(new String(result)); //自己处理字节数据
input.close();
output.close();
}
}
在最初时可以利用ByteArrayOutputStream实现大规模文本文件的读取。
管道流
管道流主要功能是实现两个线程之间的IO操作。
对于管道流也是分为两类:
- 字符管道流:PipedWriter(Writer子类)、PipedReader(Reader子类)
- 连接处理:
public void connect(PipedInputStream snk) throws IOException
- 连接处理:
- 字节管道流:PipedOutputStream(OutputStream子类)、PipedInputStream(InputStream子类)
- 连接处理:
public void connect(PipedReader snk) throws IOException
- 连接处理:
实现管道处理
//代码有问题
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
class SendThread implements Runnable{
private PipedOutputStream output; //管道输出流
public SendThread() {
this.output = new PipedOutputStream(); //实例化管道输出流
}
@Override
public void run() {
for(int x=0; x<10;x++) {
try {
//利用管道实现数据发送处理
this.output.write(("No:"+(x+1)+"-"+Thread.currentThread().getName()+"信息发送\n").getBytes());
} catch (IOException e) {
e.printStackTrace();
}
try {
this.output.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public PipedOutputStream getOutput() {
return output;
}
}
class ReceiveThread implements Runnable{
private PipedInputStream input;
public ReceiveThread() {
this.input = new PipedInputStream();
}
@Override
public void run() {
byte data[] = new byte[1024];
int len = 0;
ByteArrayOutputStream bos = new ByteArrayOutputStream(); //将所有数据保存到内存输出流
try {
while((len = this.input.read(data)) != -1) {
bos.write(data,0,len); //所有数据保存到内存流bos
}
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("{"+ Thread.currentThread().getName() +"接收消息}" + new String(bos.toByteArray()));
try {
this.input.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public PipedInputStream getInput() {
return input;
}
}
public class Piped_IO {
public static void main(String[] args) throws IOException {
SendThread send = new SendThread();
ReceiveThread receive = new ReceiveThread();
send.getOutput().connect(receive.getInput()); //进行管道连接
new Thread(send,"发送线程").start();
new Thread(receive,"接收线程").start();
}
}
管道类似于医院打点滴的效果,一个负责发送另一个负责接收,中间靠管道连接。
RandomAccessFile(随机读取类)
对于文件内容的处理操作主要是通过InputStream(Reader),OutputStream(Writer)来实现,但是利用这些类实现的内容读取只能够将数据部分读取进来,如果现在有这样一种要求:
现在给了一个非常庞大的文件,如果此时按照传统的IO操作进行读取和分析根本就不可能完成,所以这种情况下在java.io包提供有一个RandomAccessFile类,这个类可以实现文件的跳跃式的读取,可以只读取文件部分内容(前提:需要有一个完善的保存形式,即数据的保存位数都要确定好。)
RandomAccessFile类中定义有如下操作方法:
- 构造方法:
public RandomAccessFile(File file,String mode) throws FileNotFoundException;
- 处理模式:r、rw
实现文件保存
import java.io.File;
import java.io.FileNotFoundException;
import java.io.RandomAccessFile;
public class Random_AccessFile {
public static void main(String[] args) throws Exception {
File file = new File("C:\\Project\\Java_study\\src\\文件\\test.txt");
RandomAccessFile raf = new RandomAccessFile(file, "rw");
String names[] = new String[] {
"zhangsan","wangwu ","lisi "};
int ages[] = new int[] {
30,16,20};
for(int x=0;x<names.length;x++) {
raf.write(names[x].getBytes());
raf.writeInt(ages[x]);
}
raf.close();
}
}
RandomAccessFile最大的特点是在于数据的读取处理上,因为所有的数据是按照固定长度进行保存的,所以读取时可以进行跳字节读取:
- 向下跳:
public int skipBytes(int n) throws IOException;
- 向回跳:
public void seek(long pos) throws IOException;
读取数据
import java.io.File;
import java.io.FileNotFoundException;
import java.io.RandomAccessFile;
public class Random_AccessFile {
public static void main(String[] args) throws Exception {
File file = new File("C:\\Project\\Java_study\\src\\文件\\test.txt");
RandomAccessFile raf = new RandomAccessFile(file, "rw");
//跳过24位
{
raf.skipBytes(24);
byte data[] = new byte[8];
int len = raf.read(data);
System.out.println(new String(data,0,len) + " - " + raf.readInt());
}
//回
{
raf.seek(0); //回到顶点
byte data[] = new byte[8];
int len = raf.read(data);
System.out.println(new String(data,0,len) + " - " + raf.readInt());
}
raf.close();
}
}
整体使用中由用户自行定义要读取的位置,而后按照指定结构进行数据的读取。