Java基础学习18之字符编码、内存流、打印流、System类

538 阅读9分钟

Java基础学习18之字符编码、内存流、打印流、System类

「这是我参与11月更文挑战的第18天,活动详情查看:2021最后一次更文挑战」。

关于作者

  • 作者介绍

🍓 博客主页:作者主页
🍓 简介:JAVA领域优质创作者🥇、一名在校大三学生🎓、在校期间参加各种省赛、国赛,斩获一系列荣誉🏆。
🍓 关注我:关注我学习资料、文档下载统统都有,每日定时更新文章,励志做一名JAVA资深程序猿👨‍💻。


字符编码

常用字符编码

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

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

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

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

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

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

乱码产生分析

读取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();
	}
}

内存流基本操作

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

image-20210819132612577

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

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

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

image-20210819132728511

ByteArrayInputStreamByteArrayOutputStream
java.lang.Object
java.io.InputStream
java.io.ByteArrayInputStream
java.lang.Object
java.io.OutputStream
java.io.ByteArrayOutputStream
public 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());
	}
}

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

打印流

如果说想在要想输出数据,肯定使用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的继承结构和构造方法:

PrintStreamPrintWriter
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累哦所定义的操作,所以本质没有发生变化,只是提供了一些更加方便的功能支持,多以这种设计模式上讲称为装饰设计模式。

格式化文本信息

在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类。

System类

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

image-20210819183205994

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

系统输出

系统输出我们发现一共有两个常量: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"
*/

系统输出

系统输出是将所有的信息输出到指定的输出设备上——显示器。而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会根据实例化它的子类或对象的不同,输出的位置也不同。

系统输入

系统输入针对于标准的输入设备——键盘,也就是俗称的键盘输入数据,但是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的支持度原本不高,对于英文的操作是支持,但是对于中文是不太友好的,对于中文的输出还必须借助内存流来实现的。