Java学习笔记.docx
- 文档编号:7843984
- 上传时间:2023-01-26
- 格式:DOCX
- 页数:31
- 大小:36.60KB
Java学习笔记.docx
《Java学习笔记.docx》由会员分享,可在线阅读,更多相关《Java学习笔记.docx(31页珍藏版)》请在冰豆网上搜索。
Java学习笔记
资源获取
在开发java程序的过程中,我们经常要做的一件事就是获取资源。
那么什么是资源呢?
说白了,在计算机里那就是一堆数据。
只是这堆数据对我们的java程序有多种表现形式,一般来说有File,URL,InputStream等等。
而单就文件这一项就有很多种:
配置文件,java类文件,jps文件,图片、css、js文件等等。
面对这林林总总的资源,我们在设计一个读取资源的接口时,就需要针对不同形式的资源提供方法,这样就导致我们的接口还是与实际的资源形式绑定在一起,未能完全的抽象。
另外,在java程序中资源的存放位置也是各异的。
有的存放在classpath中,有的存放在文件系统中,有的存放在web应用中。
而对于不同位置的资源,java程序获取这些资源的方法各有不同。
A、获取classpath中的资源:
Java代码
1.URL url = this.getClass().getResource("resource_name");
2.URL url = this.getClass().getClassLoader().getResource("resource_name");
3.URL url = Thread.currentThread().getContextClassLoader().getResource("resource_name");
URLurl=this.getClass().getResource("resource_name");
URLurl=this.getClass().getClassLoader().getResource("resource_name");
URLurl=Thread.currentThread().getContextClassLoader().getResource("resource_name");
那么在jdk中为什么又提供了三种方式来获取classpath下的资源呢?
这其中是有些来头的。
第一行代码中是利用Class类的实例来获取,第二行代码是使用加载当前类的classloader来获取。
看下jdk中的源代码会发现class类的实例最后还是委托加载他的classloader来获取资源的。
Java代码
1.public .URL getResource(String name) {
2. name = resolveName(name);
3. ClassLoader cl = getClassLoader0();
4. if (cl==null) {
5. // A system class.
6. return ClassLoader.getSystemResource(name);
7. }
8. return cl.getResource(name);
9.}
public.URLgetResource(Stringname){
name=resolveName(name);
ClassLoadercl=getClassLoader0();
if(cl==null){
//Asystemclass.
returnClassLoader.getSystemResource(name);
}
returncl.getResource(name);
}
从上面的代码中可以看出,对于资源的加载并没有像类加载所采用的双亲委托机制。
而是当前类的classloader不为null的情况下先从当前类的classloader中加载资源。
而只有当前类的classloader为null的时候才从systemclassloader中去加载资源。
这样可以方便我们自定义配置类覆盖一些默认配置。
当然,j2se应用中如果没有特别定制classloader时,我们自己写的类都是被systemclassloader加载的。
到底利用class去获取资源和利用classloader去获取资源有什么区别呢?
区别就在resolveName(name)这个方法中。
两种方式对于资源名称的表示方式不同。
下面是一个简单的包结构,/表示类路径的根
/
|-.test
|-Test.class
|-test2.txt
|-test1.txt
Java代码
1.// 获取与当前类在同一个包下的资源
2.URL url1 = this.getClass().getResource("test2.txt");
3.// 获取.test包下的资源,需加/
4.URL url2 = this.getClass().getResource("/com/cn/test/test2.txt");
5.// 获取类路径根下的资源
6.URL url3 = this.getClass().getClassLoader().getResource("test1.txt");
7.// 获取包.test包下的资源
8.URL url4 = this.getClass().getResource("com/cn/test/test2.txt");
//获取与当前类在同一个包下的资源
URLurl1=this.getClass().getResource("test2.txt");
//获取.test包下的资源,需加/
URLurl2=this.getClass().getResource("/com/cn/test/test2.txt");
//获取类路径根下的资源
URLurl3=this.getClass().getClassLoader().getResource("test1.txt");
//获取包.test包下的资源
URLurl4=this.getClass().getResource("com/cn/test/test2.txt");
而第三利用当前线程的contextClassLoader来获取资源的解释可以参考我的另一篇
B、获取文件系统中的资源
Java代码
1.// 1、获得File对象
2.File file = new File("test.txt");
3.// 2、获得File对象的字节流
4.InputStream in = new FileInputStream(file);
//1、获得File对象
Filefile=newFile("test.txt");
//2、获得File对象的字节流
InputStreamin=newFileInputStream(file);
值得注意的是在File的构造函数File(Stringname)中的name参数可以是相对路径和绝对路径。
相对路径是相对于System.getProperties("user.dir")的。
C、获取web应用中的资源
Java代码
1.servletContext.getResourceAsStream(resource_name);
servletContext.getResourceAsStream(resource_name);
resource_names为相对于webroot的路径表示。
例如获取web.xml,resource_name表示为"/WEB-INF/web.xml"
面对上面介绍的各种资源表现形式和存放位置,难道java中就没有提供一个统一处理方式吗?
有,.URL。
从名称上来看URL(UniformResourceLocator)统一资源定位器。
看起来很好很强大。
但很多时候使用它并不能定位到我们需要的资源。
首先,它jdk中体统的URL能访问的协议非常有限(当然可以进行扩展,不过很麻烦);常用的有http,file,ftp等等。
并没有提供对classpath和servletContext中的资源的获取方法。
另外,它没有提供判断资源是否存在的方法。
每次只有等我们真正去获取资源的时候抛出异常才能知道资源无法获取。
其次,URL这个类的职责未划分清楚,既用来表示资源有用来获取其资源。
JVM原理学习笔记
(一)
关键字:
java
最近在阅读《InsidetheJVM》这本书,结合一些日常工作学习中的感想,随便写一些东西,蜻蜓点水,不必有章法。
关于“单例同步”:
一直有人在问单例对象的并发调用是否需要同步,基本属于“月经帖”了,答案是现成的满天下都是,但真正能让人心里踏实下来的解释寥寥无几。
实际上,只要学习了一些JVM的运行原理,解释这个问题就不难了。
如果一个类是单例的,比如某些DAO的设计,那么所有的线程来访问这个类的实例的时候,它们获得的都将是同一个对象,这是不言自明的。
如果这些线程的当前操作是“互斥”的,那么每个线程就必须在取得该实例的访问资格的时候为该对象上锁,以独享该对象直到当前操作结束,以免在操作中途被其它线程介入而产生不可预知的结果。
问题是,什么样的操作是“互斥”的呢?
简单地说,互斥操作就是两个操作企图对它俩共享的某个资源进行修改,而修改的结果是不可预知的。
于是问题就变成了,什么才是“共享的资源”?
从纯粹java语法的角度这个问题没法解释,因为它遵循的是当前java虚拟机的规范描述。
现假设两个线程正企图同时访问一个单例对象的方法,如,
Java代码
1.int method1(int i) {
2. int j = 3;
3. return i+j;
4.}
intmethod1(inti){
intj=3;
returni+j;
}
一个规范的虚拟机线程在调用method1()的时候是这样做的:
1)把method1()的局部变量,包括参数,压入当前线程的栈;
2)从当前线程栈弹出变量j,并赋予数值3;
3)从当前线程栈弹出参数i,与j执行加法运算;
4)从当前线程栈中释放当前方法占用的栈帧,并把method1()的结果压入当前线程栈。
需要说明的是,当前线程栈是当前线程独有的,绝对不会被其它线程访问到。
这样,只要你在method1()里面使用的全都是局部变量或参数,那就不需要为多线程的并发调用发愁,因为每个线程都有自己的栈帧,各不相干。
复杂一点,如果method1()是这样定义的:
Java代码
1.int method1(int i, SingletonClass singleObj) {
2. singleObj.intValue ++;
3. int j = i + singleObj.intValue;
4. return j;
5.}
intmethod1(inti,SingletonClasssingleObj){
singleObj.intValue++;
intj=i+singleObj.intValue;
returnj;
}
这下我们就不得不考虑线程同步问题了,这个方法显然包含了一个互斥的操作“singleObj.intValue++;”。
前面说过,方法的参数会被压入当前线程私有的栈直到方法结束,但这里要注意的是,singleObj只是一个引用地址而非真正的对象实例,因此,尽管singleObj这个引用值是被压入线程私有栈去的,但真正的对象实例却是在堆里存放的,栈虽然是线程私有的,堆却是所有线程共享的,因此singleObj的成员变量intValue是完全有可能在当前线程执行第二行代码前被其它线程修改了的。
比如说,线程1调用mothod1()的时候singleObj.intValue的值是1,i的值是2,那么正确的情况下,method1()的返回值应该是4。
但当线程1和线程2几乎同时调用method1(),线程2恰好在线程1把intValue变成2之后的一瞬间又执行了一次singleObj.intValue++,由于singleObj是单例,两个线程遇到的singleObj是同一个对象,因此这次运算将把intValue变成3。
接下来线程1继续第二行代码,结果j的结果变成了i+3=2+3=5。
如此一来,线程1调用method1()的返回结果究竟会是4还是5是无法确定的,只能凭运气,寄望线程2在线程1从调用method1()到取得返回值之间的这段时间打盹。
在绝大多数情况下,这种“凭运气”的做法是不能接受的,我们需要向线程1保证,在它调用method1()期间绝不会收到线程2的干扰。
做法如下:
Java代码
1.int method1(int i, SingletonClass singleObj) {
2. int j = 0;
3. synchronize(singleObj) {
4. singleObj.intValue ++;
5. j = i + singleObj.intValue;
6. }
7. return j;
8.}
intmethod1(inti,SingletonClasssingleObj){
intj=0;
synchronize(singleObj){
singleObj.intValue++;
j=i+singleObj.intValue;
}
returnj;
}
这个写法仍然有缺陷,因为线程2很可能在线程1执行intj=0的时候修改singleObj的intValue,所以比较可靠的应该在调用method1()之前锁住singleObj:
Java代码
1.synchronize(singleObj) {
2. int result = obj.method1(2, singleObj);
3.}
synchronize(singleObj){
intresult=obj.method1(2,singleObj);
}
小小总结一下,“一个方法如果涉及对某个共享对象(或堆对象)的写操作,那么它必须同步该对象”这个说法在大多数情况下都对,但还有些失之笼统,或许这样说比较准确些,“如果一个方法对某共享对象的写操作会造成其它线程返回值的不确定性,则该方法应该同步该对象。
”
更正:
本文出现的书名应该是《InsidetheJVM》,之前误写作《DeepIntoJVM》了,感谢fantasybei网友提出来。
本书是Java世界的经典著作,有兴趣的网友可以用书名在网上找到一大堆资料,其中文译名是《深入Java虚拟机》
JVM原理学习笔记
(二)——虚拟机规范
最近在阅读《InsidetheJVM》这本书,结合一些日常工作学习中的感想,随便写一些东西,蜻蜓点水,不必有章法。
曾经很在意C++和Java之间的优劣比较,有一段时间尤其注意在网上搜索二者比较的文章,并不时参加一些口水战,比如下面这个帖子:
在论坛里绝对是个口水飞溅潜力帖。
现在想起来很好笑,其实Java跟C++几乎是不同领域的东西,它们之所以存在是因为各自领域的需要,比较一下有助于C++程序员转移到Java去或相反,但优劣之说只能误人子弟。
帖子的作者显然不懂虚拟机规范,因此会有所谓“骗局”一说,这里无意驳斥这个作者早已无从考证的老文章,仅就虚拟机规范说几句。
《InsidetheJVM》一开始就指出,所谓“虚拟机”实际上在不同的语境下有不同的涵义。
有时候它指的是虚拟机的“规范”(spec),有时候指的是虚拟机的具体实现(如SunJDK,BEAJRockit),有时候指的是正在运行着的一个虚拟机的实例(你启动Tomcat或者JBoss,甚至一个j2se程序,都同时启动了一个虚拟机实例)。
书中如无特别说明,指的一般都是“规范”。
虚拟机规范规定了.class文件的格式、类装载的规则、运行时内存的逻辑区块、方法调用时栈的动作等等。
一旦某个虚拟机的具体实现(如OpenJDK)声称它实现了Java虚拟机规范,那么也就是同时声称它在运行时的外部行为跟规范中所描述的是一样的。
对于Java程序员而言,他的“平台”只有一个,就是虚拟机规范,只要他的.class编译完,无论到哪一个虚拟机上,OpenJDK也好,JRockit也好,SunJDK也好,甚至芯片级实现的JVM也好,都应该能够正常运行。
这就是Java跨平台的真正涵义(当然,实际项目中,100%的跨平台项目是很少的,比如有些没有完全遵守规范写出的bug,在这个虚拟机上运行的时候或许能糊弄过去,但在另一个虚拟机上却未必能够)。
帖子作者认为Java的跨平台是个“设计巧妙的骗局”,实在是言重了,我们不能天真地认为,“跨平台”就可以没有平台,事实上无论怎样我们总需要至少一个的。
此外,即使是“跨平台”也是相对的,一个平台因为没有JVM的实现而“跨”不上去,太正常了,世上哪有什么绝对的事。
JVM原理学习笔记(三)——类的初始化
最近在阅读《InsidetheJVM》这本书,结合一些日常工作学习中的感想,随便写一些东西,蜻蜓点水,不必有章法。
类的初始化工作,主要是将静态变量、常量初始化为“正确”的值(也就是程序员希望设定的特定值而非其类型的默认值),以及其它一些需要在初始化类的时候需要做的工作(如读取配置文件等)。
通常我们可以这样做:
Java代码
1.class A extends B {
2. public static int intVal = 30;
3. public static String strVal;
4.
5. static {
6. strVal = readConfig("ItemA");
7. }
8.
9. private static String readConfig(String key) {
10. ....
11. }
12.}
classAextendsB{
publicstaticintintVal=30;
publicstaticStringstrVal;
static{
strVal=readConfig("ItemA");
}
privatestaticStringreadConfig(Stringkey){
....
}
}
当一个类被加载,它将顺序经历四个过程:
验证、准备、解析、初始化(一个类被加载以前,如果其父类尚未初始化,那么JVM会先去用以上四个过程对这个父类进行初始化)。
验证只是检查class文件是否符合java语义并且不会损害JVM的完整性,这里不多赘述。
准备阶段是为类成员分配内存,同时根据该成员的类型赋给它相应的默认值,对于上面的示例类A,经过准备阶段后状态是这样的:
intVal=0;
strVal=null;
解析是把符号引用转为直接引用的过程,比如,原来JVM只知道类A有一个成员叫"intVal",通过解析则知道了该成员的地址是0xBBEDF,以后再使用这个成员的时候,就直接去这个内存地址去找了。
同时在这个阶段,类的方法比如上面的readConfig()也会被映射到某个内存地址以待调用。
初始化则是利用类定义的JAVA代码确定成员变量的初值(如果对某个成员没有相应的java代码对其进行初始赋值操作,那么它就沿用在准备阶段获得的该类型默认值)。
在这个阶段,所有的静态代码都会被执行,事实上,类在编译时编译器是会把这些静态代码封装到一个
这个方法程序员是不能调用的,它只能被JVM调用。
以上对JVM初始化一个类的过程做了一些讲解,但是JVM究竟什么时候才会初始化一个类呢?
总的来说,JVM会在“主动”使用一个类的时候将该类初始化。
所谓“主动”,大致有6种已知的行为,对我们比较常见的是:
1)试图创建该类的一个新实例;2)调用该类声明的一个静态方法;3)使用类中声明的非常量静态字段。
考虑下面的例子:
Java代码
1.public interface Angry {
2. String greeting = "Grrrr!
";
3. int angerLevel = Dog.getAngerLevel();
4.}
5.
6.public class Dog implements Angry {
7. public static final String greeting = "Wong, Wong, Wong!
";
8.
9. static {
10. System.out.println("Dog was initialized.");
11. }
12.
13. public static int getAngerLevel() {
14. System.out.println("Angry was initialized.");
15. return 1;
16. }
17.}
18.
19.public class Main {
20. public static void main(String[] args) throws Exception {
21. testClassInit();
22. }
23.
24. public static void testClassInit() throws Exception {
25.
26. //passive use of Angry
27. System.out.println(Angry.greeting);
28.
29. //passive use of Do
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Java 学习 笔记