基础篇:JAVA资源之IO、字节编码、URL和Spring.Resource

目录1 JAVA.IO字节流2 JAVA.IO字符流3 乱码问题和字符流4 字符集和字符编码的概念区分5 URI概念的简单介绍6 URL概念及与URL的区别7 Spring.Resource与Spring资源获取方式8 ResourceLoader 获取资源9 JAVA.Properties了解一下10 yml配置资源的读取11 优雅地关闭资源,try-with-resource语法和lombok@Cleanup12 资源不关闭,会导致什么最坏的结果欢迎指正文中错误关注公众号,一起交流参考文章

1 JAVA.IO字节流

基础篇:JAVA资源之IO、字节编码、URL和Spring.Resource

LineNumberInputStream和StringBufferInputStream官方建议不再使用,推荐使用LineNumberReader和StringReader代替
ByteArrayInputStream和ByteArrayOutputStream

字节数组处理流,在内存中建立一个缓冲区作为流使用,从缓存区读取数据比从存储介质(如磁盘)的速率快

//用ByteArrayOutputStream暂时缓存来自其他渠道的数据
ByteArrayOutputStream data = new ByteArrayOutputStream(1024); //1024字节大小的缓存区
data.write(System.in.read()); // 暂存用户输入数据

//将data转为ByteArrayInputStream
ByteArrayInputStream in = new ByteArrayInputStream(data.toByteArray());

FileInputStream和FileOutputStream

访问文件,把文件作为InputStream,实现对文件的读写操作

ObjectInputStream和ObjectOutputStream

对象流,构造函数需要传入一个流,实现对JAVA对象的读写功能;可用于序列化,而对象需要实现Serializable接口

//java对象的写入
FileOutputStream fileStream = new FileOutputStream("example.txt");
ObjectOutputStream out = new ObjectOutputStream(fileStream);
Example example = new Example();
out.writeObject(example);

//java对象的读取
FileInputStream fileStream = new FileInputStream("example.txt");
ObjectInputStream in = new ObjectInputStream(fileStream);
Example = (Example) in.readObject();

PipedInputStream和PipedOutputStream

管道流,适用在两个线程中传输数据,一个线程通过管道输出流发送数据,另一个线程通过管道输入流读取数据,实现两个线程间的数据通信

// 创建一个发送者对象
Sender sender = new Sender(); // 创建一个接收者对象
Receiver receiver = new Receiver(); // 获取输出管道流
// 获取输入输出管道流
PipedOutputStream outputStream = sender.getOutputStream();
PipedInputStream inputStream = receiver.getInputStream();
// 链接两个管道,这一步很重要,把输入流和输出流联通起来
outputStream.connect(inputStream);
sender.start();// 启动发送者线程
receiver.start();// 启动接收者线程

SequenceInputStream

把多个InputStream合并为一个InputStream,允许应用程序把几个输入流连续地合并起来

InputStream in1 = new FileInputStream("example1.txt");
InputStream in2 = new FileInputStream("example2.txt");
SequenceInputStream sequenceInputStream = new SequenceInputStream(in1, in2);
//数据读取
int data = sequenceInputStream.read();

FilterInputStream和FilterOutputStream 使用了装饰者模式来增加流的额外功能,子类构造参数需要一个InputStream/OutputStream

ByteArrayOutputStream out = new ByteArrayOutputStream(2014);
//数据写入,使用DataOutputStream装饰一个InputStream
//使用InputStream具有对基本数据的处理能力
DataOutputStream dataOut = new DataOutputStream(out);
dataOut.writeDouble(1.0);
//数据读取
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
DataInputStream dataIn = new DataInputStream(in);
Double data = dataIn.readDouble();

DataInputStream和DataOutputStream (Filter流的子类)

为其他流附加处理各种基本类型数据的能力,如byte、int、String

BufferedInputStream和BufferedOutputStream (Filter流的子类)

为其他流增加缓冲功能

PushBackInputStream (FilterInputStream子类)

推回输入流,可以把读取进来的某些数据重新回退到输入流的缓冲区之中

PrintStream (FilterOutputStream子类)

打印流,功能类似System.out.print

2 JAVA.IO字符流

基础篇:JAVA资源之IO、字节编码、URL和Spring.Resource

从字节流和字符流的导向图来,它们之间是相互对应的,比如CharArrayReader和ByteArrayInputStream
字节流和字符流的转化:InputStreamReader可以将InputStream转为Reader,OutputStreamReader可以将OutputStream转为Writer

//InputStream转为Reader
InputStream inputStream = new ByteArrayInputStream("程序".getBytes());
InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
//OutputStream转为Writer
OutputStream out = new FileOutputStream("example.txt");
OutputStreamWriter writer = new OutputStreamWriter(out);
//以字符为单位读写
writer.write(reader.read(new char[2]));

区别:字节流读取单位是字节,字符流读取单位是字符;一个字符由字节组成,如变字长编码UTF-8是由1~4个字节表示

3 乱码问题和字符流

字符以不同的编码表示,它的字节长度(字长)是不一样的。如“程”的utf-8编码格式,由[-25][-88][-117]组成。而ISO_8859_1编码则是单个字节[63]
平时工作对资源的操作都是面向字节流的,然而数据资源根据不同的字节编码转为字节时,它们的内容是不一样,容易造成乱码问题
两种出现乱码场景

encode和decode使用的字符编码不一致:资源使用UTF-8编码,而在代码里却使用GBK解码打开
使用字节流读取字节数不符合字符规定字长:字符是由字节组成的,比如“程”的utf-8格式是三个字节;如果在InputStream里以每两个字节读取流,再转为String(java默认编码是utf-8),此时会出现乱码(半个中文,你猜是什么)

ByteArrayInputStream in = new ByteArrayInputStream("程序大法好".getBytes());
byte[] buf = new byte[2]; //读取流的两个字节
in.read(buf); //读取数据
System.out.println(new String(buf)); //乱码
---result----
� //乱码

乱码场景1,知道资源的字符编码,就可以使用对应的字符编码来解码解决
乱码场景2,可以一次性读取所有字节,再一次性编码处理。但是对于大文件流,这是不现实的,因此有了字符流的出现
字节流使用InputStreamReader、OutputStreamReader转化为字符流,其中可以指定字符编码,再以字符为单位来处理,可解决乱码

InputStreamReader reader =
new InputStreamReader(inputStream, StandardCharsets.UTF_8);

4 字符集和字符编码的概念区分

字符集和字符编码的关系,字符集是规范,字符编码是规范的具体实现;字符集规定了符号和二进制代码值的唯一对应关系,但是没有指定具体的存储方式;
unicode、ASCII、GB2312、GBK都是字符集;其中ASCII、GB2312、GBK既是字符集也是字符编码;注意不混淆这两者区别;而unicode的具体实现有UTF-8,UTF-16,UTF-32
最早出现的ASCII码是使用一个字节(8bit)来规定字符和二进制映射关系,标准ASCII编码规定了128个字符,在英文的世界,是够用的。但是中文,日文等其他文字符号怎么映射呢?因此其他更大的字符集出现了
unicode(统一字符集),早期时它使用2个byte表示1个字符,整个字符集可以容纳65536个字符。然而仍然不够用,于是扩展到4个byte表示一个字符,现支持范围是U+010000~U+10FFFF
unicode是两个字节的说法是错误的;UTF-8是变字长的,需要用14个字节存储;UTF-16一般是两个字节(U+0000U+FFFF范围),如果遇到两个字节存不下,则用4个字节;而UTF-32是固定四个字节
unicode表示的字符,会用“U+”开头,后面跟着十六进制的数字,如“字”的编码就是U+5B57
UTF-8 编码和unicode字符集

范围 Unicode(Binary) UTF-8编码(Binary) UTF-8编码byte长度

