`

【zz】Java编码的理解和Java加载器的理解

 
阅读更多

一,我对java中编码的理解
1. 编码的产生  
  对电脑而言,只认识0,1; 而现实世界是由各种符合组成,要想让计算机解释现实世界,就必须建立一套现实世界中的符号 和 计算机能处理的符号之间的对应关系,这个对应关系就是编码  

2. 在一个编辑器中,当我们在键盘上敲入一个字符时,在该编辑器上就会显示对应的字符,这个过程用计算机执行步骤来解释大致如下:  
  输入字符 –> 编辑器根据设定的编码格式把字符编成01格式 -> 编辑器再按编码规则对01解码–> 显示字符  

3.几种常见的编码格式  
1. ASCII码:   
  计算机中最早的一套编码格式,采用7位二进制表示一个常见的字符,我们知道,计算机是按照字节来处理数据的,一个字节8位,因此用一个字节就可以表示一个ASCII字符,且还有一个位空位,规定最高位不用,常见的把最高位设定为0。 7位二进制最多可以表示128个字符(2的7次方),ASCII码只能表示常见的英文字符,数字,和少量的符号(没办法,谁让计算机是人家老美先发明的啊,优先考虑本土语言,理解理解)  
注: 由于ASCII最早定义,使用广泛,使得之后出现的新的”字符“(不是汉字喔)编码都尽量和它兼容  

2. iso-8859-1:  
  尽管ASCII应用广泛,但是由于它定义的字符太少,即使对于同是使用字母语言的国家也不够用,更别说我们汉字啦,西欧许多国家使用拉丁语言,其中的很多字符ASCII编码都不认识喔,这当然会让它们不爽啦,总不能因为这个问题让那些国家的人不使用电脑吧, 于是ISO/IEC就推出了另外的一套编码标准ISO-8859-1. 这套标准完全和ASCII兼容,它使用8位二进制表示一个字符 —--- 刚好一个字节,其中最高位是0时的解释和ASCII一样(这样不就完全兼容啦,兼容很好理解吧),但最高位是1时则用于定义其它字符,这样就在保证和ASCII兼容的同时又扩展了ASCII,可以多表示字符啦  

3. GBK编码  
  光有字母语言国家的编码就可以么?, 可以啊,好好学习英语啊, 要不你看不懂啊,不想学?,那就不玩啊,呵呵,当然不会啦,对于中国,韩国,日本等这些国家,推出了一种新的编码GBK码, 在编码上GBK采用单双字节混合的方式,它也兼容ASCII喔,具体GBK怎么个编码,你查看资料啦, 这里重点关注的是编码的演化,提供的是思路哈  

4. Unicode  
  Unicode是为了打破这种编码的各自为政,带有强烈地域性的标示而推出的一种编码格式,目的是要达到世界的大同,大同好啊,方便交流啊,就是晚了点,计算机一出现就出来多好,就没了现在让程序员头疼的编码转来转去的麻烦了,恩,看来有界限的人心是会带来麻烦的,好了,啰嗦了这么多,才算真正引入正题,为什么会出现乱码,如何解决乱码  
    
4. 揭示web中乱码的病因  
  比如说现在有两个人,张三 和 make分别来自中国和美国,现在他们访问同一台j2ee服务器,他们都向服务器发送了一条消息  
  张三: 嗨,我是张三  
  Make: hello, I’m make  
  假如张三发送的信息采用的编码格式是GBK, make的是ASCII, 对于浏览器而言,留言器没有把发送的信息采用的编码格式告诉给web服务器(之前的浏览器没提供这个功能,又是地域性的见识啊,现在最新版的不知道有没有),web服务器不知道你发过来的信息编码格式啊,怎么解释呢,没办法只好提供个默认的吧,恩还是用iso-8859-1吧(毕竟java也是老美搞出的啊,没办法),英语的没问题,web服务器能听懂,毕竟大家都说英语嘛,并且make对web服务器而言,懂得字母还少了点呢; 汉语的就不行了,就好比一个人只懂汉语,另一个人只懂英语,怎么交流啊,可以交流?那就只好”~!@#¥%“, 这就是java中乱码的病因吧  

5. 如何避免乱码的产生  
  知道了病因,对症下药就好啦,   
  1. 让浏览器明确指定信息的编码格式, 这个不行啦,程序员干涉不了啦,这不是你的问题啦,不知道现在的浏览器是否已提供这种机制啊,请知道的朋友明示:?  
  2. 明确采用GBK编码处理请求的数据啊  
  3. 因为一般的浏览器可以解析多种编码格式,我们的web响应内容,最好指定编码格式喔,这个大家都知道啦  
  4. 不知道那些国外的站点是如何处理中文的,请大师们开始啊  

二、我对java中类装载的理解 

1.Java中的所有类,必须被装载到jvm中才能运行,这个装载工作是由jvm中的类装载器完成的,  
类装载器所做的工作实质是把类文件从硬盘读取到内存中  

2.java中的类大致分为三种:  
  1.系统类  
  2.扩展类  
  3.由程序员自定义的类  

3.类装载方式,有两种  
  1.隐式装载, 程序在运行过程中当碰到通过new 等方式生成对象时,隐式调用类装载器加载对应的类到jvm中,  
  2.显式装载, 通过class.forname()等方法,显式加载需要的类  
  隐式加载与显式加载的区别:  
  两者本质是一样?,  
  ?  

4.类加载的动态性体现  
  一个应用程序总是由n多个类组成,Java程序启动时,并不是一次把所有的类全部加载后再  
运行,它总是先把保证程序运行的基础类一次性加载到jvm中,其它类等到jvm用到的时候再加载,这样的好处是节省了内存的开销,因为java最早就是为嵌入式系统而设计的,内存宝贵,这是一种可以理解的机制,而用到时再加载这也是java动态性的一种体现  

5.java类装载器  
  Java中的类装载器实质上也是类,功能是把类载入jvm中,值得注意的是jvm的类装载器并不是一个,而是三个,层次结构如下:  
  Bootstrap Loader - 负责加载系统类  
  |  
  - - ExtClassLoader - 负责加载扩展类  
  |  
  - - AppClassLoader - 负责加载应用类  
  为什么要有三个类加载器,一方面是分工,各自负责各自的区块,另一方面为了实现委托模型,下面会谈到该模型
 
6. 类加载器之间是如何协调工作的  
  前面说了,java中有三个类加载器,问题就来了,碰到一个类需要加载时,它们之间是如何协调工作的,即java是如何区分一个类该由哪个类加载器来完成呢。  
在这里java采用了委托模型机制,这个机制简单来讲,就是“类装载器有载入类的需求时,会先请示其Parent使用其搜索路径帮忙载入,如果Parent 找不到,那么才由自己依照自己的搜索路径搜索类”,注意喔,这句话具有递归性  
下面举一个例子来说明,为了更好的理解,先弄清楚几行代码:  
Public class Test{  
  Public static void main(String[] arg){  
  ClassLoader c = Test.class.getClassLoader(); //获取Test类的类加载器  
  System.out.println(c);   
  ClassLoader c1 = c.getParent(); //获取c这个类加载器的父类加载器  
  System.out.println(c1);  
  ClassLoader c2 = c1.getParent();//获取c1这个类加载器的父类加载器  
  System.out.println(c2);  
  }  
 
把以上代码存到d:\my 文件夹下,直接编译,然后在dos模式下运行  
D:\my\java Test  
  。。。AppClassLoader。。。  
  。。。ExtClassLoader。。。  
  Null  

D:\my  

注: 。。。表示省略了内容  
可以看出Test是由AppClassLoader加载器加载的  
AppClassLoader的Parent 加载器是 ExtClassLoader  

但是ExtClassLoader的Parent为 null 是怎么回事呵,朋友们留意的话,前面有提到Bootstrap Loader是用C++语言写的,依java的观点来看,逻辑上并不存在Bootstrap Loader的类实体,所以在java程序代码里试图打印出其内容时,我们就会看到输出为null  
【注:以下内容大部分引用java深度历险】  
弄明白了上面的示例,接下来直接进入类装载的委托模型实例,写两个文件,如下:  
文件:Test1.java  
Public class Test1{  
  Public static void main(String[] arg){  
  System.out.println(Test1.class.getClassLoader());  
  Test2 t2 = new Test2();  
  T2.print();  
  }  
 

文件: Test2.java  
Public class Test2{  
  Public void prin(){  
  System.out.println(this.getClass().getClassLoader());  
  }  
 

这两个类的作用就是打印出载入它们的类装载器是谁, 将这两个文件保存到d:\my目录下,编译后,我们在复制两份,分别置于 <JRE所在目录>\classes下(注意,刚开始我们的系统下没有此目录,需自己建立) 与 <JRE所在目录>\lib\ext\classes下(同样注意,开始我们的系统下也没此目录,手工建立), 然后切换到d:\my目录下开始测试,  

测试一:  
<JRE所在目录>\classes下  
Test1.class  
Test2.class  

<JRE所在目录>\lib\ext\classes下  
Test1.class  
Test2.class  

D:\my下  
Test1.class  
Test2.class  


dos下输入运行命令,结果如下:  
D:\my>java Test1  
Null  
Null  

D:\my>  
    
  从输出结果我们可以看出,当AppClassLoader要载入Test1.class时,先请其Parent,也就是ExtClassLoader来载入,而ExtclassLoader又请求其Parent,即Bootstrap Loader来载入Test1.class. 由于 <JRE所在目录>\Classes目录为Bootstrap Loader的搜索路径之一,所以Bootstrap Loader找到了Test1.class,因此将它载入,接着在Test1.class之内有载入Test2.class的需求,由于Test1.class是由Bootstrap Loader所载入,所以Test2.class内定是由Bootstrap Loader根据其搜索路径来找,因Test2.class也位于Bootstrap Loader可以找到的路径下,所以也被载入了,最后我们看到Test1.class与Test2.class都是由Bootstrap Loader(null)载入。  


测试二:  
<JRE所在目录>\classes下  
Test1.class  

<JRE所在目录>\lib\ext\classes下  
Test1.class  
Test2.class  

D:\my下  
Test1.class  
Test2.class  

dos下输入运行命令,结果如下:  
D:\my>java Test1  
Null  
Exception in thread “main” java.lang.NoClassdefFoundError:Test2 at Test1.main。。。  
D:\my>  

  从输出结果我们可以看出,当AppClassLoader要载入Test1.class时,先请其Parent,也就是ExtClassLoader来载入,而ExtclassLoader又请求其Parent,即Bootstrap Loader来载入Test1.class. 由于 <JRE所在目录>\Classes目录为Bootstrap Loader的搜索路径之一,所以Bootstrap Loader找到了Test1.class,因此将它载入,接着在Test1.class之内有载入Test2.class的需求,由于Test1.class是由Bootstrap Loader所载入,所以Test2.class内定是由Bootstrap Loader根据其搜索路径来找,但是因为Bootstrap Loader根本找不到Test2.class(被我们删除了),而Bootstrap Loader又没有Parent,所以无法载入Test2.class.最后我们看到Test1.class是由Bootstrap Loader(null)载入,而Test2.class则无法载入  


测试三  
<JRE所在目录>\classes下  

Test2.class  

<JRE所在目录>\lib\ext\classes下  
Test1.class  
Test2.class  

D:\my下  
Test1.class  
Test2.class  

dos下输入运行命令,结果如下:  
D:\my>java Test1  
。。。ExtClassLoader。。。  
Null  

D:\my>  

  从输出结果我们可以看出,当AppClassLoader要载入Test1.class时,先请其Parent,也就是ExtClassLoader来载入,而ExtclassLoader又请求其Parent,即Bootstrap Loader来载入Test1.class.但是Bootstrap Loader无法在其搜索路径下找到Test1.class(被我们删掉了),所以ExtClassLoader只得自己搜索,因此ExtClassLoader在其搜索路径 <JRE所在目录>\lib\ext\classes下找到了Test1.class,因此将它载入,接着在Test1.class之内有载入Test2.class的需求,由于Test1.class是由ExtClassLoader所载入,所以Test2.class内定是由ExtClassLoader根据其搜索路径来找,但是因为ExtClassLoader有Parent,所以先由Bootstrap Loader帮忙寻找,Test2.class位于Bootstrap Loader可以找到的路径下,所以被Bootstrap Loader载入了.最后我们看到Test1.class是由ExtClassLoader载入,而Test2.class则是由Bootstrap Loader(null)载入  

  了解了以上规则,请朋友们自行分析以下场景的执行结果  

测试四:  
<JRE所在目录>\classes下  


<JRE所在目录>\lib\ext\classes下  
Test1.class  
Test2.class  

D:\my下  
Test1.class  
Test2.class  


测试五:  
<JRE所在目录>\classes下  


<JRE所在目录>\lib\ext\classes下  
Test1.class  


D:\my下  
Test1.class  
Test2.class  


测试六:  
<JRE所在目录>\classes下  


<JRE所在目录>\lib\ext\classes下  

Test2.class  


D:\my下  
Test1.class  
Test2.class  


测试七:  
<JRE所在目录>\classes下  


<JRE所在目录>\lib\ext\classes下  


D:\my下  
Test1.class  
Test2.class  

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics