首页 > 编程语言 > Java基础字符编码与内存流详细解读
2022
05-20

Java基础字符编码与内存流详细解读

1、字符编码

1.1 常用字符编码

在计算机的世界之中,所有的显示文字都是按照其指定的数字编码进行保存的,如果没有正确的解码,那么就坑你产生乱码,如果要想清楚解决乱码问题,就要了解经常见到一些常见的编码:

GBK/GBK2312:表示国标中文编码,其中GBK是包含简体中文和繁体中文,而GB2312只有简体;

ISO 8859-1:是一种国际通用编码,可以表示任何文字,但是对于中国文字需要进行转码;

UNICODE:使用十六进制完成的编码,可以准确的表示出任何的语言文字;

UTF-8:部分编码使用UNICODE,而一些编码继续使用像ISO 8859-1,类型的编码,适合于网络传输,在以后的所有的项目开发之中,都必须采用此编码。可是考虑到日后学习的方便,几乎都会使用命令行进行操作,所以命令行只支持GBK编码,UTF不支持,一旦程序设置了UTF编码,那么通过命令行查看就是乱码。

在开发之中经常会遇见乱码的问题,所谓的乱码的核心在于编码和解码不统一。如果要想正确的避免项目之中出现的乱码,那么首先就应该知道环境之中所支持的编码是什么。

1.2 乱码产生分析

读取Java运行属性

package com.day15.demo;

public class ListDemo {
	public static void main(String[] args) {
		System.getProperties().list(System.out);
	}
}

这个时候显示出来的信息是很多的,这里面有专门的编码选项“file.encoding=GBK”,也就是说如果没有任何的意外,所有的文字编码都是GBK。

改变编码

package com.day15.demo;

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;

public class ListDemo {
	public static void main(String[] args) throws Exception{
		OutputStream out = new FileOutputStream(new File("f:" + File.separator + "test" + File.separator + "hello.txt"),true);
		out.write("世界和平".getBytes());
		out.close();
	}
}

2、内存流基本操作

在讲解之前首先来思考一个问题:就是如果现在某个操作必须发生IO,但有不希望有一些临时文件产生的话,那么现在肯定无法使用之前的文件操作流,所以为了解决这样的问题,提供了内存操作流,即:以内存进行操作的终端,以发生IO操作关系。

image-20210819132612577

对于内存操作流也分为两组:

**字节内存操作流:**内存输入流(ByteArrayInputStream)内存输出流(ByteArrayOutputStream)

**字符内存操作流:**内存输入流(CharArrayReader)内存输出流(CharArrayWriter)

image-20210819132728511

ByteArrayInputStream ByteArrayOutputStreamjava.lang.Object java.io.InputStream java.io.ByteArrayInputStreamjava.lang.Object java.io.OutputStream java.io.ByteArrayOutputStreampublic ByteArrayInputStream(byte[] buf)public ByteArrayOutputStream()

输出流

image-20210819133020134

输入流

image-20210819133247011

通过内存实现大小写转换的操作

package com.day15.demo;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;

import com.sun.xml.internal.messaging.saaj.util.ByteOutputStream;

public class MemoryDemo {
	public static void main(String[] args) throws Exception{
		String str = "Hello,World!";
		InputStream in = new ByteArrayInputStream(str.getBytes());
		OutputStream out = new ByteOutputStream();
		int temp = 0;
		while((temp = in.read())!=-1){
			out.write(Character.toUpperCase(temp));
		}
		in.close();
		out.close();
		System.out.println(out.toString());
	}
}

此过程我们发现没有文件的产生,而此过程只不过是产生了临时文件。

3、打印流

如果说想在要想输出数据,肯定使用OuputStream或者是Writer,那么请问,这两个操作类在执行输出的时候你认为它好用吗?

打印流主要解决的就是OutputStream缺陷,属于OutputStream加强版,如果现在操作不是二进制的数据,只是通过程序向终端目标输出信息。OutputStream并不方便。

缺点一:所有的数据必须变为字节数组

缺点二:只支持String类型,输出int、double就不方便

如果现在要想输出字符串,使用Writer可以直接输出,而使用OutputStream还需要将字符串变为字节数组,那么如果现在要想输出数字(int型或double型),还需要将这些数据先变为字符串,之后再变为字节数组输出,多以,如果用户直接调用OutputStream或Writer输出的时候本身并不方便,所以在这个时候可以想办法将OutputStream或Writer变得加强一些,定义一个专门的工具类:PrintUtil.java。

编写一个输出功能类PrintUtil类

package com.day15.demo;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.OutputStream;

class PrintUtil{
	OutputStream out;
	public PrintUtil(OutputStream out){
		this.out=out;
	}
	public void print(String str) throws Exception{
		this.out.write(str.getBytes());
	}
	public void println(String str) throws Exception{
		this.print(str.concat("\r\n"));
	}
	public void print(int num) throws Exception{
		this.print(String.valueOf(num));
	}
	public void println(int num) throws Exception{
		this.println(String.valueOf(num));
	}
}
public class PintUtilDemo {
	public static void main(String[] args) throws Exception {
		PrintUtil printUtil = new PrintUtil(new FileOutputStream(new File("f:" + File.separator + "test" + File.separator + "test.txt")));
		printUtil.println("姓名:" + "张麻子");
		printUtil.print("年龄");
		printUtil.print(19);
	}
}

以后使用PrintWriter的使用率挺高,但是这两者的使用形式相同的。首先观察这两个类的继承结构和构造方法:

首先来观察一下PrintStream,PrintWriter的继承结构和构造方法:

PrintStream PrintWriter
java.lang.Object
java.io.OutputStream
java.io.FilterOutputStream
java.io.PrintStream
java.lang.Object
java.io.Writer
java.io.PrintWriter
public PrintStream(OuputStream out) public PrintWriter(Writer out)

image-20210819145012602

看见以上的结构,可能第一反应就属于代理设计模式,但它并不是代理,代理设计模式的特点:以接口为使用原则,用户调用代理主题方法的时候依然是接口之中定义的方法。而此时PrintStream类调用的绝对不是OutputStream类之中定义的一系列write()方法。虽然PrintStream在外表操作上产生了变化,但实际上依然执行的是OutputStream累哦所定义的操作,所以本质没有发生变化,只是提供了一些更加方便的功能支持,多以这种设计模式上讲称为装饰设计模式。

3.1 格式化文本信息

在JDK1.5之后,打印流也进行了更新,增加了一个新的方法,格式化输出:格式化输出:public PrintStream printf(String format,Object… args)

当看到此方法名称的时候首先想到的是C语言中的输出,而现在Java也具备了同样的功能,而输出的时候可以使用一些标记表示要输出的内容,例如:字符串(%s),数字(%d)小数(%m.nf),字符(%c)等。

观察格式化输出

package com.day15.demo;

import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintWriter;

public class PrintWriterDemo {
	public static void main(String[] args) throws Exception{
		PrintWriter pu = new PrintWriter(new FileOutputStream(new File("f:" + File.separator + "test" + File.separator + "test.txt")),true);
		String name = "张麻子";
		int age=23;
		double score=8123219.127456;
		pu.printf("姓名:%s 年龄:%d 成绩:%7.2f",name,age,score);

	}
}

而在JDK1.5之后增加字符串格式化操作类不光有PrintStream,还有String类,String类也提供了一个格式化字符串的操作方法:public static String format(String format,Object… args)

格式化字符串

package com.day15.demo;

import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintWriter;

public class PrintWriterDemo {
	public static void main(String[] args) throws Exception{
		PrintWriter pu = new PrintWriter(new FileOutputStream(new File("f:" + File.separator + "test" + File.separator + "test.txt")),true);
		String name = "张麻子";
		int age=23;
		double score=8123219.127456;
		String str = String.format("姓名:%s 年龄:%d 成绩:%7.2f",name,age,score);
		System.out.println(str);
	}
}

虽然格式化字符串可以执行准确的四舍五入操作,但是这种处理完的数据都是String型,而实际工作中,如果要四舍五入,肯定还是要编写BigDecimal类完成。

以后只要是程序输出数据的操作,都使用PrintStream类。

4、 System类

在我们学习完了PrintWriter、PrintStream之后我们会发现里面的方法都很熟悉,例如print()、println()输出就利用我们的IO流模式完成的。在System类中实际上定义有三个操作的常量。

image-20210819183205994

public static final PrintStream out 标准输出
public static final PrintStream err 错误输出
public static final InputStream in 标准输出

4.1 系统输出

系统输出我们发现一共有两个常量:out、err,而且这两个常量所表示的都是PrintSream的对象。从

Java设计的本质上来讲这样的输出有以下设计的目的。out是希望输出的用户可以看见的内容

,err是希望输出用户不能够看见的内容。

package com.day15.demo;

public class PrintDemo {
	public static void main(String[] args) throws Exception{
		try{
			Integer.valueOf("abc");
			}catch(Exception e){
				System.err.println(e);
				System.out.println(e);
			}

	}
}

/*
java.lang.NumberFormatException: For input string: "abc"
java.lang.NumberFormatException: For input string: "abc"
*/

4.2 系统输出

系统输出是将所有的信息输出到指定的输出设备上——显示器。而System.out本身是属于PrintStream对象,而PrintStream是OutputStream子类,所以现在实际上可以利用System.out为OutputStream类执行实例化操作。

package com.day15.demo;

import java.io.OutputStream;
public class PrintDemo {
	public static void main(String[] args) throws Exception {
		String str ="hello,world";
		OutputStream output = System.out;//System.out为OutputStream实例化
		output.write(str.getBytes());
	}
}

本程序没有任何的意义,而讲解的主要目的就希望可以理解:OutputStream会根据实例化它的子类或对象的不同,输出的位置也不同。

4.3 系统输入

系统输入针对于标准的输入设备——键盘,也就是俗称的键盘输入数据,但是System.in返回的是InputStream型的数据,所以下面编写一个操作由键盘输入数据。

package com.day15.demo;

import java.io.IOException;
import java.io.InputStream;

public class inDemo {
	public static void main(String[] args) throws Exception {
		InputStream input = System.in;
		System.out.println("请输入!");
		byte data[] = new byte[1024];
		int len = input.read(data);
		System.out.println(new String(data,0,len));
	}
}

除了实例化InputStream类的对象不同之外,其他的地方和之前文件输入数据没有任何区别,但是这个程序本身有问题,已经开辟的空间大小是1024,如果输入的数据超过1024呢?发现只会接收满足指定长度的数据,程序有bug,那么最好的解决方法是不设置长度,输入一个读取一个,一直到用户不输入为止。

package com.day15.demo;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

public class inDemo {
	public static void main(String[] args) throws Exception {
		InputStream input = System.in;//为父类实例化
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		byte data[] = new byte[10];//开辟一个空间
		System.out.println("请输入!");
		int temp = 0;
		while((temp = input.read(data))!=-1){//数据读取到字节数
			//这里面要用户自己来处理换行问题
			bos.write(data,0,temp);//保存在内存输出流
			if(temp < data.length){
				break;
			}
		}
		System.out.println(new String(bos.toByteArray()));
	}
}

简化操作,但是中文无法识别

package com.day15.demo;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

public class inDemo {
	public static void main(String[] args) throws Exception {
		InputStream input = System.in;//为父类实例化
		StringBuffer buf = new StringBuffer();
		System.out.println("请输入!");
		int temp = 0;
		while((temp = input.read())!=-1){//数据读取到字节数
			//这里面要用户自己来处理换行问题
			if(temp == '\n'){
				break;
			}
			buf.append((char)temp);
		}
		System.out.println(buf);
	}
}

通过以上比较可以感受到System.in的支持度原本不高,对于英文的操作是支持,但是对于中文是不太友好的,对于中文的输出还必须借助内存流来实现的。

5、BufferedReader类

BufferedReader属于一个缓冲的输入流,而且是一个字符流的操作对象。但是必须清楚一点就是对于我们的缓存流定义有两类:字节缓冲流( BufferedInputStream )、字节缓冲流( BufferedReader )。

如果说想在把所有的输入数据放在一起了,一次性读取出来,那么这个时候肯定就能够避免中文问题了,而这一操作就必须依靠缓冲区操作流完成。对于缓冲区的读取在IO包中定义了两种类:BufferedInputStream,BufferedReader,但是考虑到本次操作有中文的问题,肯定使用BufferedReader类完成操作。下面就需要观察一下BufferedReader类的继承结构,构造方法,操作方法:

继承结构: java.lang.Object
java.io.Reader
java.io.BuffereedReader
构造方法: public BuffereedReader(Reader in)
读取操作: public String readLine() throws IOException

之所以选择BufferReader类操作提供的readLine()方法,这个方法可以读取一行数据以回车为准。

image-20210819191629741

使用BufferedReader进行数据读取

package com.day15.demo;

import java.io.BufferedReader;
import java.io.InputStreamReader;

public class BufferReaderDemo {
	public static void main(String[] args) throws Exception{
		BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
		System.out.println("请输入");
		//默认的换行模式是BufferReader最大的缺点
		String str = bufr.readLine();//接受输入信息,默认使用回车换行
		System.out.println(str);
	}
}

对输入的数据进行验证,判断是否是数字

package com.day15.demo;

import java.io.BufferedReader;
import java.io.InputStreamReader;

public class BufferReaderDemo {
	public static void main(String[] args) throws Exception{
		BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
		System.out.println("请输入年龄");
		//默认的换行模式是BufferReader最大的缺点
		String str = bufr.readLine();//接受输入信息,默认使用回车换行
		if(str.matches("\\d{1,3}"))
			System.out.println(str);
		else
			System.out.println("输入数据有误!");
	}
}

6、Scanner

这个类是作为了一个工具类出现的,在Scanner之中定义两个如下的一些方法:

public Scanner(InputStream sourse); 构造方法
public Boolean hasNextXxx(); 判断是否有数据
public 数据类型 nextXxx(); 取得数据
public Scanner useDelimiter(String partern); 定义分隔符

以后调用的时候在执行nextXxx()之前一定要首先使用hasNextXxx()判断是否有指定格式的数据出现。

通过Scanner类进行数据的输入

package com.day15.demo;

import java.util.Scanner;

public class ScannerDemo {
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		System.out.println("请输入数据:");
		if(sc.hasNext()){//现在有输入的内容,不能判断空字符串
			System.out.println(sc.next());
		}
		sc.close();
	}
}

使用Scanner类判断输入数据是否是int型数据

package com.day15.demo;

import java.util.Scanner;

public class ScannerDemo {
	public static void main(String[] args) {
		System.out.println("请输入数据:");
		Scanner sca=new Scanner(System.in);
		if(sca.hasNextInt()){
			int date=sca.nextInt();
			System.out.println("输入的数据是:"+date);
		}else{
			System.out.println("输入的不是数字");
		}
	}
}

在Scaner类之中,useDelimiter()方法的输入针对于字符串,但是其他的数据类型并不方便使用。

使用Scanner类判断用户输入的是不是生日

package com.day15.demo;

import java.util.Scanner;

public class ScannerDemo {
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		System.out.println("请输入生日:");
		if(sc.hasNext("\\d{4}-\\d{2}-\\d{2}")){//现在有输入的内容,不能判断空字符串
			String bir = sc.next("\\d{4}-\\d{2}-\\d{2}");
			System.out.println(bir);
		}
		sc.close();
	}
}

Scanner读取文件内容

package com.day15.demo;

import java.io.File;
import java.io.FileInputStream;
import java.util.Scanner;

public class ScannerDemo {
	public static void main(String[] args) throws Exception{
		Scanner sc = new Scanner(new FileInputStream(new File("f:" + File.separator + "test" + File.separator + "hello.txt")));
		sc.useDelimiter("\n");
		if(sc.hasNext()){//现在有输入的内容,不能判断空字符串
			System.out.println(sc.next());
		}
		sc.close();
	}
}

除了二进制文件拷贝处理之外,只要针对于程序的信息输出都使用打印流,对于信息的输入都是Scanner。

7、对象序列化

所有的项目都一定有序列化的概念。

7.1 对象序列化的概念

所谓的对象序列化是指在内存中保存的对象变为二进制流的形式进行传输,或者将其保存在文本中。但是我们并不意味着所有对象都可以被序列化,严格来讲,我们需要被实例化的类对象往往需要传输使用,同时这个类 必须实现java.io.Serializable接口。但是这个接口没有任何方法定义,所以只是一个标识。

package com.day15.demo;

import java.io.Serializable;

class Person implements Serializable{
	private String name;
	private int age;
	public Person(String name, int age) {
		this.name = name;
		this.age = age;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
}
public class SerializableDemo {

}

序列化对象是所需要保存的就是对象中的属性所以默认情况下对象的属性将被转为二进制数据流存储。

7.2 实现序列化和反序列化

如果要想进行对象的序列化和反序列话的手工操作,在java之中提提供了两个操作类:ObjectOutputStream,ObjectInputStream,而这两个类的继承结构,构造方法,操作方法定义如下:

ObjectOutputStream ObjectInputStream
java.lang.Object
java.io.OutputStream
java.io.ObjectOutputStream
java.lang.Object
java.io.InputStream
java.io.ObjectInputStream
public ObjectOutputStream(OutputStream out)
throws IOException
public ObjectInputStream(InputStream in)
throws IOException
public final void writeObject(Object obj)
throws IOException
public final Object readObject()
throws IOException,ClassNotFoundException

image-20210819210231615

image-20210819210310589

实现对象的序列化操作

package com.day15.demo;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Arrays;

class Person implements Serializable{
	private String name;
	private int age;
	public Person(String name, int age) {
		this.name = name;
		this.age = age;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	@Override
	public String toString() {
		return "Person [name=" + name + ", age=" + age + "]";
	}
}
public class SerializableDemo{
	public static final File FILE = new File("F:" + File.separator + "test" + File.separator + "person.txt");
	public static void ser(Object o) throws Exception {
		ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(FILE));
		outputStream.writeObject(o);
		outputStream.close();
	}
	public static void dser() throws Exception {
		ObjectInputStream inputStream = new	ObjectInputStream(new FileInputStream(FILE));
		System.out.println(inputStream.readObject());
		inputStream.close();
	}
	public static void main(String[] args) throws Exception{
        //序列化
		//ser(new Person("张麻子",20));
        //反序列化
		dser();
	}
}

如果出现com.day15.demo.Person@6d311334这个情况的主要原因是因为实体类没有进行toString()方法的重写。

7.3 transient关键字(了解)

实际上序列化的处理在Java.io有两类,Serializable是使用最多的序列化接口,这种操作采用自动化的模式完成,默认情况下所有的属性都会进行序列化。有一个Externalizable接口需要用户手动序列化处理。

由于默认情况Serializable会将对象中的所有属性进行保存,但是如果现在有某些属性不希望被保存了,那么可以使用transient关键字。

使用transient

package com.day15.demo;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Arrays;

class Person implements Serializable{
	private transient String name;
	private int age;
	public Person(String name, int age) {
		this.name = name;
		this.age = age;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	@Override
	public String toString() {
		return "Person [name=" + name + ", age=" + age + "]";
	}
}
public class SerializableDemo{
	public static final File FILE = new File("F:" + File.separator + "test" + File.separator + "person.txt");
	public static void ser(Object o) throws Exception {
		ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(FILE));
		outputStream.writeObject(o);
		outputStream.close();
	}
	public static void dser() throws Exception {
		ObjectInputStream inputStream = new	ObjectInputStream(new FileInputStream(FILE));
		System.out.println(inputStream.readObject());
		inputStream.close();
	}
	public static void main(String[] args) throws Exception{
		ser(new Person("张麻子",20));
		dser();
	}
}
/*
Person [name=null, age=20]
*/

发现此处name没有进行序列化操作。使用序列化往往在简单java类上使用,其他类上使用序列化的使用很少,但是在简单java类中基本上不去使用transient。

到此这篇关于Day15基础不牢地动山摇-Java基础的文章就介绍到这了,更多相关Java基础内容请搜索自学编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持自学编程网!

编程技巧