U+0000~U+007F
00000000 00000000 00000000 0XXXXXXX
0XXXXXX
1

U+0080~U+07FF
00000000 00000000 00000YYY YYXXXXXX
110YYYYY 10XXXXXX
2

U+0800~U+FFFF
00000000 00000000 ZZZZYYYY YYXXXXXX
1110ZZZZ 10YYYYYY 10XXXXXX
3

U+010000~U+10FFFF
00000000 000AAAZZ ZZZZYYYY YYXXXXXX
11110AAA 10ZZZZZZ 10YYYYYY 10XXXXXX
4

程序是分内码和外码,java的默认编码是UTF-8,其实指的是外码;内码倾向于使用定长码,和内存对齐一个原理,便于处理。外码倾向于使用变长码,变长码将常用字符编为短编码,罕见字符编为长编码,节省存储空间与传输带宽
JDK8的字符串,是使用char[]来存储字符的,char是两个字节大小,其中使用的是UTF-16编码(内码)。而unicode规定的中文字符在U+0000~U+FFFF内,因此使用char(UTF-16编码)存储中文是不会出现乱码的
JDK9后,字符串则使用byte[]数组来存储,因为有一些字符一个char已经存不了,如emoji表情字符,使用字节存储字符串更容易拓展
JDK9,如果字符串的内容都是ISO-8859-1/Latin-1字符(1个字符1字节),则使用ISO-8859-1/Latin-1编码存储字符串,否则使用UTF-16编码存储数组(2或4个字节)

System.out.println(Charset.defaultCharset()); //输出java默认编码
for (byte item : "程序".getBytes(StandardCharsets.UTF_16)) {
System.out.print("[" + item + "]");
}
System.out.println("");
for (byte item : "程序".getBytes(StandardCharsets.UTF_8)) {
System.out.print("[" + item + "]");
}
----result----
UTF-8 //java默认编码UTF-8
[-2][-1][122][11][94][-113] //UTF_16:6个字节?
[-25][-88][-117][-27][-70][-113] //UTF_8:6个字节 正常

“程序”的UTF-16编码竟是输出6个字节,多出了两个字节,这是什么情况?再试试一个字符的输出

for (byte item : "程".getBytes(StandardCharsets.UTF_16)) {
System.out.print("[" + item + "]");
}
---result--
[-2][-1][122][11]

可以看出UTF-16编码的字节是多了[-2][-1]两个字节,十六进制是0xFEFF。而它用来标识编码顺序是Big endian还是Little endian。以字符’中’为例,它的unicode十六进制是4E2D,存储时4E在前,2D在后,就是Big endian;2D在前,4E在后,就是Little endian。FEFF表示存储采用Big endian,FFFE表示使用Little endian
为什么UTF-8没有字节序的问题呢?个人看法,因为UTF-8是变长的,由第一个字节的头部的0、110、1110、11110判断是否需后续几个字节组成字符,使用Big endian易读取处理,反过来不好处理,因此强制用Big endian
其实感觉UTF-16可以强制规定用Big endian;但这其中历史问题。。。

5 URI概念的简单介绍

