写在前面
编码知识是每个开发人员都需要掌握的知识 ,乱码问题是开发中常遇到的问题,这一点我深有体会
编码是解决不同语言转换的桥梁
目录
- 字符集编码
- IO相关
- 文件读、写
- FileWriter、FileReader字符节点流组合
- OutputStreamWriter字符处理流、FileOuputStream字节节点流组合输出数据
InputStreamReader字符处理流、FileInputStream字节节点流组合读取数据
- 文件复制
- FileReader、FileWriter组合文件复制
- FileInputStream、FileOutputStream组合文件复制
- BufferReader、BufferWriter组合文件复制
- Buffered缓冲处理流
- 文件读、写
开始
一、字符集编码
参考:菜鸟教程-字符集和字符编码(Charset & Encoding)
参考:百度百科
1.1小结一下课堂上涉及到的部分编码知识
常见的字符编码
-
ACSII码:美国信息标准交换标准代码,主要对接老外,它是现今最通用的单字节编码系统,使用7位表示一个字符共128个字符,等同于国际标准ISO 646
- ASCII扩展码:为了表示欧洲更多常用的字符,使用8位表示一个字符,共256个字符,扩充后的符号增加了表格符号、计算符号、希腊字母和特殊的拉丁符号。
-
ISO-8859码:ISO8859 不是一个标准,而是一系列的标准,这套字符集与编码系统的共同特色是,以同样的码位对应不同字符集。
- 历史上存在两个独立的尝试创立单一字符集的组织,即国际标准化组织(ISO)和多语言软件制造商组成的统一码联盟。前者开发的 ISO/IEC 10646 项目,后者开发的统一码项目。因此最初制定了不同的标准。
- 后来两个组织开始合作合并双方的工作成果
- 从Unicode 2.0开始,Unicode采用了与ISO 10646-1相同的字库和字码;ISO也承诺,ISO 10646将不会替超出U+10FFFF的UCS-4编码赋值,以使得两者保持一致。两个项目仍都存在,并独立地公布各自的标准。
-
ANSI编码:不同的国家和地区制定了不同的标准,由此产生了 GB2312、GBK、Big5、Shift_JIS 等各自的编码标准。这些使用 1 至 4 个字节来代表一个字符的各种汉字延伸编码方式,称为 ANSI 编码
- 每个国家都有一套,需要对应各国的代码页,中国的是936
- 以0开头的8位是一个字符,1开头的两个 8 位 是一个汉字
-
BIG5码
-
GBX系列编码:国标码,主要是对汉字圈国家,
- GB2312码:GB2312是中国国家标准的简体中文字符集。它所收录的汉字已经覆盖99.75%的使用频率,基本满足了汉字的计算机处理需要。在中国大陆和新加坡获广泛使用。对于人名、古汉语等方面出现的罕用字,GB2312不能处理,这导致了后来GBK及GB 18030汉字字符集的出现
- GBK码:GBK是对GB2312-80的扩展,也就是CP936字码表
- GBK 18030:与UTF-8相同,每个字可以由1、2、4个字节组成。汉字收录范围包含繁体汉字以及日韩汉字。
-
Unicode字符集&UTF编码:万国码
像天朝一样,当计算机传到世界各个国家时,为了适合当地语言和字符,设计和实现类似GB232/GBK/GB18030/BIG5的编码方案。这样各搞一套,在本地使用没有问题,一旦出现在网络中,由于不兼容,互相访问就出现了乱码现象。
(可以这样理解:Unicode是字符集,UTF-32/ UTF-16/ UTF-8是三种字符编码方案。)-
UTF-32:对每个字符都使用4字节。就空间而言,是非常没有效率的。就空间而言,是非常没有效率的。
这种方法有其优点,最重要的一点就是可以在常数时间内定位字符串里的第N个字符,因为第N个字符从第4×Nth个字节开始。虽然每一个码位使用固定长定的字节看似方便,它并不如其它Unicode编码使用得广泛。 -
UTF-16:尽管有Unicode字符非常多,但是实际上大多数人不会用到超过前65535个以外的字符。因此,就有了另外一种Unicode编码方式,叫做UTF-16(因为16位 = 2字节)。
-
UTF-8:是一种针对Unicode的可变长度字符编码(定长码),也是一种前缀码。它可以用来表示Unicode标准中的任何字符,且其编码中的第一个字节仍与ASCII兼容,这使得原来处理ASCII字符的软件无须或只须做少部份修改,即可继续使用。因此,它逐渐成为电子邮件、网页及其他存储或传送文字的应用中,优先采用的编码。互联网工程工作小组(IETF)要求所有互联网协议都必须支持UTF-8编码。
UTF-8使用一至四个字节为每个字符编码: -
大头、小头模式
对于UTF-32和UTF-16编码方式还有一些其他不明显的缺点。不同的计算机系统会以不同的顺序保存字节。这意味着字符U+4E2D在 UTF- 16编码方式下可能被保存为4E 2D或者2D 4E,这取决于该系统使用的是大尾端(big-endian)还是小尾端(little-endian)。
为了解决这个问题,多字节的Unicode编码方式定义了一个"字节顺序标记(Byte Order Mark)",它是一个特殊的非打印字符,你可以把它包含在文档的开头来指示你所使用的字节顺序。对于UTF-16,字节顺序标记是U+FEFF。如果收到一个以字节FF FE开头的UTF-16编码的文档,你就能确定它的字节顺序是单向的(one way)的了;如果它以FE FF开头,则可以确定字节顺序反向了。
-
1.2在记事本中占字节大小
- ANSI编码:一个字母占8位,一个汉字占16位
- UTF-8:一个字母应该占1个字节,但是一个字母实际占4个字节,两个字母占5个字节
- Unicode:一个字母占2个字节,一个汉字占2个字节
二、IO相关
目前主要涉及的是简单的同步IO,以后还会涉及异步IO,阻塞IO、NIO等
大致划分
- 字节流
- InputStream
- OutStream
- 字符流
- Reader
- Writer
字符流也是字节流,万物皆字节
也可划分
- 节点流
- 处理流
2.1写、读文件
组合1
- FileWriter字符节点流输出数据
- FileReader字符节点流写入数据
- 根据操作系统的编码格式确定文件的编码格式,输出为Unicode编码格式
- 单个整数的write()写入为ASCII码,字符串的写入为原字符
组合2
- OutputStreamWriter字符处理流、FileOuputStream字节节点流组合输出数据
- InputStreamReader字符处理流、FileInputStream字节节点流组合读取数据
- 可以指定读取文件默认编码格式,
- 为了区分InputStream的read方法的结束返回,返回一个4个字节的-1,读了一个字节,返回4个字节,只有低字节有效区分字符,无符号数据255
- 对于write的write方法,传入一个4字节,但是只是低16位有效
2.1.1FileWriter字符输出流写入
FileWrite为字符输出流,节点流
举栗
单个写入 默认写入的为ASCIi编码的字符
如果想得到原样,写入的时候需要以字符串的形式写入
package four.test;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
public class TestFileReaderAndWriter {
public static void main(String[] args) {
FileWriter fWriter = null;
try {
//创建文件
File file = new File("src\\a.txt");
fWriter = new FileWriter(file);
//写文件
for (int i = 0; i < 100; i++) {
fWriter.write(i + " ");
}
fWriter.close();
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
System.out.println("文件写入错误!");
System.exit(-1);
}
finally {
}
}
}
2.1.2FileReader字符输入流读取文件
字符输入流,节点流
public static void testFileReader() {
File file = new File("src//a.txt");
FileReader fileReader = null;
try {
fileReader = new FileReader(file);
int ln = 0;
while((ln = fileReader.read()) != -1) {
System.out.println(ln);
}
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
2.1.3OutputStreamWriter字符处理流、FileOutputStream字节节点输出流写入文件
需要注意关闭流的顺序,先开的后关,否则报错
public static void testOutputStreamWriter() {
OutputStreamWriter osw = null;
FileOutputStream fos = null;
try {
//创建文件
File file = new File("src\\b.txt");
fos = new FileOutputStream(file);
osw = new OutputStreamWriter(fos);
//写文件
for (int i = 0; i < 100; i++) {
osw.write(i+" ");
}
osw.close();
fos.close();
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
System.out.println("文件写入错误!");
System.exit(-1);
}
finally {
}
}
2.1.4InputStreamReader字符处理流、FileInputStream字节节点流读取文件
大致步骤类似
public static void testInputStreamReader() {
InputStreamReader isr = null;
FileInputStream fis = null;
try {
//创建文件
File file = new File("src\\b.txt");
fis = new FileInputStream(file);
isr = new InputStreamReader(fis);
//读文件
int ln = 0;
while((ln = isr.read()) != -1) {
System.out.print((char)ln );
}
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
System.out.println("文件写入错误!");
System.exit(-1);
}
finally {
}
}
2.2文件复制
2.2.1FileReader、FileWriter组合
使用FileReader、FileWriter来实现文件复制
- 注意仅限于一般的文本文件,文本文件编码为GBK,对于其他识别不了编码的文件,
reader()
读取的过程中遇到未识别的编码就会停止读写。 - 速度很慢,因为是从一个文件中一个个的取出字符,送到另一个文件
import java.io.*;
public class FileCopy {
public static void main(String[] args) {
int b = 0;
FileReader in = null;
FileWriter out = null;
try {
in = new FileReader("d:/share/java/HelloWorld.java");
out = new FileWriter("d:/share/java/io/HW.java");
while((b=in.read())!=-1){
out.write(b);
}
out.close();
in.close();
} catch (FileNotFoundException e2) {
System.out.println("找不到指定文件"); System.exit(-1);
} catch (IOException e1) {
System.out.println("文件复制错误"); System.exit(-1);
}
System.out.println("文件已复制");
}
}
2.2.2 FileInputStream、FileOutputStream组合文件复制
- 对于非文本文件也适合,不会存在解码失败复制失败的问题,因为这是一个字节一个字节的读取、写入
- 速度也是很慢
public static void testFileCopy1() {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
//读取文件
File file1 = new File("src\\b.txt");
fis = new FileInputStream(file1);
//写出文件
File file2 = new File("src\\a.txt");
fos = new FileOutputStream(file2);
int ln = 0;
while((ln = fis.read()) != -1) {
fos.write(ln);
}
fis.close();
fos.close();
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
System.out.println("文件复制错误!");
System.exit(-1);
}
finally {
}
}
public static void testFileCopy2() {
InputStreamReader isr = null;
FileInputStream fis = null;
OutputStreamWriter osw = null;
FileOutputStream fos = null;
try {
//读取文件
File file1 = new File("src\\b.txt");
fis = new FileInputStream(file1);
isr = new InputStreamReader(fis);
//写出文件
File file2 = new File("src\\a.txt");
fos = new FileOutputStream(file2);
osw = new OutputStreamWriter(fos);
int ln = 0;
while((ln = isr.read()) != -1) {
osw.write(ln);
}
isr.close();
fis.close();
osw.close();
fos.close();
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
System.out.println("文件复制错误!");
System.exit(-1);
}
finally {
}
}
2.3Buffer缓冲处理流
- FileInputStream、BufferedInputStream组合读取文件
import java.io.*;
public class TestBufferStream1 {
public static void main(String[] args) {
try {
FileInputStream fis = new FileInputStream("d:\\share\\java\\HelloWorld.java");
BufferedInputStream bis = new BufferedInputStream(fis);
int c = 0;
System.out.println(bis.read());
System.out.println(bis.read());
bis.mark(100);
for(int i=0;i<=10 && (c=bis.read())!=-1;i++){
System.out.print((char)c+" ");
}
System.out.println();
bis.reset();
for(int i=0;i<=10 && (c=bis.read())!=-1;i++){
System.out.print((char)c+" ");
}
bis.close();
} catch (IOException e) {
e.printStackTrace();}
}
}
- BufferedReader、BufferedWriter实现文件内容随机互换
import java.io.*;
public class TestBufferStream2 {
public static void main(String[] args) {
try {
BufferedWriter bw = new BufferedWriter(new FileWriter("d:\\share\\java\\dat2.txt"));
BufferedReader br = new BufferedReader(new FileReader("d:\\share\\java\\dat2.txt"));
String s = null;
for(int i=1;i<=100;i++){
s = String.valueOf(Math.random());
bw.write(s);
bw.newLine();
}
bw.flush();
while((s=br.readLine())!=null){
System.out.println(s);
}
bw.close();
br.close();
} catch (IOException e) {
e.printStackTrace();}
}
}