既然有了java.io来操作资源流;但是对于网络的资源,该怎么打开,怎么定位呢?答URI-URL
URI全称是Uniform Resource Identifier 统一资源标识符
通俗说,就是一个类似身份证号码的字符串,只不过它是用来标识资源(如:邮件地址,主机名,文件等)
URI 具有特定的规则: [scheme]:[scheme-specific-part][#fragment]

进一步细入划分可表示为[scheme]:[//authority][/path][?query][#fragment],其中模式特定部分为authority和path、query;而authority可以看做域名,如www.baidu.com
终极细分则是[scheme]:[//host:port][/path][?query][#fragment],和日常见到的地址链接一毛一样了

模式特定部分(scheme-specific-part)的形式取决于模式,而URI的常用模式如下

ftp:FTP服务器
file:本地磁盘上的文件
http:使用超文本传输协议
mailto:电子邮件的地址
telnet:基于Telnet的服务的连接
Java中还大量使用了一些非标准的定制模式,如rmi、jar、jndi、doc、jdbc等

在java中URI抽象为java.net.URI类,下面列举几种常用构造方法

//根据str生成URI
public URI(String str) throws URISyntaxException
public URI(String scheme, String authority,
String path, String query, String fragment)throws URISyntaxException
public static URI create(String str) //调用 URI(String str)

JAVA.URI的常用操作方法

public String getScheme()    //获取模式
public String getSchemeSpecificPart()//获取模式特定部分
public String getFragment() //获取片段标识符
//以上三个方法是通用的
public String getAuthority() //授权机构,如www.baidu.com
public String getHost() //获取主机部分,如127.0.0.1
public int getPort() //如8080
public String getPath() //定位路径
public String getQuery() //查询条件

6 URL概念及与URL的区别

URL全称是Uniform Resource Location,统一资源定位符
URL就是URI的子集,它除了标识资源,还提供找到资源的路径;在Java类库中,URI类不包含任何访问资源的方法,它唯一的作用就是解析,而URL类可以打开一个到达资源的流
同属URI子集的URN(统一资源名称),只标识资源名称,却不指定如何定位资源;如:mailto:clswcl@gmail.com就是一种URN,知道这是个邮箱,却不知道该怎么查找定位
通俗就是,URN告诉你有一个地方叫广州,但没有说怎么去,你可以搭动车,也可以搭飞机;URL会告诉你坐飞机去广州,而另一URL则说搭动车去
URL的一般语法规则

协议://主机名:端口/路径?查询#片段
[protocol]:[//host:port][/path][?query][#fragment]

URL的构造方法、获取方法

//基于URL模式构造URL实例
public URL(String spec) throws MalformedURLException
//其中file相当于path、query和fragment三个部分组成
public URL(String protocol, String host, int port, String file) throws MalformedURLException

//根据类加载器获取URL
URL systemResource = ClassLoader.getSystemResource(String name)
Enumeration<URL> systemResources = ClassLoader.getSystemResources(String name)
URL resource = Main.class.getResource(String name)
Enumeration<URL> resources = Main.class.getClassLoader().getResources(String name)

通过URL获取资源数据的操作函数

public final InputStream openStream() throws java.io.IOException
public URLConnection openConnection() throws java.io.IOException
public final Object getContent() throws java.io.IOException

7 Spring.Resource与Spring资源获取方式

讲到资源,就得提下Spring获取资源方式,常用的有两种

通过Resource接口的子类获取资源
通过ResourceLoader接口的子类获取资源

Spring.Resource 资源操作函数一览

public interface Resource extends InputStreamSource {
//判断资源是否存在
boolean exists(); //
//返回当前资源对应的URL,不能解析则会抛出异常;如ByteArrayResource就不能解析为一个URL
URL getURL() throws IOException;
//返回当前资源对应的URI
URI getURI() throws IOException;
//返回当前资源对应的File
File getFile() throws IOException;
//返回对应的ReadableByteChannel
default ReadableByteChannel readableChannel() throws IOException

介绍下Resource相关子类的使用
1 FileSystemResource:通过文件系统获取资源

Resource resource = new FileSystemResource("D:/example.txt");
File file= new File("example.txt");
Resource resource2 = new FileSystemResource(file);

2 ByteArrayResource:获取byte数组表示的资源

基于ByteArrayInputStream和字节数组实现,应用场景类似ByteArrayInputStream,缓存byte[]资源

3 ClassPathResource:获取类路径下的资源

public class ClassPathResource extends AbstractFileResolvingResource {
//ClassPathResource.java 的三个属性
private final String path;
//使用Class或ClassLoader加载资源
private ClassLoader classLoader;
private Class<?> clazz;

---使用方式----
Resource resource = new ClassPathResource("test.txt");

4 InputStreamResource:接收一个InputStream对象,获取输入流封装的资源
5 ServletContextResourse:加载ServletContext环境下(相对于Web应用根目录的)路径资源,获取的资源
6 UrlResource:通过URL访问http资源和FTP资源等

8 ResourceLoader 获取资源

基础篇:JAVA资源之IO、字节编码、URL和Spring.Resource

ResourceLoader是为了屏蔽了Resource的具体实现,统一资源的获取方式。你即能从ResourceLoader加载ClassPathResource,也能加载FileSystemResource等

public interface ResourceLoader {
// 默认从类路径加载的资源 前缀: "classpath:",获取ClassPathResource
String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
Resource getResource(String location);

ResourceLoader接口默认对classpath路径下面的资源进行加载

public interface ResourcePatternResolver extends ResourceLoader {
// 默认加载所有路径(包括jar包)下面的文件,"classpath*:", 获取ClassPathResource
String CLASSPATH_ALL_URL_PREFIX = "classpath*:";

ResourcePatternResolver默认会加载所有路径下面的文件,获得ClassPathResource;classpath:只会在class类路径下查找;而classpath*:会扫描所有JAR包及class类路径下出现的文件

//Ant风格表达式  com/smart/**/*.xml
ResourcePatternResoler resolver = new PathMatchingResourcePatternResolver();
Resource resources[] = resolver.getResources("com/smart/**/*.xml");

// ApplicationContext ctx
//FileSystemResource资源
Resource template = ctx.getResource("file:///res.txt");
//UrlResource资源
Resource template = ctx.getResource("https://my.cn/res.txt");

ResourceLoader方法getResource的locationPattern可设置资源模式前缀来获取非ClassPathResource资源,locationPattern支持Ant风格

前缀 示例 描述

classpath:
classpath:config.xml
从类路径加载

file:
file:///res.txt
从文件系统加载FileSystemResource

http:
http://my.cn/res.txt
加载UrlResource

9 JAVA.Properties了解一下

Properties是java自带的配置处理类;Properties加载资源的两种方式

public class Properties extends Hashtable<Object,Object>{
.... //可根据Reader或者InputStream加载properties文件内容
public synchronized void load(Reader reader) throws IOException
public synchronized void load(InputStream inStream) throws IOException

Properties读取配置示例代码

//res.properties
username = root
password = password
-------代码示例-------------
InputStream input = ClassLoader.getSystemResourceAsStream("res.properties");
Properties prop = new Properties();
prop.load(inputStream); //根据inputStream载入资源
String username = prop.getProperty("username");

10 yml配置资源的读取

普通java项目如果需要读取yml可引入jackson-dataformat-yaml,而springboot默认配置支持yml的读取

<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
<version>2.9.5</version>
</dependency>

基于jackson-dataformat-yaml对yml配置资源的读取

//res.yml 配置
name: chen
params:
url: http://www.my.com

----------代码示例---------------
InputStream input = ClassLoader.getSystemResourceAsStream("res.yml");
Yaml yml = new Yaml();
Map map = new Yaml().loadAs(input, LinkedHashMap.class);; //根据inputStream载入资源
String name = MapUtils.getString(map,"name"); // chen
//url: http://www.my.com
String url = MapUtils.getString((Map)map.get("params"),"url")

11 优雅地关闭资源,try-with-resource语法和lombok@Cleanup

资源的打开就需要对应的关闭,但我们常会忘记关闭资源,或在多处代码关闭资源感到杂乱,有没有简洁的关闭方法呢?
自动关闭资源类需实现AutoCloseable接口和配和try-with-resource语法糖使用

public class YSOAPConnection implements AutoCloseable {
private SOAPConnection connection;
public static YSOAPConnection open(SOAPConnectionFactory soapConnectionFactory) throws SOAPException {
YSOAPConnection ySoapConnection = new YSOAPConnection();
SOAPConnection connection = soapConnectionFactory.createConnection();
ySoapConnection.setConnection(connection);
return ySoapConnection;
}
public SOAPMessage call(SOAPMessage request, Object to) throws SOAPException {
return connection.call(request, to);
}
@Override
public void close() throws SOAPException {
if (connection != null) { connection.close(); }
}
}

//自动关闭的资源类使用示例
try (YSOAPConnection soapConnection=YSOAPConnection.open(soapConnectionFactory)){
SOAPMessage soapResponse = soapConnection.call(request, endpoint);
...//数据操作
} catch (Exception e) {
log.error(e.getMessage(), e);
...
}

lombok注解@Cleanup,对象生命周期结束时会调用public void close();对象需实现AutoCloseable接口

import lombok.Cleanup;
@Cleanup // @Cleanup的使用
YSOAPConnection soapConnection=YSOAPConnection.open(soapConnectionFactory)

12 资源不关闭,会导致什么最坏的结果

JDK的原生资源类不关闭,它也不会永远存在。JVM会借助finalize自动关闭它,例如FileInputStream

//FileInputStream.java - JDK8
//jdk8的FileInputStream重写了finalize,保证对象回收前开启的资源被关闭
protected void finalize () throws IOException {
if (guard != null) {
guard.warnIfOpen();
}
if ((fd != null) && (fd != FileDescriptor.in)) {
close();
}
}

在JDK9后,用Cleaner机制代替了finalize机制;Cleaner机制自动回收的对象同样需要实现AutoCloseable接口;Cleaner是基于PhantomReference实现的;对实现细节感兴趣的同学,可自行查阅下相关文档
但是使用JDK的提供的资源关闭机制的,那么资源的关闭比手动关闭时要延后很长时间的。据测试,使用try-with-resources关闭资源,并让垃圾回收器回收它的时间在12纳秒。而使用finalizer机制,时间增加到550纳秒
不及时关闭资源,就会占用资源,影响其他线程的执行;比如linux的文件资源,linux进程默认能打开的最大文件数是1024(有的是2048,此数值是可配置的);如果一个线程持有十几个文件资源,还要等550纳秒用finalizer机制释放资源,同进程的其他线程都等到花谢了

欢迎指正文中错误
关注公众号,一起交流

基础篇:JAVA资源之IO、字节编码、URL和Spring.Resource

参考文章

从源码角度理解Java设计模式——装饰者模式
Java中的管道流
InputStream乱码问题的解决方法
未关闭的文件流会引起内存泄露么?
char类型与字符编码
结合Java详谈字符编码和字符集
Cleaner 对比 finalize 对比 AutoCloseable

原创:https://www.panoramacn.com
源码网提供WordPress源码,帝国CMS源码discuz源码,微信小程序,小说源码,杰奇源码,thinkphp源码,ecshop模板源码,微擎模板源码,dede源码,织梦源码等。

专业搭建小说网站,小说程序,杰奇系列,微信小说系列,app系列小说

基础篇:JAVA资源之IO、字节编码、URL和Spring.Resource

免责声明,若由于商用引起版权纠纷,一切责任均由使用者承担。

您必须遵守我们的协议,如果您下载了该资源行为将被视为对《免责声明》全部内容的认可-> 联系客服 投诉资源
www.panoramacn.com资源全部来自互联网收集,仅供用于学习和交流,请勿用于商业用途。如有侵权、不妥之处,请联系站长并出示版权证明以便删除。 敬请谅解! 侵权删帖/违法举报/投稿等事物联系邮箱:2640602276@qq.com
未经允许不得转载:书荒源码源码网每日更新网站源码模板! » 基础篇:JAVA资源之IO、字节编码、URL和Spring.Resource
关注我们小说电影免费看
关注我们,获取更多的全网素材资源,有趣有料!
120000+人已关注
分享到:
赞(0) 打赏

评论抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

您的打赏就是我分享的动力!

支付宝扫一扫打赏

微信扫一扫打赏