├── 5.5步-2 ├── 5.5步-3 ├── README.md ├── 第4步 ├── 第5.5步 ├── 第5步 ├── 第6步 ├── 第7步 ├── 第8步 └── 路线图.txt /5.5步-2: -------------------------------------------------------------------------------- 1 | 5.5-2 这时候应该已经涉及了Java的垃圾回收。要留意即使有垃圾回收的情况下也会发生的内存泄露(如自己设计数组容器,元素是引用,逻辑上删除了元素, 2 | 但并没有清成null)。注意垃圾回收只能回收内存中的对象,除了内存以外,其它资源不能依靠垃圾回收来关闭。比如,文件、管道、Socket、数据库连接等, 3 | 垃圾回收是不会帮你关闭的。 4 | 本章内容主要是内存管理和垃圾回收机制参考,参考资料主要为《深入理解java虚拟机》的前三章和部分《java高手真经》。 5 | 垃圾回收的内容过多,在5.5步中仅写入关于虚拟机内存的内容,在5.5-2中写入垃圾回收的内容。 6 | 一、概述 7 | 首先程序计数器、虚拟机栈、本地方法栈3个区域随线程而生,随线程而灭(敲黑板、划重点啦) 8 | 一般而言栈针随着方法的进入和退出而进行着进栈、出栈动作,每个栈针分配多少内存在类结构确定以后就已知的了,所以程序计数器、虚拟机栈、本地方法栈 9 | 的内存分配和回收都具有确定性,因此方法结束(嗯,方法)或者线程结束时内存也就跟着回收了。 10 | 但是java堆和方法区则不然 11 | 一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样,我们只有在程序处于运行期间才能知道创建了多少对象, 12 | 这部分内存的分配和回收是动态的,垃圾收集器所处理回收的也是对象所占的内存。 13 | 二、如何确定对象已经死了 14 | (1)引用计数法 15 | 嗯,简单理想的方法,但是几乎没有虚拟机会用这种方法判断对象是否已死。 16 | 给对象添加一个引用计数器,每当有一个地方引用它,计数器加1,引用失效时计数器减1,当计数器值为0时此对象不再使用,实现很简单,效率很高。 17 | 但是有一个致命缺点,它很难解决对象之间相互循环引用的问题。 18 | public Object instance=null; 19 | private static final int_1MB=1024*1024; 20 | /** 21 | *这个成员属性的唯一意义就是占点内存,以便能在GC日志中看清楚是否被回收过 22 | */ 23 | private byte[]bigSize=new byte[2*_1MB]; 24 | public static void testGC(){ 25 | ReferenceCountingGC objA=new ReferenceCountingGC(); 26 | ReferenceCountingGC objB=new ReferenceCountingGC(); 27 | objA.instance=objB; 28 | objB.instance=objA; 29 | objA=null; 30 | objB=null; 31 | //假设在这行发生GC,objA和objB是否能被回收? 32 | System.gc(); 33 | }} 34 | 对象objA和objB都有字段instance,赋值令objA.instance=objB及objB.instance=objA,除此之外,这两个对象再无任何引用,实际上这两个对象已经 35 | 不可能再被访问,但是它们因为互相引用着对方,导致它们的引用计数都不为0,于是引用计数算法无法通知GC收集器回收它们。 36 | (2)可达性分析算法 37 | 主流的判断对象是否死亡的方法。 38 | 基本思想为通过一系列称为“GC Roots”的对象(敲黑板,5.5中提到过:垃圾回收器对象)作为起始点,从这些节点向下探索,搜索走过的路径被称为引用链 39 | (Reference Chain)(敲黑板,5.5中也提到过),当一个对象到GC Roots没有任何引用链相连,即对于该对象,GC Roots是不可达的,那么这个对象就是 40 | 不可用的,需要回收的。 41 | 在java中可作为GC Roots的有: 42 | (a)虚拟机栈(栈针中的本地变量表)中引用的对象 43 | (b)方法区中类静态属性引用的对象 44 | (c)方法区中的常量引用的对象 45 | (d)本地方法栈中native方法引用的对象 46 | 三、再谈谈引用 47 | “引用”这块这个地址写的内容挺好的:http://www.cnblogs.com/yw-ah/p/5830458.html 48 | 上面一直说的例如“常量引用的对象”到底是什么意思,每个名词都明白但连起来就不明白,归根到底动词“引用”没有深入的了解 49 | 引用分为四种:强引用、软引用、弱引用、虚引用4种,引用强度降序排列。 50 | (a)强引用在程序代码中经常使用,object obj = new object(); 51 | 而这样 obj对象对后面new Object的一个强引用,只有当obj这个引用被释放之后,对象才会被释放掉,这也是我们经常所用到的编码形式。 52 | 当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。 53 | 如果不使用时,要通过如下方式来弱化引用,如下:obj = null;显式地设置obj为null,或超出对象的生命周期范围,则gc认为该对象不 54 | 存在引用,这时就可以回收这个对象。具体什么时候收集这要取决于gc的算法。 55 | (b)软引用 56 | 用来描述一些还有用但并非必需的对象,对于软引用关联的对象,在系统将要发生内存溢出异常之前(敲黑板,此时内存已经不够了, 57 | 要抛出内存溢出异常啦,回收这部分内存),此时将对象列入回收范围中进行第二次回收。 58 | 如果回收之后内存还不够,就会抛出内存溢出异常。采用SoftReference类来实现软引用。 59 | Object obj = new Object(); 60 | SoftReference sf = new SoftReference(obj); 61 | obj = null; 62 | sf.get();//有时候会返回null 63 | 这时候sf是对obj的一个软引用,通过sf.get()方法可以取到这个对象,当然,当这个对象被标记为需要回收的对象时,则返回null; 64 | 软引用主要用户实现类似缓存的功能,在内存足够的情况下直接通过软引用取值,无需从繁忙的真实来源查询数据,提升速度; 65 | 当内存不足时,自动删除这部分缓存数据,从真正的来源查询这些数据。 66 | (c)弱引用 67 | 用来描述非必须对象,它强度比软引用要弱一些,被弱引用关联的对象只能生存到下一次垃圾收集之前,当垃圾收集器工作时无论当前内存是否够用 68 | 被弱引用关联的对象都要被回收。使用WeakReference类来实现弱引用。 69 | Object obj = new Object(); 70 | WeakReference wf = new WeakReference(obj); 71 | obj = null; 72 | wf.get();//有时候会返回null 73 | wf.isEnQueued();//返回是否被垃圾回收器标记为即将回收的垃圾 74 | 弱引用是在第二次垃圾回收时回收,短时间内通过弱引用取对应的数据,可以取到,当执行过第二次垃圾回收时,将返回null。 75 | 弱引用主要用于监控对象是否已经被垃圾回收器标记为即将回收的垃圾,可以通过弱引用的isEnQueued方法返回对象是否被垃圾回收器标记。 76 | (d)虚引用 77 | 最弱的一种引用关系,一个对象是否有虚引用对其一点影响都没有,不会对其生存空间产生影响,也无法通过虚引用得到对象实例,为一个对象 78 | 设置虚引用的目的是能在这个对象被回收时收到系统通知。 79 | 提供了PhantomReference类来实现虚引用 80 | ReferenceQueue queue = new ReferenceQueue (); 81 | PhantomReference pr = new PhantomReference (object, queue); 82 | 四、回收方法区 83 | 其实很多人认为方法区是没有垃圾回收的,因为方法区是所有线程共享的区域,存储着类信息,常量,静态变量等等,即编译后的代码、数据等。 84 | 也因此方法区的回收效率很低,回收了也回收不了太大的空间。 85 | 一般来说方法区回收的主要包括两部分内容:废弃的常量和无用的类。回收废弃的常量与回收堆中的对象差不多,比如说如果当前系统中没有 86 | 一个String对象叫做“abc”的,即没有一个String对象引用常量池中的“abc”常量,其他地方也没有引用到这个常量,垃圾回收时如果有必要,这个“abc”常量 87 | 会被清除出常量池。 88 | 但是需要注意的是判断一个常量为废弃常量很简单,没有对象引用它即可,但是判断类是否是无用的类则十分地苛刻。需要同时满足下面3个条件才行。 89 | (a)该类的所有实例都被回收,即java堆中不存在该类的实例(对象)。 90 | (b)加载该类的ClassLoader已经被回收 91 | (c)该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。 92 | 所以说回收类的条件十分地苛刻。 93 | 五、垃圾回收算法 94 | (1)标记-清除算法 95 | 算法分为两个阶段:“标记”和“清除”。首先标记处所有需要被回收的对象,标记完成后统一回收所有被标记的对象。标记过程就是之前的可达性分析算法中 96 | 判定与GC roots之间没有引用链连接的对象。 97 | 这个方法是最基本的算法,后面的算法都基于它,但是这个算法有两个明显地不足:(a)第一个是效率问题,标记过程和清除过程效率都不高(b)另一个是空间 98 | 问题,标记清除后悔产生大量不连续的内存碎片,空间碎片过多会导致后续为某一对象分配较大内存的时候没有足够大的连续的(嗯,重点,连续的)内存空间 99 | 从而触发另一次垃圾回收。 100 | (2)复制算法 101 | 它将可用内存按容量划分为大小相等的两块,每次只使用一块,当这一块内存使用完了,就将还活着的对象复制到另一块上,然后把已用的那一块内存空间 102 | 清除掉,这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单, 103 | 运行高效。 104 | 但是这种方法代价巨大:本质可用的内存为总内存的一半,嗯,代价不小。 105 | 现在的商业虚拟机都采用这种收集算法来回收新生代(嗯,新生代和老年代,需要注意一下),IBM公司的专门研究表明,新生代中的对象98%是“朝生夕死”的, 106 | 所以并不需要按照1:1的比例来划分内存空间,而是将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor。 107 | 当回收时,将Eden和Survivor中还存活着的对象一次性地复制到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间。HotSpot虚拟 108 | 机默认Eden和Survivor的大小比例是8:1,也就是每次新生代中可用内存空间为整个新生代容量的90%(80%+10%),只有10%的内存会被“浪费”。 109 | (3)标记-整理算法 110 | 复制收集算法在对象存活率较高的情况下效率并不高,因为其需要较多的复制操作,更关键的 111 | 是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中 112 | 所有对象都100%存活的极端情况,所以在老年代中一般不能直接使用复制算法 113 | 根据老年代的特点,有人提出了标记-整理算法,标记过程与“标记-清除”中的标记过程一致,标记完后不是直接对可回收对象进行清理,而是让所有存活的 114 | 对象都向一端移动,然后清理掉端边界以外的内存 115 | (4)分代-收集算法 116 | 没什么新意,将java堆分块,一般分为新生代和老年代,因为新生代每次垃圾回收时都会有大批对象死去,对于这部分采用复制算法,对于老年代,因为对象 117 | 生存率高,没有额外空间担保,所以采用“标记-整理”或者“标记-清除”算法。 118 | (5)新生代和老年代 119 | 在 Java 中,堆被划分成两个不同的区域:新生代 ( Young )、老年代 ( Old )。新生代 ( Young ) 又被划分为三个 120 | 区域:Eden、From Survivor、To Survivor。 121 | --------------------- ---------- 122 | | eden | from | to | | | 123 | | | | | | | 124 | | 8/10 |1/10 | 1/10| | | 125 | 新生代: 堆的1/3 老年代:堆的2/3 126 | 127 | 新生代:新生代几乎是所有 Java 对象出生的地方,即 Java 对象申请的内存以及存放都是在这个地方。Java 中 128 | 的大部分对象通常不需长久存活,具有朝生夕灭的性质。 129 | 老年代:年代里面的对象几乎个个都是在 Survivor 区域中熬过来的,它们是不会那么容易就 "死掉" 了的。 130 | java的堆是GC收集垃圾的主要区域,GC分为两种:Minor GC、Full GC ( 或称为 Major GC )。 131 | Minor GC 是发生在新生代中的垃圾收集动作,所采用的是复制算法。 132 | Full GC 是发生在老年代的垃圾收集动作,所采用的是标记-清除算法或者标记-整理算法 133 | 因为老年代中的对象不那么容易死去,因此Full GC发生的次数不会像Minor GC那样频繁,而且做一次Full GC所花时间比Minor GC要长。 134 | 除此以外,如果老年代中采用标记-清除算法,垃圾回收后会留下大量不连续的内存碎片,如果在给某个对象分配内存时没有足够大的空间,那么 135 | 会引发又一次的垃圾回收。 136 | 137 | 六、HotSpot虚拟机中垃圾判定的算法实现。 138 | (1)枚举根节点 139 | 从可达性分析中从GC Roots节点找引用链这个操作为例,一般可作为GC Roots的节点主要在全局性应用(例如常量和类静态属性)与执行上下文(栈针中 140 | 的本地变量表)中,但是如今很多应用仅方法区就几百兆,逐个检查里面的索引必然消耗很多的时间。 141 | 另外,可达性分析对执行时间的敏感还体现在GC停顿上,在整个可达性分析期间,整个执行系统必须看起来像是被冻结在某个时间点上,不可以出现分析 142 | 过程中对象引用关系还在不断变化的情况,这个要求不满足的话无法保证分析结果的准确性。 143 | 因为这个原因,在GC过程中必须停顿所有java执行线程。 144 | 目前主流的java虚拟机使用的都是准确式GC,在整个系统停顿期间,并不需要一个不漏地检查完所有全局性应用和执行上下文的引用位置,虚拟机有办法 145 | 直接得知哪些地方存放着对象引用。 146 | 在HotSpot中用一个名为OopMap的数据结构来达到这个目的。在类加载完成的时候(类的加载:虚拟机把描述类的数据从class文件加载到内存,并对数据 147 | 进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。)HotSpot就把对象内什么偏移量上什么类型的 148 | 数据计算出来,在JIT编译过程中,也会在特定的位置记录下栈和寄存器中哪些位置是引用。这样,GC在扫描时就可以直接得知这些信息了。 149 | OopMap例子: 150 | 可以看到在0x026eb7a9处的call指令有OopMap记录,它指明了EBX寄存器和栈中偏移量为16的内存区域中各有一个普通对象指针(Ordinary Object 151 | Pointer)的引用,有效范围为从call指令开始直到0x026eb730(指令流的起始位置)+142(OopMap记录的偏移量)=0x026eb7be,即hlt指令为止。 152 | (2)安全点 153 | 在OopMap的协助下,HotSpot可以快速且准确地完成GC Roots枚举,但是有一个问题:说OopMap内容变化的指令非常多,如果为每一 154 | 条指令都生成对应的OopMap,那将会需要大量的额外空间,这样GC的空间成本将会变得很高。 155 | HotSpot也的确没有为每条指令都生成OopMap,前面已经提到,只是在“特定的位置”记录了这些信息,这些位置称为安全点(Safepoint),即程序 156 | 执行时并非在所有地方都能停顿下来开始GC,只有在到达安全点时才能暂停。Safepoint的选定既不能太少以致于让GC等待时间太长,也不能过于频 157 | 繁以致于过分增大运行时的负荷。所以,安全点的选定基本上是以程序“是否具有让程序长时间执行的特征”为标准进行选定 158 | 159 | 对于Sefepoint,另一个需要考虑的问题是如何在GC发生时让所有线程(这里不包括执行JNI调用的线程)都“跑”到最近的安全点上再停顿下来。 160 | 两种方法::抢先式中断(Preemptive Suspension)和主动式中断(Voluntary Suspension),其中抢先式中断不需要线程的执行代码主动去配合, 161 | 在GC发生时,首先把所有线程全部中断,如果发现有线程中断的地方不在安全点上,就恢复线程,让它“跑”到安全点上。现在几乎没有虚拟机实现采用抢 162 | 先式中断来暂停线程从而响应GC事件。而主动式中断的思想是当GC需要中断线程的时候,不直接对线程操作,仅仅简单地设置一个标志,各个线程执行时 163 | 主动去轮询这个标志,发现中断标志为真时就自己中断挂起。轮询标志的地方和安全点是重合的,另外再加上创建对象需要分配内存的地方 164 | (3)安全区域 165 | Safepoint机制保证了程序执行时,在不太长的时间内就会遇到可进入GC的Safepoint。但是,程序“不执行”的时候呢?所谓的程序不执行就是没有分配 166 | CPU时间,典型的例子就是线程处于Sleep状态或者Blocked状态,这时候线程无法响应JVM的中断请求,“走”到安全的地方去中断挂起,JVM也显然不太 167 | 可能等待线程重新被分配CPU时间。对于这种情况,就需要安全区域(Safe Region)来解决。安全区域是指在一段代码片段之中,引用关系不会发生 168 | 变化。在这个区域中的任意地方开始GC都是安全的。我们也可以把Safe Region看做是被扩展了的Safepoint。 169 | 七、垃圾收集器 170 | 各种垃圾收集器指标 171 | 八、内存分配与回收策略 172 | 对象的内存分配,往大方向讲,就是在堆上分配。对象主要分配在新生代的Eden区上,如果启动了本地线程分配缓冲,将按线程优先在TLAB上分配。 173 | 少数情况下也可能会直接分配在老年代中,具体分配情况根据实际的虚拟机而定。 174 | 175 | 下面几条内存分配原则: 176 | (1)对象优先在Eden分配 177 | (2)大对象直接进入老年代 178 | 所谓的大对象是指,需要大量连续内存空间的Java对象,最典型的大对象就是那种很长的字符串以及数组,大对象对虚拟机 179 | 的内存分配来说就是一个坏消息,经常出现大对象容易导致内存还有不少空间时就提前触发垃圾收集以获取足够的连续空间来“安置”它们。 180 | (3)长期存活的对象将进入老年代 181 | 分代收集的思想 182 | (4)动态对象年龄判定 183 | 为了能更好地适应不同程序的内存状况,虚拟机并不是永远地要求对象的年龄必须达到了MaxTenuringThreshold才能晋升老年代, 184 | 如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等 185 | 到MaxTenuringThreshold中要求的年龄。 186 | 九、空间分配担保 187 | 在发生Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那么Minor GC可以确保 188 | 是安全的。如果不成立,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。如果允许,那么会继续检查老年代最大可用的连续 189 | 空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次Minor GC,尽管这次Minor GC是有风险的;如果小于,或 190 | 者HandlePromotionFailure设置不允许冒险,那这时也要改为进行一次Full GC。 191 | 192 | 风险:新生代使用复制收集算法,但为了内存利用率,只使用其中一个Survivor空间来作为轮换备份,因此当出现大量对象在Minor GC后仍然存活的 193 | 情况(最极端的情况就是内存回收后新生代中所有对象都存活),就需要老年代进行分配担保,把Survivor无法容纳的对象直接进入老年代 194 | 195 | 196 | -------------------------------------------------------------------------------- /5.5步-3: -------------------------------------------------------------------------------- 1 | 对象与对象引用的区别,注意垃圾回收。 2 | 这部分相当重要,之前一直弄混,如今找到一个好的文档,借此梳理。 3 | 网址:http://blog.sina.com.cn/s/blog_4cd5d2bb0100ve9r.html 4 | 内容: 5 | 首先定义一个简单的类 6 | class Vehicle { 7 | int passengers; 8 | int fuelcap; 9 | int mpg; 10 | } 11 | 然后一般创建对象都有这句代码: Vehicle veh1 = new Vehicle(); 12 | 按照网址的内容所述,这一句代码包含4个动作 13 | 1)右边的“new Vehicle”,是以Vehicle类为模板,在堆空间里创建一个Vehicle类对象(也简称为Vehicle对象)。 14 | 2)末尾的()意味着,在对象创建后,立即调用Vehicle类的构造函数,对刚生成的对象进行初始化。构造函数是肯定有的。 15 | 如果你没写,Java会给你补上一个默认的构造函数。 16 | 3)左边的“Vehicle veh1”创建了一个Vehicle类引用变量。所谓Vehicle类引用,就是以后可以用来指向Vehicle对象的对象引用。 17 | 4)“=”操作符使对象引用指向刚创建的那个Vehicle对象。 18 | 将这句代码拆成两段: 19 | Vehicle veh1; 20 | veh1 = new Vehicle(); 21 | 有两个实体,一个是类对象引用变量,一个是类对象本身 22 | 对象创建在堆当中,而引用变量在数据段以及栈空间中(虚拟机栈中存着对象引用),二者所在的地方不同。 23 | 一个类Vehicle 可以创建很多个对象,但他们的名字是什么,不可能都叫Vehicle,那么如何访问该对象呢,这是用引用变量来间接访问。 24 | 对象好比是一只很大的气球,大到我们抓不住它。引用变量是一根绳, 可以用来系汽球。 25 | 如果只执行了第一条语句,还没执行第二条,此时创建的引用变量veh1还没指向任何一个对象,它的值是null。引用变量可以指向某个对象,或者为null。 26 | 它是一根绳,一根还没有系上任何一个汽球的绳。执行了第二句后,一只新汽球做出来了,并被系在veh1这根绳上。我们抓住这根绳,就等于抓住了那只汽球。 27 | 这是如果有这一句代码: 28 | Vehicle veh2; 29 | 就又做了一根绳,还没系上汽球。如果再加一句: 30 | veh2 = veh1; 31 | 系上了。这里,发生了复制行为。但是,要说明的是,对象本身并没有被复制,被复制的只是对象引用。结果是,veh2也指向了veh1所指向的对象。 32 | 两根绳系的是同一只汽球。 33 | 如果用下句再创建一个对象: 34 | veh2 = new Vehicle(); 35 | 则引用变量veh2改指向第二个对象 36 | 从以上叙述再推演下去,我们可以获得以下结论: 37 | (1)一个对象引用可以指向0个或1个对象(一根绳子可以不系汽球,也可以系一个汽球); 38 | (2)一个对象可以有N个引用指向它(可以有N条绳子系住一个汽球)。 39 | 如果再来下面语句: 40 | veh1 = veh2; 41 | 按上面的推断,veh1也指向了第二个对象。这个没问题。问题是第一个对象呢?没有一条绳子系住它,它飞了。多数书里说,它被Java的垃圾回收机制回收了。 42 | 这不确切。正确地说,它已成为垃圾回收机制的处理对象。至于什么时候真正被回收,那要看垃圾回收机制的心情了。 43 | 44 | 综上垃圾回收的是堆里的对象,但是要看栈空间中是否有引用变量引用该对象。在java中通过引用来操纵对象。 45 | Java对象和引用的关系可以说是互相关联,却又彼此独立。彼此独立主要表现在:引用是可以改变的,它可以指向别的对象,譬如上面的s,你可以给它另外 46 | 的对象,如:s = new StringBuffer("Java"); 47 | 这样一来,s就和它指向的第一个对象脱离关系。 48 | 49 | 从存储空间上来说,对象和引用也是独立的,它们存储在不同的地方,对象一般存储在堆中,而引用存储在速度更快的栈中。 50 | 51 | 引用可以指向不同的对象,对象也可以被多个引用操纵,如: 52 | StringBuffer s1 = s; 53 | 这条语句使得s1和s指向同一个对象。既然两个引用指向同一个对象,那么不管使用哪个引用操纵对象,对象的内容都发生改变,并且只有一份,通 54 | 过s1和s得到的内容自然也一样,(String除外,因为String始终不变,String s1=”AAAA”; String s=s1,操作s,s1由于始 55 | 终不变,所以为s另外开辟了空间来存储s,)如下面的程序:注意(String和StringBuffer的区别) 56 | StringBuffer s; 57 | s = new StringBuffer("Java"); 58 | StringBuffer s1 = s; 59 | s1.append(" World"); 60 | System.out.println("s1=" + s1.toString());//打印结果为:s1=Java World 61 | System.out.println("s=" + s.toString());//打印结果为:s=Java World 62 | 63 | 这段代码很重要: 64 | public class ObjectRef { 65 | 66 | //基本类型的参数传递 67 | public static void testBasicType(int m) { 68 | System.out.println("m=" + m);//m=50 69 | m = 100; 70 | System.out.println("m=" + m);//m=100 71 | } 72 | 73 | //参数为对象,不改变引用的值 ?????? 74 | public static void add(StringBuffer s) { 75 | s.append("_add"); 76 | } 77 | 78 | //参数为对象,改变引用的值 ????? 79 | public static void changeRef(StringBuffer s) { 80 | s = new StringBuffer("Java"); 81 | } 82 | 83 | public static void main(String[] args) { 84 | int i = 50; 85 | testBasicType(i); 86 | System.out.println(i);//i=50 87 | StringBuffer sMain = new StringBuffer("init"); 88 | System.out.println("sMain=" + sMain.toString());//sMain=init 89 | add(sMain); 90 | System.out.println("sMain=" + sMain.toString());//sMain=init_add 91 | changeRef(sMain); 92 | System.out.println("sMain=" + sMain.toString());//sMain=init_add 93 | } 94 | } 95 | 以上程序的允许结果显示出,testBasicType方法的参数是基本类型,尽管参数m的值发生改变,但并不影响i。 96 | add方法的参数是一个对象,当把sMain传给参数s时,s得到的是sMain的拷贝(重点),所以s和sMain指向同一个对象,因此,使用s操作影响的其 97 | 实就是sMain指向的对象,故调用add方法后,sMain指向的对象的内容发生了改变。 98 | 在changeRef方法中,参数也是对象,当把sMain传给参数s时,s得到的是sMain的拷贝(敲黑板,重点),但与add方法不同的是,在方法体内改变了s指 99 | 向的对象(也就是s指向了别的对象,牵着气球的绳子换气球了),给s重新赋值后,s与sMain已经毫无关联,它和sMain指向了不同的对象,所以不管 100 | 对s做什么操作,都不会影响sMain指向的对象,故调用changeRef方法前后sMain指向的对象内容并未发生改变。 101 | 102 | 103 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # java_learning 2 | 这是北邮人论坛暖羊羊大神的java学习路线 3 | 侵权则删 4 | -------------------------------------------------------------------------------- /第4步: -------------------------------------------------------------------------------- 1 | 第4步 2 | 4. 学习Java的面向过程编程,包括基本数据结构、表达式、语句、控制流、函数调用。 3 | java数据结构: 4 | (1)首先我们先来了解基本的数据结构: 5 | 数据结构是指计算机组织数据的方式,数据元素之间存在什么关系。 6 | A:集合结构, 数据元素之间没有什么直接联系,他们唯一的关系为他们都属于这个集合,嗯,类似布朗运动。 7 | B:线性结构,集合中元素存在“一一对应”关系,集合中必定存在一个唯一的“第一个元素”,也必定存在唯一的“最后一个元素”,除最后元素之外, 8 | 其它数据元素均有唯一的"后继",除第一元素之外,其它数据元素均有唯一的"前驱"。 9 | C:树形结构,集合中元素存在“一对多的关系”,类似于家谱等。 10 | D:图形结构,集合中元素存在“多对多的关系”,关系错综复杂,嗯,网状结构。 11 | java常用的数据结构在第7步中讲,主要是java.util.*中的东西。 12 | (2)java的表达式 13 | java中的表达式是变量、常量、运算符和方法调用的序列,其执行一个指定的运算并且返回某个确定的值。 14 | 例如关系表达式“3<7”返回ture,7+2返回9等等。 15 | 还有一种叫“表达式语句”,就是在表达式后加上分号作为语句来使用。(int i=123;) 16 | 下面我将分别介绍变量、常量、运算符、方法等。 17 | A变量: 18 | 一,概述:通常我们通过内存的地址找到内存的位置,从而知道内存中存储的数据,但是我们都清楚地址十分地不好记,于是我们给存储 19 | 数据的这个空间取名字,从而通过变量名更加简单方便地取出数据。变量是存储数据的基本单元,变量值可以发生改变。 20 | 或者说变量就是数据,可以改变的数据。 21 | 二,变量命名规则:变量必须以字母、下划线"_"或"$"符号开头;变量可以包括数字,但不能以数字开头; 22 | 除了"_"或"$"符号以外,变量名不能包含任何特殊字符;不能使用Java语言的关键字,如int、class、public、void、static等 23 | 三,变量的声明:数据类型 变量名 = 值(int i=123) 24 | 四,就算变量没有赋值,当sizeof(变量名) != 0,应该为1,即便没有数据,其在内存中也占有空间。 25 | 根据变量的作用范围,我们可以将变量分为:成员变量与局部变量 26 | 成员变量:作为类的成员存在,直接存在于类中 27 | 局部变量:作为方法的成员存在,存在于方法的参数列表和方法定义中。 28 | 局部变量在使用前必须被程序员主动初始化,但是成员变量可以被系统提供一个默认的初始值,所以在语法上成员变量定义后可以使用,注意,只是语法 29 | 上,局部变量定义后赋初值才能使用。 30 | 例子 31 | public class Temp { 32 | int t; //实例变量 33 | public static void main(String args[]){ 34 | int t=1; //局部变量 35 | System.out.println(t); //打印局部变量 36 | Temp a= new Temp(); //创建实例 37 | System.out.println(a.t); //通过实例访问实例变量 38 | } 39 | } 40 | 结果为1,0 //成员变量有初始值 41 | 42 | 成员变量可以分为实例变量和静态变量. 43 | 静态变量之前要加static。 44 | 静态变量属于静态存储方式,其存储空间为内存中的静态数据区(在 静态存储区内分配存储单元),该区域中的数据在整个程序的运行期间 45 | 一直占用这些存储空间(在程序整个运行期间都不释放),也可以认为是其内存地址不变,直到整个程序运行结束 46 | 区别: 47 | 实例变量属于某个对象的属性,必须创建了实例对象,其中的实例变量才会被分配空间,才能使用这个实例变量。静态变量不属于某个实例对象, 48 | 而是属于类,所以也称为类变量,只要程序加载了类的字节码,不用创建任何实例对象,静态变量就会被分配空间,静态变量就可以被使用了。 49 | 实例变量必须创建对象后才可以通过这个对象来使用,静态变量则可以直接使用类名来引用。 50 | 下面是一个例子 51 | public class Temp { 52 | static int t; //类变量,或者说静态变量 53 | public static void main(String args[]){ 54 | System.out.println(t); //打印类变量 55 | int t=1; //局部变量 56 | System.out.println(t); //打印局部变量 57 | Temp a= new Temp(); //创建实例 58 | System.out.println(a.t); //通过实例访问实例变量 59 | } 60 | } 61 | 结果为0,1,0 62 | B常量: 63 | 一,概述:与变量相对应,常量是不可改变的数据。 64 | 二,作用:常量在程序中通常有两个作用:代表常数,增强程序的可读性。 65 | 三,常量的声明:final 常量名 = 值(在Java编码规范中,要求常量名必须大写),例如:final PI = 3.1415926 66 | C基本数据类型: 67 | 之前所说的变量、常量都是指数据,那么我们来详细说说数据的类型。 68 | Java语言提供了八种基本类型。六种数字类型(四个整数型(默认是int 型),两个浮点型(默认是double 型)),一种字符类型,还有一种布尔型。 69 | byte: 1Byte 70 | char,short: 2Byte 71 | int,float: 4Byte 72 | long,double: 8Byte 73 | byte: 74 | 此类型为8位,有符号,以二进制补码表示的整数,占一个字节 75 | 最小值是-128(-2^7); 76 | 最大值是127(2^7-1);//此类型有一位用于做符号位 77 | 其所表示的数据范围挺小的,此类型用在大型数组中节约空间,主要用来代替整数,byte变量所占空间只有int变量的1/4 78 | short: 79 | short数据类型是16位、有符号的以二进制补码表示的整数,占2字节 80 | 最小值是-32768(-2^15); 81 | 最大值是32767(2^15 - 1);//此类型有一位用于做符号位 82 | Short数据类型也可以像byte那样节省空间。一个short变量是int型变量所占空间的二分之一; 83 | 例子:short s = 1000,short r = -20000。 84 | int: 85 | int数据类型是32位、有符号的以二进制补码表示的整数;占4字节 86 | 最小值是-2,147,483,648(-2^31); 87 | 最大值是2,147,485,647(2^31 - 1); 88 | 例子:int a = 100000, int b = -200000。 89 | long: 90 | long数据类型是64位、有符号的以二进制补码表示的整数;占8字节 91 | 最小值是-9,223,372,036,854,775,808(-2^63); 92 | 最大值是9,223,372,036,854,775,807(2^63 -1); 93 | 这种类型主要使用在需要比较大整数的系统上; 94 | 例子: long a = 100000L,int b = -200000L。 95 | long a=111111111111111111111111(错误,整数型变量默认是int型) 96 | long a=111111111111111111111111L(正确,强制转换) 97 | float: 98 | float数据类型是单精度、32位、符合IEEE 754标准的浮点数;占4字节 -3.4*E38- 3.4*E38。。。浮点数是有舍入误差的 99 | float在储存大型浮点数组的时候可节省内存空间; 100 | 默认值是0.0f; 101 | 例子:float f1 = 234.5f。 102 | float f=6.26(错误 浮点数默认类型是double类型) 103 | float f=6.26F(转换正确,强制) 104 | double d=4.55(正确) 105 | double: 106 | double数据类型是双精度、64位、符合IEEE 754标准的浮点数; 107 | 浮点数的默认类型为double类型; 108 | double类型同样不能表示精确的值,如货币; 109 | 默认值是0.0d; 110 | 例子:double d1 = 123.4。 111 | boolean: 112 | boolean数据类型表示一位的信息; 113 | 只有两个取值:true和false; 114 | 这种类型只作为一种标志来记录true/false情况; 115 | 默认值是false; 116 | 例子:boolean one = true。 117 | char: 118 | char类型是一个单一的16位Unicode字符;用 ‘’表示一个字符。java 内部使用Unicode字符集。他有一些转义字符,2字节 119 | 最小值是’\u0000’(即为0); 120 | 最大值是’\uffff’(即为65,535);可以当整数来用,它的每一个字符都对应一个数字 121 | 例子 char c1 = 'a'; 122 | D对象数据类型 123 | 基本数据类型在栈中进行分配,而对象类型在堆中进行分配。 124 | 例如string对象,Date对象,数组对象等 125 | String world = "world"; 126 | Date time = new Date(); 127 | 基本数据类型 byte short int long float double char boolean 128 | 对象数据类型 Byte Short Integer Long Float Double Character Boolean 129 | 以int为例:int是基本数据类型,Integer是对int进行了封装的一个类。 130 | 声明为int的变量不需要实例化,声明为Interger的变量需要实例化 131 | Java 提供两种不同的类型:引用类型和原始类型(或内置类型)。Int是java的原始数据类型,Integer是java为int提供的封装类。 132 | int 一般做为数值参数就够了 133 | integer 一般做类型转换的时候用的较多 134 | 基本数据类型比较: 135 | if (a==b) 模式 136 | 对象比较: 137 | 对象.equals(对象)模式,比较是地址,不是比较内容 138 | 所以string对象比较时使用equal 139 | "=="操作符的作用1、用于基本数据类型的比较2、判断引用是否指向堆内存的同一块地址。 140 | equals的作用:用于判断两个变量是否是对同一个对象的引用,即堆中的内容是否相同,返回值为布尔类型 141 | 例子一:对象不同,内容相同,"=="返回false,equals返回true 142 | String s1 = new String("java"); 143 | String s2 = new String("java"); 144 | System.out.println(s1==s2); //false 145 | System.out.println(s1.equals(s2)); //true 146 | 例子二:同一对象,"=="和equals结果相同 147 | String s1 = new String("java"); 148 | String s2 = s1; 149 | System.out.println(s1==s2); //true 150 | System.out.println(s1.equals(s2)); //true 151 | E引用数据类型 152 | int a = 1; //基本数据类型 153 | Integer b = new Integer(1);// 引用数据类型 154 | Integer c = b;// 引用数据类型 155 | 例如: Point x = new Point (0, 0); 156 | Point y = new Point (1, 1); 157 | System.out.println(x) 158 | System.out.println(y) 159 | 输出为(0, 0),(1, 1) 160 | x = y; 161 | x.setlocation(5, 5); 162 | System.out.println(x) 163 | System.out.println(y) 164 | 输出为(5, 5),(5, 5),因为对于对象类型(或者说非基本数据类型),赋值的是对象引用,x和y都引用了同一个对象,x改变,y也改变。 165 | F数组数据类型 166 | int[] myarray = new int[5]; 167 | G数据类型的转换 168 | 数据类型转换有两种: 169 | (1) 自动类型转换:编译器自动完成类型转换,不需要在程序中编写代码; 170 | 规则:从存储范围小的类型到存储范围大的类型。 171 | 具体规则:byte→short(char)→int→long→float→double. 172 | 例如:byte b1 = 100; 173 | int n = b1;数据不会丢失 174 | 注意问题:在整数之间进行类型转换时,数值不发生改变,而将整数类型,特别是比较大的整数类型转换成小数类型时, 175 | 由于存储方式不同,有可能存在数据精度的损失。 176 | (2) 强制类型转换:强制编译器进行类型转换,必须在程序中编写代码。该类型转换很可能存在精度的损失。 177 | 规则:从存储范围大的类型到存储范围小的类型。 178 | 具体规则:double→float→long→int→short(char)→byte. 179 | 语法格式为:(转换到的类型)需要转换的值 180 | 例如:int m = 1234; 181 | byte b1 = (byte)m;注意问题:强制类型转换通常都会存储精度的损失,需谨慎。 182 | H运算符 183 | 算术运算符:+,-,*,/,% 184 | 赋值运算符:=,+=,-+,*=,/=,%= 185 | 关系运算符:==,!=,>,<,>=,<= 186 | 自增/键:++,-- 187 | 逻辑运算符:&&,||,!,^ 188 | (3)java中的语句:与(4)中的控制流关系比较密切。 189 | java中的语句: 190 | a条件语句: 191 | if.....else if.........else.......... 192 | switch(){ 193 | case value1: 194 | ...... 195 | break; 196 | case value2: 197 | ...... 198 | break; 199 | . 200 | . 201 | . 202 | . 203 | default: 204 | ...... 205 | break; 206 | } 207 | b循环语句: 208 | for(){ 209 | } 210 | while(){ 211 | } 212 | do(){ 213 | }while() 214 | c控制语句: 215 | break 216 | continue 217 | c=a>b?a:b; 218 | (5)函数调用 219 | 注意如果函数(方法)的形参为基本数据类型,其传递的是值,形参的改变不会影响实参。 220 | 如果函数(方法)的形参为数组、对象等非基本数据类型,,其传递的是引用,形参的改变会影响实参,在函数里面要注意。 221 | 222 | 223 | 224 | 225 | 226 | 227 | -------------------------------------------------------------------------------- /第5.5步: -------------------------------------------------------------------------------- 1 | 5.5 这时候应该已经涉及了Java的垃圾回收。要留意即使有垃圾回收的情况下也会发生的内存泄露(如自己设计数组容器,元素是引用,逻辑上删除了元素, 2 | 但并没有清成null)。注意垃圾回收只能回收内存中的对象,除了内存以外,其它资源不能依靠垃圾回收来关闭。比如,文件、管道、Socket、数据库连接等, 3 | 垃圾回收是不会帮你关闭的。 4 | 本章内容主要是内存管理和垃圾回收机制参考,参考资料主要为《深入理解java虚拟机》的前三章和部分《java高手真经》 5 | 首先是内存管理:主要包括java内存区域的介绍和内存溢出异常 6 | 开始----------------------------------------------------| 7 | | 此为运行数据区 | 8 | | 方法区 虚拟机栈 本地方法栈 | 9 | | | 10 | | | 11 | | 堆 程序计数器 | 12 | | | 13 | |-------------------------------------------------------| 14 | ^ | ^ | 15 | | v | v 16 | 执行引擎 -> 本地库接口 -> 本地方法库 17 | 18 | 所有线程共享的数据区: 方法区、堆、执行引擎、本地库接口 19 | 线程隔离的数据区(各线程私有的):虚拟机栈、本地方法栈、程序计数器。 20 | 21 | 上述为java虚拟机运行时的数据区 22 | 23 | 一、下面我来一个个介绍: 24 | (1)程序计数器:各线程私有的,书上的说法:它可以看作是当前线 25 | 程所执行的字节码的行号指示器。在虚拟机的概念模型里(仅是概念模型,各种虚拟机可能会通过一些更高效的方式去实现),字节码解释器 26 | 工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这 27 | 个计数器来完成。 28 | 我来解释一下,我们都知道多线程并发是指线程轮流切换,轮流使用控制器,通过分配控制器的时间来实现的,比如说1秒内,多少毫秒给这个线程 29 | 多少毫秒给那个线程,如此实现多线程。在某一时刻内,控制器只能处理一个线程。 30 | 当线程切换,最后又回来时,控制器怎么知道这个线程进行到哪一步了,即上次执行到第几行指令,这次从哪开始,这时程序计数器发挥作用,其能 31 | 使得回到正确的执行位置。 32 | 也因此程序计数器是各线程所私有的。 33 | (2)java虚拟机栈:java虚拟机栈也为线程所私有,虚拟机栈描述的是java方法(敲黑板,注意是方法)执行的内存模型:每个方法在执行的同时 34 | 都会创建一个栈帧(Stack Frame[1])用于存储局部变量表(重点)、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程, 35 | 就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。栈帧是方法运行时的基础数据结构 36 | 通常人们所讨论的把虚拟机内存区分成堆和栈两个部分,栈就是虚拟机栈,或者说虚拟机栈中的局部变量表。 37 | 局部变量表存储的内容:编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型, 38 | 它不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)和 39 | returnAddress类型(指向了一条字节码指令的地址)。 40 | 局部变量表,其所需的内存在编译期间已经确定,进入一个方法时,这个方法在帧中分配多大的局部变量空间已经确定,方法在运行期间不会改变局部变量表 41 | 的大小。 42 | 43 | 虚拟机栈这个部分可能会出现两个异常:(1)当线程申请的栈深度大于虚拟机所允许的深度,则会跑出stackoverflowerror异常(2)对于大部分可动态扩展 44 | 的虚拟机栈,如果扩展时无法申请到足够的内存,则会抛出outofmemoryerror异常。 45 | (3)本地方法栈:其重用与虚拟机栈相似,二者区别是虚拟机栈为虚拟机执行java方法服务,而本地方法栈为虚拟机执行native方法服务 46 | native方法:简单地讲,一个Native Method就是一个java调用非java代码的接口。一个Native Method是这样一个java的方法:该方法的实现由非java 47 | 语言实现,比如C。这个特征并非java所特有,很多其它的编程语言都有这一机制,比如在C++中,你可以用extern "C"告知C++编译器去调用一个C的 48 | 函数。对于native方法而言,Java程序不会为该方法提供实现体。 49 | 例子: 50 | public class NativeTest{ 51 | public native void info(); 52 | } 53 | (4)java堆:注意堆与栈的区别。 54 | java堆是虚拟机栈中所占内存最大的一块,它是所有线程共享的一块内存区域,虚拟机启动时创建,其唯一(敲黑板,注意啦)目的是存放对象实例, 55 | 几乎所有的对象实例都在这里分配内存。 56 | 规范上说;所有的对象实例和数组都在堆上分配内存。 57 | 垃圾回收主要是回收对象实例所占的内存,用暖神的说法:注意垃圾回收只能回收内存中的对象,除了内存以外,其它资源不能依靠垃圾回收来关闭。 58 | 比如,文件、管道、Socket、数据库连接等,垃圾回收是不会帮你关闭的。所以java堆是垃圾收集器管理的主要区域。 59 | java堆的一般特性:Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,就像我们的磁盘空间一样。在实现时,既可以实现成固 60 | 定大小的,也可以是可扩展的,不过当前主流的虚拟机都是按照可扩展来实现的(通过-Xmx和-Xms控制)。如果在堆中没有内存 61 | 完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。 62 | (5)方法区:各个线程所共享的区域。其用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。 63 | Java虚拟机规范对方法区的限制非常宽松,除了和Java堆一样不需要连续的内存和可以选择固定大小或者可扩展外,还可以选择不实现垃圾收集。 64 | 相对而言,垃圾收集行为在这个区域是比较少出现的,但并非数据进入了方法区就如永久代的名字一样“永久”存在了。这区域的内存回收目标主要 65 | 是针对常量池的回收和对类型的卸载,一般来说,这个区域的回收“成绩”比较难以令人满意,尤其是类型的卸载,条件相当苛刻,但是这部分区域 66 | 的回收确实是必要的。 67 | (6)运行时常量池:注意,运行时常量池是方法区的一部分,Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常 68 | 量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池 69 | 中存放。 70 | 首先我们先来看看常量池的信息:Java中的常量池,实际上分为两种形态:静态常量池和运行时常量池。 71 | 所谓静态常量池,即*.class文件中的常量池,class文件中的常量池不仅仅包含字符串(数字)字面量,还包含类、方法的信息, 72 | 占用class文件绝大部分空间。 73 | 而运行时常量池,则是jvm虚拟机在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区中,我们常说的常量池, 74 | 就是指方法区中的运行时常量池。 75 | 常量池指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。除了包含代码中所定义的各种基本类型(如int、long等等) 76 | 和对象型(如String及数组)的常量值(final)(敲黑板,常量值才是重点,各种基本类型和对象型的常量值,不是基本类型和对象型, 77 | 此二者存在虚拟机栈中的局部变量表中)还包含一些以文本形式出现的符号引用,比如:类和接口的全限定名; 字段的名称和描述符; 78 | 方法和名称和描述符。虚拟机必须为每个被装载的类型维护一个常量池。 79 | 可能会和虚拟机栈有些冲突误会:虚拟机栈保存的主要是局部变量表,用于描述方法执行的内存模型,而运行常量池保存的是被保存在 80 | 已编译的.class文件中的一些数据。 81 | (7)直接内存 82 | 直接内存并不是java虚拟机运行数据区的一部分,但是这部分内存也被频繁地使用, 83 | 在JDK1.4以后加入了NIO(new input/output)类,引入了基础通道(Channel)与缓冲区(Buffer)的I/O方式,这种方法使得native函数库 84 | 直接分配堆外内存(直接内存),然后通过java堆中的一个DirectByteBuffer对象作为这块内存的引用进行操作,从而避免了java堆和native堆 85 | 中来回复制数据 86 | 直接内存的分配不受java堆大小的限制,但由于是内存,所以肯定受到总内存大小的限制,所以动态扩展时若忽略了直接内存则有可能导致 87 | OutOfMemoryError异常 88 | 89 | 90 | 二、在了解虚拟机运行区的基本构造之后,我们来看看虚拟机一般是怎么工作的。 91 | (1)对象的创建 92 | (a)首先我们都知道一般创建对象要用到new关键字,虚拟机遇到一条new指令。 93 | (b)将new后面带有的参数能否在常量池中定位到一个类的符号引用,常量池是方法区的一部分,存储着类信息(即类的版本、字段、方法、 94 | 接口等描述信息)。检查这个类是否被加载、解析、初始化过,如果没有,则执行相应的类加载过程。 95 | (c)类加载检查完后,就需要为对象分配内存了,即从堆中划分出一块内存出来分配给对象。 96 | 此时这个划分分配方法根据java内存堆是否规整分成两类: 97 | (1)如果java内存堆十分地规整,所有正在用的内存都放在一边,未用过的都放在另一边,那么划分内存只不过是指针的移动 98 | 这种方法称为“指针碰撞”,老实说这种方法挺难,因为垃圾回收时需要对整体进行移动, 99 | (2)如果java的内存堆并不规整,正在用的内存和未使用的内存交错分布,这时不能只是简单地移动指针就能分配内存了。虚拟机 100 | 必须维护一个列表,记录那块内存能够使用,大小如何,在分配时划出足够大的空间给对象,这种方式成为“空闲列表” 101 | 选择哪种方式取决于内存堆是否规整,而java内存堆是否规整取决于垃圾收集器是否带有压缩整理功能。 102 | (d)对象的创建划分内存除了确定划给它的区域大小是否足够外,还需要注意一个问题:对象的创建在工程是十分地频繁的,即时是仅仅修改 103 | 指针所指向的位置在高并发的情况下也有可能会出错。即在一个线程正在给A分配内存,指针还没来得及移动,另一个线程给B分配内存,使用了 104 | 原来的指针进行分配,这就不安全了。这是处理方法一般有两种:(1)对分配内存的动作进行同步处理,即分配内存这个动作已经在其作用了, 105 | 另一个线程必须等其这个动作执行完后才能使用这个动作(2)把内存分配的动作按照线程划分在不同空间中进行,即每个线程在java堆中预先 106 | 分配一小块内存,成为本地线程缓存(TLAB),那个线程要分配内存就在自己的本地线程缓存上分配,其用完了才同步锁定。 107 | (e)内存分配完后虚拟机需要将分配的内存空间都初始化为零值(不包括对象头),这样可使得对象的实例字段(类名 对象名//这样为声明对象, 108 | new 构造方法([参数列表]),即给对象分配内存,这样对象才成为类的实例)在java代码中可以不赋初始值就可以直接使用,程序能访问到这 109 | 些字段的数据类型所对应的零值。 110 | (f)接下来虚拟机对对象进行必要的配置,如这个对象是哪个类的实例,怎样找到类的元数据信息,对象的哈希码、对象的GC分代年龄等信息。 111 | 这些信息存放在对象的对象头(Object Header)之中 112 | (g)在上面工作都完成后从虚拟机角度对象的创建工作已完成,但是但从Java程序的视角来看,对象创建才刚开始,方法还没有执行, 113 | 所有的字段都还为零。所以一般来说,执行new指令之后会接着执行方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的 114 | 对象才算完全产生出来。 115 | 116 | 总体而言对象的创建步骤如下:虚拟机遇到new指令,检查常量池看类是否被加载,没有则加载,然后给对象分配内存,两种方法“指针碰撞”和 117 | “空闲列表”,然后虚拟机将所分配的内存空间都初始化为零值,再对对象进行必要的配置,然后一般程序员协定方法初始化对象,如此 118 | 可用的对象才完成。 119 | (2)对象的内存布局 120 | 对象在内存中的布局可以分成3个部分:对象头、实例数据、对象填充 121 | (a)对象头:主要包括两部分内容(1)分用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、 122 | 偏向线程ID、偏向时间戳等 123 | (2)类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。 124 | (b)实例数据:对象真正存储的信息数据。 125 | (c)对象填充:不是必然存在的,没什么特别地含义,只是起到占位符的作用 126 | (3)对象的访问定位:建立对象是为了使用对象,通过哪种方式去定位、访问堆中的对象,从而使用对象的实例数据。主要包括两种方法:句柄和直接指针 127 | (a)如果使用句柄,首先想一下句柄的含义:可将这一情形想象成用遥控板(句柄)操纵电视机(对象)。只要握住这个遥控板, 128 | 就相当于掌握了与电视机连接的通道。 129 | 如果使用句柄:如果使用句柄访问的话,那么Java堆中将会划分出一块内存来作为句柄池,reference中 130 | 存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息。 131 | (b)如果使用直接指针:如果使用直接指针访问,那么Java堆对象的布局中就必须考虑如何放置访问类型数据的 132 | 相关信息,而reference中存储的直接就是对象地址。 133 | (c)这两种方式各有千秋,使用句柄的最大好处是reference中存储的是稳定的句柄地址,对象被移动(垃圾收集时移动对象是十分普及 134 | 的行为),只需改变句柄中的实例数据指针,而reference本身不用修改。 135 | 使用直接实例的最大好处是速度更快,其节省了一次指针定位的时间开销,由于对象的访问十分频繁,这类开销也十分可观。 136 | 三、内存泄露与内存溢出的区别 137 | 内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个integer,但给它 138 | 存了long才能存下的数,那就是内存溢出。 139 | 内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多 140 | 少内存,迟早会被占光。memory leak会最终会导致out of memory! 141 | 四、java堆溢出 142 | 不断新建对象,消耗堆内存,当堆内存不够时就会显示OutOfMemory:java heap(堆) space. 143 | 先通过内存影像工具(eclipse memory analyze)对堆存储快照进行分析,看到底是内存溢出还是内存泄露。 144 | (1)如果是内存泄露,则可以通过工具查看泄露对象到GC roots(Garbage Collector roots,特指垃圾收集器的对象,GC会收集那些不是GC roots且没 145 | 有被GC roots引用的对象)的引用链,于是便可以知道泄露对象通过怎样地路径与GC roots相关联并导致GC无法自动回收它们。掌握了泄露对象的类型 146 | 信息及GC roots引用链的信息,就可以定位泄露代码的位置。 147 | (2)如果不是内存泄露,那么对象就必须还是活着的,那就应当检查虚拟机的堆参数(-Xmx与-Xms),与机器物理内存对比看是否还可以调大,从代码上 148 | 检查是否存在某些对象生命周期过长、持有状态时间过长的情况,尝试减少程序运行期的内存消耗。 149 | 五、虚拟机栈和本地方法栈溢出 150 | (1)如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出stackoverflowerror异常 151 | (2)如果虚拟机在扩展栈时无法申请到足够的内存,则抛出outofmemoryerror异常 152 | 但是本质上这两个都是一样东西,内存空间不够了。 153 | 154 | 虚拟机提供参数控制堆和方法区的最大内存空间,譬如32位windows限制内存为2GB内存,减去Xmx(最大堆容量)和MaxPermSize(最大方法区容量) 155 | 程序计数器几乎不占内存,所以剩余内存被虚拟机栈和本地方法栈瓜分,如果分给每个线程的栈容量越大,那么同时可在用的线程数量就越少,建立 156 | 线程时越容易把剩下的内存耗尽。 157 | 158 | 如果是建立过多线程导致的内存溢出,再不能减少线程数量的情况下,我们只能减少最大堆和减少分配给每个线程的栈容量来换取更多的线程。 159 | 160 | 六、方法区和运行时常量池溢出 161 | 运行时常量池溢出:Exception in thread"main"java.lang.OutOfMemoryError:PermGen space 162 | 方法区溢出也是一种常见的内存溢出异常,一个类要被垃圾收集器回收掉,判定条件是比较苛刻的。在经常动态生成大量Class的应用中, 163 | 需要特别注意类的回收状况。这类场景除了上面提到的程序使用了CGLib字节码增强和动态语言之外,常见的还有:大量JSP或动态产 164 | 生JSP文件的应用(JSP第一次运行时需要编译为Java类)、基于OSGi的应用(即使是同一个类文件,被不同的加载器加载也会视为不同的类)等。 165 | 七、本机直接内存溢出 166 | Exception in thread"main"java.lang.OutOfMemoryError 167 | 由DirectMemory导致的内存溢出,一个明显的特征是在Heap Dump文件中不会看见明显的异常,如果读者发现OOM之后Dump文件很小, 168 | 而程序中又直接或间接使用了NIO,那就可以考虑检查一下是不是这方面的原因。 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | -------------------------------------------------------------------------------- /第5步: -------------------------------------------------------------------------------- 1 | 5. 学习Java的面向对象编程,包括类、修饰符、引用类型和值类型的区别、成员、方法、访问控制、继承、多态、接口、接口实现。 2 | 顺便学习一下面向对象的基本思想,即对象、消息、封装、继承、多态等,这些通用的内容不是Java特有的。 3 | (1)类 4 | java类的基本构成:包,引入,类,变量,方法,注释 5 | package mypackage //定义包 6 | import java.util.* //引入类 7 | 8 | public class helloworld{ //定义类 9 | private String hello = "helloworld"; //变量 10 | public void say(){ //方法 11 | System.out.println(hello); 12 | } 13 | } 14 | 15 | 包:我们把功能相似的类放在一个包中 16 | 引入(import):引入类后就能用类中的各种函数功能 17 | 类:java程序的基本单位 18 | 变量,函数就不多说了。 19 | (2)修饰符 20 | a类的修饰符 21 | 抽象类 abstract(注意抽象类与接口的区别) 22 | 抽象类就是没有具体对象的概念类,抽象类是它所有子类的公共属性的集合 23 | 比如:我们要描述“水果”,它就是一个抽象,它有质量、体积等一些共性(水果有质量), 24 | 但又缺乏特性(苹果、橘子都是水果,它们有自己的特性),我们拿不出唯一一种能代表水果的东西 25 | 在面向对象领域,抽象类主要用来进行类型隐藏。我们可以构造出一个固定的一组行为的抽象描述,但是这组行为却能够有任意个可能的具体实现方式。 26 | 这个抽象描述就是抽象类,而这一组任意个可能的具体实现则表现为这个抽象类的所有派生类。 27 | 抽象类提供了继承的概念,它的出发点就是为了继承,否则它没有存在的任何意义。所以说定义的抽象类一定是用来继承的,同时在 28 | 一个以抽象类为节点的继承关系等级链中,叶子节点一定是具体的实现类。 29 | 1、抽象类不能被实例化,实例化的工作应该交由它的子类来完成,它只需要有一个引用即可。 30 | 2、抽象方法必须由子类来进行重写。 31 | 3、只要包含一个抽象方法的抽象类,该方法必须要定义成抽象类,不管是否还包含有其他方法。 32 | 4、抽象类中可以包含具体的方法,当然也可以不包含抽象方法。 33 | 5、子类中的抽象方法不能与父类的抽象方法同名。 34 | 6、abstract不能与final并列修饰同一个类。 35 | 7、abstract 不能与private、static、final或native并列修饰同一个方法。、 36 | 最终类 final 37 | 如果一个类被final修饰,则这个类不可能有子类 38 | 被定义为final的类通常是一些有固定作用、用来完成标准功能的类 39 | 如Socket等类 40 | b变量的修饰符 41 | 静态变量 static 42 | 如果你想要处理的东西是整个程序中唯一的,弄成静态是个好方法。非静态的东西你修改以后只是修改了他自己的数据,但是不会影响其他同类对象的数据。 43 | 静态变量是类中所有对象的共享的变量,是公共的存储单元 44 | 标记一个变量为static,则该变量在内存中有不变的位置,相当于全局变量,所有类的实例都访问同一个存储变量区域。 45 | 对其修改对于所有类的实例来说都是可见和一致的。 46 | 注意,敲黑板:static变量在编译时已经分配内存, 47 | 定义static变量是称为静态变量 也是局部变量 48 | 谁说全局就不能改变值了,除非是 static final声明,就成常量了,就不能改了 49 | 最终变量 final 50 | 其修饰代表常量,不可改变,修饰的同时指出常量的具体取值 51 | final static ....代表全局常量 52 | 异失变量 volatile 53 | 如果一个域被volatile修饰,则说明其可能同时被多个线程所控制、修改,即此域不仅仅被当前程序所掌握,在运行过程中极有可能受到其他线程的影响 54 | volatile String world = "world"; 55 | 通常volatile用来修饰解释外部输入的量,如表示当前时间的变量,后台线程随时修改。 56 | c方法的修饰符 57 | 抽象方法 abstract 58 | 此方法仅有方法头,没有具体的方法体和操作实现的方法。其目的是为了所有子类对外呈现一个相同名字的方法,是一个统一的接口。所有抽象方法必须存在 59 | 与抽象类中。 60 | 该方法必须由子类来实现,才能被调用。 61 | 静态方法 static 62 | 可以像调用静态变量一样,直接通过类名调用静态方法。(Hello.say()) 63 | 调用此方法时用类名做前缀。此方法只能处理属于整个类的成员变量 64 | 最终方法 final 65 | 功能和内部语句不能再更改,不能再被继承 66 | 同步方法 synchronized 67 | 如果其修饰的是类的方法(static的方法,静态方法),在被调用执行前,将把系统类class中对应当前类的对象加锁。 68 | 如果其修饰的是对象的方法(没有static修饰的方法),在被调用执行前,把当前对象加锁。 69 | (3)引用类型与值类型的区别 70 | 跟之前说的差不多,传的是引用还是值 71 | 引用类型表示你操作的数据是同一个,也就是说当你传一个参数给另一个方法时,你在另一个方法中改变这个变量的值, 72 | 那么调用这个方法是传入的变量的值也将改变. 73 | 值类型表示复制一个当前变量传给方法,当你在这个方法中改变这个变量的值时,最初生命的变量的值不会变. 74 | 值类型: 75 | 也就是基本数据类型 76 | 引用类型: 77 | 除了四类八种基本类型外,所有的类型都称为引用类型(数组,类,接口,字符串) 78 | (4)成员 79 | java类有成员和方法 80 | 成员就是指 某一类事物的属性 81 | 例如 人类的属性有:姓名 性别 年龄等 82 | public class Student { 83 | private String name ;//name是Student的成员变量 84 | public String getName(){ //getName就是成员方法,定义在Student中的方法 85 | return this.name ; 86 | } 87 | } 88 | (5)方法 89 | 就是函数,所谓方法,就是用来解决一类问题的代码的有序组合,是一个功能模块。 90 | (6)访问控制 91 | 访问控制符 92 | 可见/访问性 在同一类中 同一包中 不同包中 同一包子类中 不同包子类中 93 | public yes yes yes yes yes 任意地方都可以访问 94 | protected yes yes no yes yes 同一包和子类可以访问 95 | package yes yes no yes no 96 | private yes no no no no 仅该类内部可访问 97 | (1) 在java中有public、protected、private三种显示的修饰符用于控制可见性,package不是显示的修饰符, 98 | 它是隐含的,即如果在类、变量等前没加显示的可见性修饰符,那它就是package级别的。如果在类的定义中没有指定package, 99 | 那么java会把它放在缺省包中,一般来说这个缺省的包就是当前目录。 100 | (2)在子类中的方法如果重载了父类的方法,那么该方法的可见级别应更底或者相同,如父类中的方法是public, 101 | 那么子类中方法必须是public。 102 | (3)在java中,一般来说,变量成员最好是private,对它们的访问可以通过public的方法,在这些方法中可以做些控制以保证数据 103 | 的一致性。这些方法名一般以get和set做为前缀。 104 | (7)继承 105 | 继承就是继承父类的遗产,就是使用已存在的类的定义作为基础建立新的类,新类的定义可以增加新的数据和功能,也可以用父类的功能,其 106 | 使得复用以前的代码十分地方便。 107 | 抽象类(abstract)必须被继承,最终类(final)不能被继承 108 | 用extends来继承 109 | class car{ 110 | int v;//速度 111 | void drive(){ 112 | System.out.print("car速度是" + v); 113 | } 114 | } 115 | class bus extend car{ 116 | int p;//人数 117 | void carry(){ 118 | System.out.print("bus载人" + p); 119 | } 120 | void sum(){ 121 | System.out.print("bus速度是" + v); 122 | System.out.print("bus载人" + p); 123 | } 124 | } 125 | 此时测试用例 126 | car ca = new car(); 127 | bus bu = new bus(); 128 | ca.v = 60; 129 | bu.v = 40; 130 | bu.p = 20; 131 | ca.drive(); car速度是60 132 | bu.drive(); car速度是40 133 | bu.carry(); bus载人20 134 | bu.sum(); bus速度是40,bus载人20 135 | 子类包含父类的所有成员,但是无法访问父类中被private修饰的成员(变量或函数) 136 | 注意this和super的使用,用法如下 137 | (1)this防止父类中也有p 138 | class bus extend car{ 139 | int p;//人数 140 | void carry(){ 141 | System.out.print("bus载人" + this.p); 142 | } 143 | void sum(){ 144 | System.out.print("bus速度是" + v); 145 | System.out.print("bus载人" + this.p); 146 | } 147 | } 148 | (2)super直接访问父类中的成员 149 | class bus extend car{ 150 | int p;//人数 151 | void carry(){ 152 | System.out.print("bus载人" + p); 153 | } 154 | void sum(){ 155 | System.out.print("bus速度是" + super.v); 156 | System.out.print("bus载人" + p); 157 | } 158 | } 159 | (3)this还有一种用法,this.实参 160 | class car{ 161 | int v; 162 | void car(int v){ //构造函数 163 | this.v = v; 164 | } 165 | } 166 | (8)多态(方法的覆盖,重载,重写) 167 | 在子类继承父类之后,子类就拥有了父类的成员变量和方法,但是可能子类中有与父类同名的变量和方法,同一个类内部,也可以有同名的,参数列表 168 | 不同的方法,这就是多态,即同一个名字有多种实现状态。 169 | 多态通过方法的覆盖,重载,重写来实现 170 | (a)覆盖---------继承了父类的同名无参函数 171 | class car{ 172 | int v;//速度 173 | void drive(){ 174 | System.out.print("car速度是" + v); 175 | } 176 | } 177 | class bus extend car{ 178 | int p;//人数 179 | void drive(){ 180 | System.out.print("bus速度是" + v); 181 | } 182 | void carry(){ 183 | System.out.print("bus载人" + p); 184 | } 185 | void sum(){ 186 | System.out.print("bus速度是" + v); 187 | System.out.print("bus载人" + p); 188 | } 189 | } 190 | car ca = new car(); 191 | bus bu = new bus(); 192 | ca.v = 60; 193 | bu.v = 40; 194 | bu.p = 20; 195 | ca.drive(); car速度是60 196 | bu.drive(); bus速度是40 197 | bu.carry(); bus载人20 198 | bu.sum(); bus速度是40,bus载人20 199 | (b)重载---------继承了父类的同名有参函数 200 | class bus extend car{ 201 | int p;//人数 202 | void drive(){ 203 | System.out.print("bus速度是" + v); 204 | } 205 | void drive(int v){ 206 | System.out.print("bus速度是" + v); 207 | } 208 | void carry(){ 209 | System.out.print("bus载人" + p); 210 | } 211 | void sum(){ 212 | System.out.print("bus速度是" + v); 213 | System.out.print("bus载人" + p); 214 | } 215 | } 216 | 测试用例 217 | bu.drive(100);输入参数即可,区别只是在于参数不同 218 | (c)重写-------------当前类的同名方法 219 | 通过方法的重写,一个类中可以有多个相同名字的方法,由传递给他们参数的类型和个数不同来区分,注意的是,重写的方法是当前类内的同名 220 | 方法,不是父类中的方法。 221 | class bus extend car{ 222 | int p;//人数 223 | void drive(){ 224 | System.out.print("bus速度是" + v); 225 | } 226 | void carry(){ 227 | System.out.print("bus载人" + p); 228 | } 229 | void carry(int t){ 230 | System.out.print("bus载人" + t); 231 | } 232 | void sum(){ 233 | System.out.print("bus速度是" + v); 234 | System.out.print("bus载人" + p); 235 | } 236 | } 237 | carry和carry(int t)区分调用 238 | (9)接口 239 | java只支持单一继承,为了能够多重继承,我们利用接口实现多重继承 240 | 一个类只有一个直接父类,但可以有多个接口 241 | (a)接口的含义: 242 | 接口是一系列方法的声明,是一些方法特征的集合,接口只有方法的特征没有方法的实现,所以这些方法能在不同的地方被不同的类实现。 243 | 而这些实现可以具有不同的行为(功能) 244 | 关键字:interface 245 | 接口中定义的方法形式如下,成员变量可以被static,final修饰: 246 | public interface 接口名{ 247 | public 返回值类型 方法名(......) 248 | public static int a = 1; 249 | public final int b = 2; 250 | } 251 | (b)一个类要使用某个接口:关键字implements 252 | public class myclass implements 接口名{ 253 | } 254 | (c)接口和抽象类的区别 255 | (1)抽象类可以提供实现方法,接口不行。java中抽象类可以提供某些方法的部分实现,如果向某个抽象类中填入一个新的方法,那么他所有的 256 | 子类都可以拥有这个新方法, 257 | (2)抽象类只能继承一个,但可以实现多个接口 258 | 抽象类的实现只能通过子类给出,但一个子类只能继承一个父类,但是一个子类可以实现任意多个接口。 259 | (d)接口的特点 260 | 1、Java接口中的成员变量默认都是public,static,final类型的(都可省略),必须被显示初始化,即接口中的成员变量为常量(大写,单词之间用"_"分隔) 261 | 262 | 2、Java接口中的方法默认都是public,abstract类型的(都可省略),没有方法体,不能被实例化 263 | 264 | 3、Java接口中只能包含public,static,final类型的成员变量和public,abstract类型的成员方法 265 | 266 | 4、接口中没有构造方法,不能被实例化 267 | 268 | 5、一个接口不能实现(implements)另一个接口,但它可以继承多个其它的接口 269 | 270 | 6、Java接口必须通过类来实现它的抽象方法 271 | 272 | 7、当类实现了某个Java接口时,它必须实现接口中的所有抽象方法,否则这个类必须声明为抽象类 273 | 274 | 8、不允许创建接口的实例(实例化),但允许定义接口类型的引用变量,该引用变量引用实现了这个接口的类的实例 275 | 276 | 9、一个类只能继承一个直接的父类,但可以实现多个接口,间接的实现了多继承. 277 | (10)接口的实现(实例) 278 | interface Flyanimal{ 279 | void fly(); 280 | } 281 | class Insect { 282 | int legnum=6; 283 | } 284 | class Bird { 285 | int legnum=2; 286 | void egg(){}; 287 | } 288 | class Ant extends Insect implements Flyanimal { 289 | public void fly(){ 290 | System.out.println("Ant can fly"); 291 | } 292 | } 293 | class Pigeon extends Bird implements Flyanimal { 294 | public void fly(){ 295 | System.out.println("pigeon can fly"); 296 | } 297 | public void egg(){ 298 | System.out.println("pigeon can lay eggs "); 299 | } 300 | } 301 | public class InterfaceDemo{ 302 | public static void main(String args[]){ 303 | Ant a=new Ant(); 304 | a.fly(); //Ant can fly 305 | System.out.println("Ant's legs are"+ a.legnum); //Ant'slegs are 6 306 | Pigeon p= new Pigeon(); 307 | p.fly(); //pigeon can fly 308 | p.egg(); //pigeon can lay eggs 309 | } 310 | } 311 | (11)面向对象 对象、消息、封装、继承、多态 312 | (a)对象: 313 | 对象是一个模型。比如,你要做一个和建筑相关的软件。那么在整个软件中肯定会经常出现砖,钢筋等这些物体。 314 | 你如果每次遇到都定义一遍砖的属性肯定很麻烦,那么这个时候你就可以定义一个对象---砖,将它的属性封装起来。 315 | 然后每次遇到时候直接调用就可以了。 316 | (b)消息 317 | 书中曰:消息是对象间通信的手段,一个对象通过向另一对象发送消息来请求其服务 318 | (c)封装 319 | 利用访问控制符等等 320 | (1)将事物的内部实现细节隐藏起来 321 | (2)对外提供一致的公共的接口---间接访问隐藏数据 322 | (3)可维护性 323 | (d)继承、多态已经在上面叙述,此处就不细说了。 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | -------------------------------------------------------------------------------- /第6步: -------------------------------------------------------------------------------- 1 | 6. 学习Java的异常处理,但更重要的是学习什么时候用特殊返回值而不使用异常,什么时候应该抛出异常而不处理异常,知道什么是pokemon catch及其 2 | 危害,了解为什么Java的checked exception是一个糟糕的特性。如果愿意,同时学习一下Java1.7的try-with-resource语句和AutoCloseable接口。 3 | 一、java的异常处理 4 | 来,首先对异常有一个大体的了解。 5 | 异常主要是指一种意想不到处理的情况,你感觉没问题但偏偏有问题。如文件找不到、非法的参数、网络失败让你大骂“我曹”的事件。 6 | 它发生在程序运行期间,干扰了程序的正常运行。 |-----stackoverflowerror 7 | |----------------virtualmachinerror| 8 | ----- error| |-----outofmemoryerror 9 | | |-----------------AWTerror 10 | | 11 | throwable| -----eofException 12 | | |----------------------IOException| 13 | | | -----filenotfoundException 14 | ------exception| |------arrithmeticException 15 | | |------missingresourceException 16 | |-----------------runtimeException|------classnotfoundException 17 | |-------nullpointerException 18 | |-------illegalArgumentException 19 | |-------ArrayindexOutofBoundsException 20 | |-------UnkonwnTypeException 21 | 在 Java 中,所有的异常都有一个共同的祖先 Throwable(可抛出)。Throwable 指定代码中可用异常传播机制通过 Java 应用程序传输的任何问题的共性。 22 | Throwable: 有两个重要的子类:Exception(异常)和 Error(错误),二者都是 Java 异常处理的重要子类,各自都包含大量子类。 23 | (1)error 24 | 此类代表程序自身无法解决的错误。主要是jvm出了问题,上面也说了,栈溢出,内存溢出。 25 | (2)exception 26 | 是程序本身可以处理的异常。(error和exception的区别就是是否可以程序本身可以处理). 27 | 异常有一个重要子类为runtimeException,RuntimeException 类及其子类表示“JVM 常用操作”引发的错误,如空值对象引用(NullPointerException) 28 | 除数为0(ArithmeticException)和数组越界(ArrayIndexOutOfBoundException) 29 | (a)java异常(包含error和exception)通常分为可查的异常和不可查的异常 30 | (i)可查的异常(编译器必须处理的异常),除了RuntimeException及其子类以外,其他的Exception类及其子类都属于可查异常。这种异常 31 | 的特点是Java编译器会检查它,要么用try-catch-finally捕获它,要么用throw抛出它. 32 | (ii)不可查异常(包括runtimeException和error),编译器不强制处理的异常。runtimeException能编译,error不一定。 33 | (b)单纯异常(exception)可以分为两类: 34 | (i)运行时异常(RuntimeException)如NullPointerException(空指针异常),这类异常是不检查异常,程序可以选择捕捉,也可以不处理, 35 | 这类异常通常为逻辑上的错误导致的。运行时异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有 36 | 用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过。 37 | (ii)非运行时异常 (编译异常):是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须 38 | 进行处理的异常,如果不处理,程序就不能编译通过。 39 | 40 | (c)处理异常的机制 41 | (i)抛出异常:当一个方法出现错误引发异常时,方法创建异常对象并交付运行时系统,异常对象中包含了异常类型和异常出现时的 42 | 程序状态等异常信息。运行时系统负责寻找处置异常的代码并执行。 43 | (ii) 捕获异常:在方法抛出异常之后,运行时系统将转为寻找合适的异常处理器(exception handler)。 44 | 45 | 对于运行时异常、错误或可查异常,Java技术所要求的异常处理方式有所不同. 46 | (i)对于运行时异常,根据经验,java虚拟机能够自行处理。由于运行时异常的不可查性,当然,数组越界,空值指针引用,都不想,但是很难 47 | 察觉,当然察觉了也就自己改了。这部分java规定,将由Java运行时系统自动抛出,允许应用程序忽略运行时异常。 48 | (ii)对于错误(error), 当方法不进行捕捉(try-catch-finally)时,Java允许该方法不做任何抛出声明。因为error是不被允许出现的 49 | 情况,所以对于合理地程序,不应该对error进行捕捉 50 | (iii)对于所有的可查异常,Java规定:一个方法必须捕捉,或者声明抛出方法之外。也就是说,当一个方法选择不捕捉可查异常时, 51 | 它必须声明将抛出异常。因为该异常是可查的。 52 | 53 | 一个方法所能捕捉的异常,一定是Java代码在某处所抛出的异常。简单地说,异常总是先被抛出,后被捕捉的。 54 | (d)捕获异常的语句 55 | (i)try-catch 56 | try{ 57 | } catch(type1 id1){ 58 | } 59 | catch(trpe2 id2){ 60 | } 61 | 62 | 例1 捕捉throw语句抛出的“除数为0”异常 63 | public class TestException { 64 | public static void main(String[] args) { 65 | int a = 6; 66 | int b = 0; 67 | try { // try监控区域 68 | 69 | if (b == 0) throw new ArithmeticException(); // 通过throw语句抛出异常 70 | System.out.println("a/b的值是:" + a / b); 71 | } 72 | catch (ArithmeticException e) { // catch捕捉异常 73 | System.out.println("程序出现异常,变量b不能为0。"); 74 | } 75 | System.out.println("程序正常结束。"); 76 | } 77 | } 78 | 运行结果:程序出现异常,变量b不能为0。 79 | 80 | 程序正常结束。 81 | 例2 捕捉运行时系统自动抛出“除数为0”引发的ArithmeticException异常。 82 | public static void main(String[] args) { 83 | int a = 6; 84 | int b = 0; 85 | try { 86 | System.out.println("a/b的值是:" + a / b); 87 | } catch (ArithmeticException e) { 88 | System.out.println("程序出现异常,变量b不能为0。"); 89 | } 90 | System.out.println("程序正常结束。"); 91 | } 92 | } 93 | 运行结果:程序出现异常,变量b不能为0。 94 | 95 | 程序正常结束。 96 | (ii) try-catch-finally语句 97 | try { 98 | // 可能会发生异常的程序代码 99 | } catch (Type1 id1) { 100 | // 捕获并处理try抛出的异常类型Type1 101 | } catch (Type2 id2) { 102 | // 捕获并处理try抛出的异常类型Type2 103 | } finally { 104 | // 无论是否发生异常,都将执行的语句块 105 | } 106 | 107 | 例子 108 | public class TestException { 109 | public static void main(String args[]) { 110 | int i = 0; 111 | String greetings[] = { " Hello world !", " Hello World !! ", 112 | " HELLO WORLD !!!" }; 113 | while (i < 4) { 114 | try { 115 | // 特别注意循环控制变量i的设计,避免造成无限循环 116 | System.out.println(greetings[i++]); 117 | } catch (ArrayIndexOutOfBoundsException e) { 118 | System.out.println("数组下标越界异常"); 119 | } finally { 120 | System.out.println("--------------------------"); 121 | } 122 | } 123 | } 124 | } 125 | 126 | 运行结果: 127 | Hello world ! 128 | -------------------------- 129 | Hello World !! 130 | -------------------------- 131 | HELLO WORLD !!! 132 | -------------------------- 133 | 数组下标越界异常 134 | -------------------------- 135 | 136 | 例5中的try语句这样设计 137 | try { 138 | System.out.println (greetings[i]); i++; 139 | } 140 | 会出现死循环.因为i越界,直接被捕获,i没有在++,所以一直死循环 141 | 小结: 142 | try 块:用于捕获异常。其后可接零个或多个catch块,如果没有catch块,则必须跟一个finally块。 143 | catch 块:用于处理try捕获到的异常。 144 | finally 块:无论是否捕获或处理异常,finally块里的语句都会被执行。当在try块或catch块中遇到return语句时,finally语句块将在方法返回之前被执行。在以下4种特殊情况下,finally块不会被执行: 145 | 1)在finally语句块中发生了异常。 146 | 2)在前面的代码中用了System.exit()退出程序。 147 | 3)程序所在的线程死亡。 148 | 4)关闭CPU。 149 | 150 | (e)抛出异常的语句 151 | 如果一个方法可能会出现异常,但没有能力处理这种异常,可以在方法声明处用throws子句来声明抛出异常。 152 | throws语句用在方法定义时声明该方法要抛出的异常类型,如果抛出的是Exception异常类型,则该方法被声明为抛出所有的异常。多个异常 153 | 可使用逗号分割。throws语句的语法格式为: 154 | methodname throws Exception1,Exception2,..,ExceptionN 155 | { 156 | } 157 | 当方法抛出异常列表的异常时,方法将不对这些类型及其子类类型的异常作处理,而抛向调用该方法的方法,由他去处理 158 | 例子 159 | import java.lang.Exception; 160 | public class TestException { 161 | static void pop() throws NegativeArraySizeException { 162 | // 定义方法并抛出NegativeArraySizeException异常 163 | int[] arr = new int[-3]; // 创建数组 164 | } 165 | 166 | public static void main(String[] args) { // 主方法 167 | try { // try语句处理异常信息 168 | pop(); // 调用pop()方法 169 | } catch (NegativeArraySizeException e) { 170 | System.out.println("pop()方法抛出的异常");// 输出异常信息 171 | } 172 | } 173 | 174 | } 175 | 结果: pop方法没有处理异常NegativeArraySizeException,而是由main函数来处理。 176 | 177 | throw抛出异常的规则 178 | 1) 如果是不可查异常(unchecked exception),即Error、RuntimeException或它们的子类,那么可以不使用throws关键字 179 | 来声明要抛出的异常,编译仍能顺利通过,但在运行时会被系统抛出。 180 | 181 | 2)必须声明方法可抛出的任何(敲黑板,重点)可查异常(checked exception)。即如果一个方法可能出现受可查异常,要么用try-catch语句 182 | 捕获,要么用throws子句声明将它抛出,否则会导致编译错误 183 | 184 | 3)仅当抛出了异常,该方法的调用者才必须处理或者重新抛出该异常。当方法的调用者无力处理该异常的时候,应该继续抛出,而不是囫囵吞枣。 185 | 186 | 4)调用方法必须遵循任何可查异常的处理和声明规则。若覆盖一个方法,则不能声明与覆盖方法不同的异常。声明的任何异常必须是被 187 | 覆盖方法所声明异常的同类或子类。 188 | 189 | (f)自定义异常(自定义异常类) 190 | public class myexception extends Exception{ 191 | public myexception(){} 192 | public myexception(String smg){ 193 | super(smg); 194 | System.out.print(smg); 195 | } 196 | } 197 | 使用 198 | public void test() throws myexception{ 199 | try{ 200 | }catch(type ide){ 201 | throw new myexception(); 202 | } 203 | } 204 | 205 | 206 | (3)什么是有用特殊返回值而不适用异常。 207 | 从逻辑上讲,函数的返回值是它的求值结果,是“预期的”、“正常的”结束函数求值得到的结果,而异常是发成错误,函 208 | 数求值无法正常进行的时候必须处理的情况。 209 | 从逻辑上讲,函数的返回值是它的求值结果,是“预期的”、“正常的”结束函数求值得到的结果,而异常是发成错误,函数求值无法正常 210 | 进行的时候必须处理的情况。 211 | 不要随便抛出异常的意思,是指确实是异常情况才抛出异常,有些不属于异常情况,只是单纯的为了方便处理,就不建议抛出异常。举个 212 | 例子,FileNotFoundException,对于一些应用,其确实表示异常,因为系统的某个部分引用了错误的文件信息;而对于一个文件管理应 213 | 用,如果这个文件路径是用户输入的,则该种情况即不属于异常情况,应该明确地告诉给用户:指定的文件路径有误。检查入参是否为空,是否 214 | 需抛出异常,需要视具体情况而定。 215 | (4)什么时候应该抛出异常而不处理异常 216 | 首先注意到一个原则:仅当抛出了异常,该方法的调用者才必须处理或者重新抛出该异常 217 | 当方法的调用者无力处理该异常的时候,应该继续抛出 218 | (5) 知道什么是pokemon catch及其危害 219 | 抱歉这部分找不到 220 | (6)了解为什么Java的checked exception是一个糟糕的特性。 221 | 可查异常:上面已经提到,可查的异常(编译器必须处理的异常),除了RuntimeException及其子类以外,其他的Exception类及其子类都属于可查异常。这种异常 222 | 的特点是Java编译器会检查它,要么用try-catch-finally捕获它,要么用throw抛出它. 223 | 224 | 225 | 226 | 227 | 228 | 229 | -------------------------------------------------------------------------------- /第7步: -------------------------------------------------------------------------------- 1 | 熟悉Java常用的数据结构,如基本的数组类型,以及泛型容器(java.util.*),尤其是java.util.List接口 2 | 和java.util.ArrayList实现;以及java.util.Map接口和java.util.HashMap实现。(java1.5以前的没有泛 3 | 型参数的就不用碰了)同时留意一下基本类型int, double等和装箱类型Integer和Double的区别,以及它们是如何自动转换的。 4 | 5 | java 常用的数据结构主要分为collection和map两种接口,常用的数据结构主要是继承来自这些接口的数据结构类。 6 | Collection---->Collections Map----->SortedMap------>TreeMap 7 | Collection---->List----->(Vector \ ArryList \ LinkedList) Map------>HashMap----->linkedhashmap 8 | Collection---->Set------>(HashSet \ LinkedHashSet \ SortedSet) 9 | 10 | interfaces hashtable resizable array tree linked list hash table + linked list 11 | Set HashSet Treeset LinkedHashSet 12 | List ArrayList LinkedList 13 | Queue 14 | Map HashMap TreeMap LinkedHashMap 15 | 16 | HashMap无序 17 | LinkedHashMap有序 18 | (1)java vector(java.util.vector) 19 | 可实现自动增长的对象数组。嗯,动态数组。 20 | 向量类提供了三种构造方法: 21 | public vector() 22 | public vector(int initialcapacity,int capacityIncrement) 23 | public vector(int initialcapacity) 24 | 使用第一种方法系统会自动对向量进行管理,若使用后两种方法。则系统将根据参数,initialcapacity设定向量对象的 25 | 容量(即向量对象可存储数据的大小),当真正存放的数据个数超过容量时。系统会扩充向量对象存储容量。 26 | 参数capacityincrement给定了每次扩充的扩充值。当capacityincrement为0的时候,则没次扩充一倍,利用这个功能可以优化 27 | 存储。在Vector类中提供了各种方法方便用户的使用 28 | 插入功能,插入整数1时不要直接v1.addElement(1) 29 | (a)Vector v1 = new Vector(); 30 | Integer integer1 = new Integer(1); 31 | v1.addElement(integer1); 32 | 覆盖功能 33 | (b)public final synchronized void setElementAt(Object obj,int index) 34 | 将index处的对象设置成obj,原来的对象将被覆盖。 35 | vector里面存的是对象类型。 36 | (c)public final synchronized void insertElementAt(Object obj,int index) 37 | 在index指定的位置插入obj,原来对象以及此后的对象依次往后顺延。 38 | 删除功能 39 | (d) public final synchronized void removeElement(Object obj) 40 | 从向量中删除obj,若有多个存在,则从向量头开始试,删除找到的第一个与obj相同的向量成员。 41 | (e)public final synchronized void removeAllElement(); 42 | 删除向量所有的对象 43 | (f)public fianl synchronized void removeElementAt(int index) 44 | 删除index所指的地方的对象 45 | 查找功能 46 | (g)public final int indexOf(Object obj) 47 | 从向量头开始搜索obj,返回所遇到的第一个obj对应的下标,若不存在此obj,返回-1. 48 | (h)public final synchronized int indexOf(Object obj,int index) 49 | 从index所表示的下标处开始搜索obj. 50 | (i)public final int lastindexOf(Object obj) 51 | 从向量尾部开始逆向搜索obj. 52 | (k)public final synchornized int lastIndex(Object obj,int index) 53 | 从index所表示的下标处由尾至头逆向搜索obj. 54 | (l)public final synchornized firstElement() 55 | 获取向量对象中的首个obj 56 | (m)public final synchornized Object lastElement() 57 | 获取向量对象的最后一个obj 58 | (n)类vector定义了方法 59 | public final int size(); 60 | 此方法用于获取向量元素的个数。它们返回值是向量中实际存在的元素个数,而非向量容量。可以调用方法capacity()来获取容量值。 61 | (o)取出元素 62 | s.elementAt(1) 返回的是第二个元素 63 | (2)java ArrayList(java.util.*) 64 | 它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能,提供了随机访问功能,ArrayList 本身就没有去重的功能 65 | ArrayList的API 66 | // Collection中定义的API 67 | ArrayList list = new ArrayList(); 68 | boolean add(E object) list.add(1) 69 | 70 | boolean addAll(Collection collection) ArrayList arrlist = new ArrayList(); 71 | arrlist.add(2); 72 | arrlist.add(3); 73 | ArrayList arrlist2 = new ArrayList(); 74 | arrlist2.add(2); 75 | arrlist2.add(3); 76 | arrlist.addAll(arrlist2); 77 | 在小数据量时,for循环效率高,大数据量时addAll方法效率高: 78 | 1.使用内存拷贝,移动数据。 79 | 2.本地函数,执行效率高 80 | 81 | void clear() list.clear();后清空所有元素,size()为0 82 | 83 | boolean contains(Object object) 如果此列表包含指定的元素,此方法返回true。 84 | 85 | boolean containsAll(Collection collection) containsAll方法用于判断列表中是否包含指定collection的所有元素。 86 | arrlist2.containsAll(arrlist); 87 | 88 | boolean equals(Object object) ArrayList的equals方法,两个size()相等的list,在相同order中的 89 | 对象相等,则两个lisit相等,如果只想比较两个 list中的元素是否相等, 90 | 不考虑order,那么先用Collections.sort()方法对list进行排序再进 91 | 行比较。。。 92 | 93 | int hashCode() 这个貌似hashset用的多(待定) 94 | 95 | boolean isEmpty() 判定是否为空 96 | 97 | Iterator iterator() 迭代器 98 | 99 | boolean remove(Object object) 在项目开发中,我们可能往往需要动态的删除ArrayList中的一些元素。 100 | 这种方法是错误的 101 | for(int i = 0 , len= list.size();i collection) list.removeAll(sub2); 117 | 是删出list中包含sub2中的元素的。 118 | boolean retainAll(Collection collection) 119 | int size() 120 | T[] toArray(T[] array) 第二种方法是将list转化为你所需要类型的数组, 121 | String[] array =new String[list.size()]; 122 | list.toArray(array); 123 | Object[] toArray() 是将list直接转为Object[] 数组; 124 | // AbstractCollection中定义的API 125 | void add(int location, E object) 126 | boolean addAll(int location, Collection collection) 127 | E get(int location) 128 | int indexOf(Object object) 129 | int lastIndexOf(Object object) 130 | ListIterator listIterator(int location) 131 | ListIterator listIterator() 132 | E remove(int location) 133 | E set(int location, E object) 134 | List subList(int start, int end) 135 | // ArrayList新增的API 136 | Object clone() 137 | void ensureCapacity(int minimumCapacity) 138 | void trimToSize() 139 | void removeRange(int fromIndex, int toIndex) 140 | 141 | (3)LinkedList 142 | 网页:http://blog.csdn.net/jdsjlzx/article/details/41654295 143 | LinkedList类是双向列表,列表中的每个节点都包含了对前一个和后一个元素的引用. 144 | LinkedList lList = new LinkedList(); 145 | 添加: 146 | lList.add("1"); 147 | 第一个和最后一个元素 148 | lList.getFirst() 149 | lList.getLast() 150 | 获取链表元素 151 | for (String str: lList) { 152 | System.out.println(str); 153 | } 154 | 也可以把链表当初栈或者队列来处理: 155 | public boolean addFirst(Object element) 156 | public boolean addLast(Object element) 157 | 删除元素 158 | list.removeFirst(); 159 | list.removeLast(); 160 | 删掉所有元素:清空LinkedList 161 | lList.clear(); 162 | 根据范围删除列表元素 163 | lList.subList(2, 5).clear(); 164 | 删除链表的特定元素 165 | lList.remove("2")删除元素值=2的元素 166 | lList.remove(2); //删除第2个元素,链表 167 | 将LinkedList转换为数组,数组长度为0 168 | String[] my = theList.toArray(new String[0]) 169 | 将LinkedList转换为数组,数组长度为链表长度 170 | String[] my = theList.toArray(new String[theList.size()]); 171 | 查找位置 172 | .indexOf("2") 173 | 替换元素 174 | lList.set(3, "Replaced");//使用set方法替换元素,方法的第一个参数是元素索引,后一个是替换值 175 | 确认链表是否存在特定元素 176 | lList.contains("4") 177 | 178 | 179 | 180 | Map: 181 | HashMap 182 | 当我们把 Java 对象放入数组之时,并不是真正的把 Java 对象放入数组中,只是把对象的引用放入数组中,每个数组元素都是一个引用变量。 183 | HashMap hashMap = new HashMap(); 184 | hashmap.containsKey() 185 | hashmap.get() 186 | 存储: 187 | map.put("语文" , 80.0); 188 | map.put("数学" , 89.0); 189 | map.put("英语" , 78.2); 190 | 读取: 191 | .get(key) 192 | 遍历 193 | 第一种: 194 |   Map map = new HashMap(); 195 |   Iterator iter = map.entrySet().iterator(); 196 |   while (iter.hasNext()) { 197 |   Map.Entry entry = (Map.Entry) iter.next(); 198 |   Object key = entry.getKey(); 199 |   Object val = entry.getValue(); 200 |   } 201 |   效率高,以后一定要使用此种方式! 202 | 第二种: 203 |   Map map = new HashMap(); 204 |   Iterator iter = map.keySet().iterator(); 205 |   while (iter.hasNext()) { 206 |   Object key = iter.next(); 207 |   Object val = map.get(key); 208 |   } 209 |   效率低,以后尽量少使用! 210 | 211 | TreeMap 212 | 网址:http://blog.csdn.net/chenssy/article/details/26668941 213 | hashmap是没有顺序的,而treemap则是按顺序排列的哦!! 214 | HashMap通过hashcode对其内容进行快速查找,而TreeMap中所有 215 | 的元素都保持着某种固定的顺序,如果你需要得到一个有序的结果你就应该使用TreeMap(HashMap中元素的排列顺序是不固定的)。 216 | 红黑树(待定) 217 | 218 | Set 219 | HashSet 220 | set中不能存储重复的元素,可以存储null元素。如果需要存储多个重复元素,需要使用List。 221 | 它不允许出现重复元素; 222 | 不保证和政集合中元素的顺序 223 | 允许包含值为null的元素,但最多只能有一个null元素。 224 | boolean add(E o) 225 | 如果此集合中还不包含指定元素,则添加指定元素。 226 | void clear() 227 | 从此集合中移除所有元素。 228 | Object clone() 229 | 返回此 HashSet 实例的浅表复制:并没有克隆这些元素本身。 230 | boolean contains(Object o) 231 | Returns 如果此集合不包含指定元素,则返回 true。 232 | boolean isEmpty() 233 | 如果此集合不包含任何元素,则返回 true。 234 | Iterator iterator() 235 | 返回对此集合中元素进行迭代的迭代器。 236 | boolean remove(Object o) 237 | 如果指定元素存在于此集合中,则将其移除。 238 | int size() 239 | 返回此集合中的元素的数量(集合的容量) 240 | 241 | LinkedHashSet 242 | 类HashSet和LinkedHashSet都是接口Set的实现,两者都不能保存重复的数据。主要区别是HashSet不保证集合中元素的顺序,即不能保证迭代的顺序与插入的顺序一致。 243 | 而LinkedHashSet按照元素插入的顺序进行迭代,即迭代输出的顺序与插入的顺序保持一致。 244 | HashSet hs = new HashSet(); 245 | hs.add("B"); 246 | hs.add("A"); 247 | hs.add("D"); 248 | hs.add("E"); 249 | hs.add("C"); 250 | hs.add("F"); 251 | System.out.println("HashSet 顺序:\n"+hs); 252 | HashSet 顺序: 253 | [D, E, F, A, B, C] 254 | 255 | LinkedHashSet lhs = new LinkedHashSet(); 256 | lhs.add("B"); 257 | lhs.add("A"); 258 | lhs.add("D"); 259 | lhs.add("E"); 260 | lhs.add("C"); 261 | lhs.add("F"); 262 | System.out.println("LinkedHashSet 顺序:\n"+lhs); 263 | LinkedHashSet 顺序: 264 | [B, A, D, E, C, F] 265 | 266 | TreeSet 267 | HashSet:哈希表是通过使用称为散列法的机制来存储信息的,元素并没有以某种特定顺序来存放; 268 | * LinkedHashSet:以元素插入的顺序来维护集合的链接表,允许以插入的顺序在集合中迭代; 269 | * TreeSet:提供一个使用树结构存储Set接口的实现,对象以升序顺序存储,访问和遍历的时间很快。 270 | TreeSet ts = new TreeSet(); 271 | ts.add("B"); 272 | ts.add("A"); 273 | ts.add("D"); 274 | ts.add("E"); 275 | ts.add("C"); 276 | ts.add("F"); 277 | System.out.println("TreeSet 顺序:\n"+ts); 278 | TreeSet 顺序: 279 | [A, B, C, D, E, F] 280 | 281 | 282 | 283 | java中栈类的使用 284 | 2-->public boolean empty()测试堆栈是否为空; 285 | 286 | 3-->public E pop()移除堆栈顶部的对象,并作为此函数的值返回该对象。 287 | 288 | 4-->public E push(E item)把项压入堆栈顶部 289 | 290 | 5-->public E peek()查看堆栈顶部的对象,但不从堆栈中移除它。 291 | 292 | 6-->public boolean empty()测试堆栈是否为空 293 | 294 | Stack sk = new Stack(); 295 | 296 | 297 | 298 | -------------------------------------------------------------------------------- /第8步: -------------------------------------------------------------------------------- 1 | 第8步:8. 熟悉Java标准库里的各种工具,包括日期时间、字符串格式化、IO等。知道文件要自己 2 | 在finally子句中close(),或者用Java1.7的try-with-resource,不要妄想垃圾回收器会帮你关掉文件。 3 | -------------------------------------------------------------------------------- /路线图.txt: -------------------------------------------------------------------------------- 1 | Java学习路线 2 | 【以下肯定是不完整的列表,欢迎补充】 3 | 4 | 【好像还缺什么:缓存技术。欢迎补充】 5 | 6 | Java是一个通用的编程语言,其实可以干很多事,怎么学Java就看怎么用了。 7 | 8 | 但有一些一般的步骤: 9 | 10 | 1. 熟悉一种文本编辑器,比如Vim, Emacs, Notepad++, TextMate等。知道哪些是开源的,哪些是闭源的,哪些要收费。养成不用盗版软件的习惯。 11 | 2. 安装JDK(建议用你的Linux发行版自带的软件包管理器安装openjdk,过程中可能需要读发行版特定的文档) 12 | 3. 写一个Java的Hello world程序,并用命令行工具javac编译,再用java命令运行这个程序。过程中熟悉源代码、字节码、虚拟机这些东西,以及Java的包(package)对.class文件所在的路径的影响。 13 | 3.5. 如果这两个命令行工具使用熟练了,可以开始选一个喜欢的集成开发环境,比如Eclipse。当然,养成不用盗版软件的习惯。熟悉一下如何建立“工程”,以及快捷键的使用。 14 | 4. 学习Java的面向过程编程,包括基本数据结构、表达式、语句、控制流、函数调用。 15 | 5. 学习Java的面向对象编程,包括类、修饰符、引用类型和值类型的区别、成员、方法、访问控制、继承、多态、接口、接口实现。顺便学习一下面向对象的基本思想,即对象、消息、封装、继承、多态等,这些通用的内容不是Java特有的。 16 | 5.5 这时候应该已经涉及了Java的垃圾回收。要留意即使有垃圾回收的情况下也会发生的内存泄露(如自己设计数组容器,元素是引用,逻辑上删除了元素,但并没有清成null)。注意垃圾回收只能回收内存中的对象,除了内存以外,其它资源不能依靠垃圾回收来关闭。比如,文件、管道、Socket、数据库连接等,垃圾回收是不会帮你关闭的。 17 | 6. 学习Java的异常处理,但更重要的是学习什么时候用特殊返回值而不使用异常,什么时候应该抛出异常而不处理异常,知道什么是pokemon catch及其危害,了解为什么Java的checked exception是一个糟糕的特性。如果愿意,同时学习一下Java1.7的try-with-resource语句和AutoCloseable接口。 18 | 7. 熟悉Java常用的数据结构,如基本的数组类型,以及泛型容器(java.util.*),尤其是java.util.List接口和java.util.ArrayList实现;以及java.util.Map接口和java.util.HashMap实现。(java1.5以前的没有泛型参数的就不用碰了)同时留意一下基本类型int, double等和装箱类型Integer和Double的区别,以及它们是如何自动转换的。 19 | 8. 熟悉Java标准库里的各种工具,包括日期时间、字符串格式化、IO等。知道文件要自己在finally子句中close(),或者用Java1.7的try-with-resource,不要妄想垃圾回收器会帮你关掉文件。 20 | 9. 学习一下Java的命名习惯,以及JavaBeans的常规,知道为什么getter/setter比直接操作成员变量好。按这种方式给Java的变量、方法命名。同时看看你的IDE能不能自动帮你生成getter和setter。 21 | 10. 使用一个第三方的库(比如Apache Commons Lang通用工具库),让你的程序依赖于它的二进制jar包(而不是直接拷贝源代码),用命令行编译、运行(注意classpath等);也熟悉一下如何用你的集成开发环境添加第三方依赖。感受一下手动管理依赖关系的麻烦。 22 | 11. 学习Maven的使用,试着让Maven帮你解决依赖关系,再试着让Maven帮你创建一个Eclipse工程。再试试用Maven打包发布。 23 | 12. 学习软件测试,以及JUnit的使用,以及怎么在IDE中使用JUnit。有空看一下coverage工具。 24 | 13. 读读四人帮写的《设计模式》(这本书是用C++和Smalltalk语言为例子的,但仍然适合Java)。具体的是这本书, http://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612 图书馆应该能还借到英文原版,因为我借到过。 25 | 26 | 接下来就看具体要做哪方面的应用了,看需求。比如(下面的没有顺序) 27 | 28 | 【....】如果学Java学得不舒服了,学Python。 29 | * 如果对面向对象编程的概念有点不习惯,学Smalltalk。(Ruby也行,但不如Smalltalk经典。Ruby的文档是一大硬伤。) 30 | * 如果嫌Java太啰嗦,学Python 31 | * 如果嫌Java太啰嗦,又想用JVM,自己又有精力,学Scala 32 | * 如果对对象之间的关系有点晕,学一学UML,以及它的一些图,可以对程序和运行进行直观的建模。你的IDE也许有插件可以生成UML图。但是不要太沉迷于这些方法论。 33 | 34 | 【调试和辅助工具】学习一下你的集成开发环境提供的调试工具,加一些断点试试 35 | * 试试用jconsole或者VisualVM监控另一个jvm的状态。 36 | * 用profiling工具寻找程序中慢的地方。Eclipse有profiling工具。VisualVM也有这样的功能。(如果不介意使用闭源软件的话,也试试JProfiler和YourKit) 37 | * 有的JVM允许在运行时更新代码。Eclipse可以和某些JVM集成。这样你可以频繁修改代码而不用频繁重启JVM。对于某些“重型”工程很有用。(如果不介意使用闭源软件的话,也试试jRebel) 38 | 39 | 【多线程】先读Oracle的Java Tutorial里的这一章,请自始至终关注“happens-before“这个词,学完以后应该可以写出正确的并发程序了。 40 | * 抽空顺便了解一下多核处理器、缓存、内存的关系,以及CPU内部的内存读写指令的重排序等细节,体会一下为什么多线程编程这么难。 41 | * 学习Java的多线程编程接口,主要是lock、condition的用法 42 | * Java里每个对象都可以用synchronized语句同步,有wait, notify方法,但这套机制有缺陷。 43 | * 看看java.util.concurrent.lock里的Lock和Condition接口,看它们是怎么解决这些缺陷的。 44 | * 学完之后,学学用Future和Promise来同步,而不是手动用lock和condition。 45 | * 还有semaphore、cyclic barrier、count-down latch、phaser等高级同步工具,或许可以少重新发明一些轮子。 46 | * 还有BlockingQueue 47 | * 学习一下如何让线程停下来,以及为什么要频繁确认isInterrupted()而不要用Thread.stop()(看Java API里Thread.stop()的文档)。 48 | * 如果还舒服,学习一下用Runnable来封装“任务”而不是“线程”,并用Java自带的ThreadPoolExecuter、ForkJoinPool等工具帮你管理线程。 49 | * 应该已经留意到java.util里面的很多容器不是线程安全的,但是java.util.Collections可以帮你创建一些安全的版本。另外关注一下java.util.concurrent里面有ConcurrentMap等容器可供使用。 50 | * 如果有空的话,看看memory model(内存一致性模型)和无锁同步(见java memory model和java.util.concurrent.atomic)。 51 | * 如果还有空,再了解一下除了“共享内存多线程编程”以外有没有别的模型(多进程multi-processing、消息传递message passing等)。或许这时候你已经爱上BlockingQueue了。 52 | 53 | 【反射、元编程】学习Java的反射机制,以及Annotation的用法。 54 | * 如果还舒服,试试java.lang.reflect.Proxy的用法。 55 | * 如果仍然还舒服,玩一玩CGLib(一个第三方的库)。 56 | 57 | 【网络编程】学习一下IP, TCP协议(计算机专业的应该学过,复习一下),学习Socket编程(注意垃圾回收器不会帮你关掉Socket)。 58 | 1. 如果不是很关心HTTP,看看java.nio,学习单线程轮询式IO复用(Selector)。 59 | * 如果有点不明白nio的意图的话,了解一下c10k问题。 http://www.kegel.com/c10k.html 60 | * 如果身体没有异样的话,大概了解一下操作系统(包括C语言)提供的select, poll, epoll, kqueue等接口。 61 | * 如果身体仍然没有异样的话,试着用java.nio写一个文件服务器。 62 | * 如果还有精力的话,上网扒一扒有没有其他的通信库,如netty等。 63 | 2. 如果关心Web还有HTTP,就学习一下HTTP协议,以及用Java进行HTTP的客户端编程。 64 | * 如果还舒服,学学HTML,写写HTML的静态网页(不需要Java) 65 | * 如果还舒服,用Java写一个基于DOM、XPath或者CSS Selector的网页解析器(爬网页)。 66 | * 如果还舒服,学学Java的Servlet接口(先别学jsp)进行Web服务器端编程。学学标准的Servlet容器怎么用,包括web.xml的用法以及listener、filter等概念。以及某个Servlet容器(如Jetty或者Tomcat)的具体用法。 67 | * 如果仍然还舒服,试着学一种模板语言(如haml, velocity, freemarker,【还有其他更好的框架吗?我不知道】, String.format,如果真的想学JSP的话JSP倒是也行,但不推荐)。 68 | * 如果仍然觉得舒服,学学Spring框架中的Web框架,或者Struts,看你的口味。 69 | * 如果还舒服,看看Spring Bean Container以及里面各种乱七八糟的工具。 70 | * 如果还舒服,或者有需求,了解一下什么是RESTful Web Service,复习一下HTTP,找找适合的Java工具。 71 | * 你可能会觉得Jackson是一个解析JSON用的好用的东西。 72 | 73 | 【数据库】学习一下关系数据库(计算机专业的应该学过,复习一下),包括SQL。选一个数据库管理系统熟悉一下(比如MariaDB,或者(如果你不讨厌Oracle的话)用被Oracle收购了的MySQL。先脱离Java单独学学)。然后看它们的官方文档教你怎么用Java连接这种数据库。这中间会涉及到JDBC接口。同时一定要知道SQL注入安全漏洞,以及掌握如何用PreparedStatement防止注入!!。建议看 http://bobby-tables.com/ 74 | * 可能中间会涉及“事务”问题,让你不知不觉地开始去了解java transaction api(JTA)。 75 | * 如果还舒服,学一学对象关系转换(如Hibernate)。 76 | * 也可以学学非关系数据库,以及如何用Java访问它们。 77 | 78 | 【日志记录】学习一下slf4j和logback的用法。 79 | * 如果有精力的话,大概了解一下世界上有多少种Java日志框架,以及slf4j是怎么桥接这些框架的。 80 | 81 | 【构建(build)系统】学习一下Ant的用法。 82 | * 如果还舒服的话,学习一下用Ivy从Maven的仓库里下载软件包,解决依赖关系。 83 | 84 | 【版本控制】学习一种分布式版本控制器(如Git、Mercurial、Bzr、Darcs等,推荐Git)的基本用法,以及如何用它管理Java工程。希望你已经开始使用Maven了,并且知道为什么把IDE生成的工程文件(如eclipse的.project,.classpath和.metadata)放入版本控制器不好。然后了解一下如何在IDE中使用版本控制(Eclipse自带Git插件)。 85 | * 如果感觉很舒服的话,为你们实验室搭建一个Linux+SSH+Git服务器,装个GitLab(一种Web界面)。 86 | * 了解“集中式版本控制器”和“分布式版本控制器”的区别,并说服同事们不要再用SVN、CVS或者SourceSafe等老旧的“集中式版本控制器”了。 87 | * 开设一个GitHub账户。如果你不喜欢Git,就用BitBucket等。 88 | 89 | 【持续集成】自己(或者为你们实验室)搭建一个持续集成(Continuous Integration)服务器,如Jenkins,定期编译你的程序。建议同时使用Git等分布式版本控制器。 90 | * 如果你做开源软件,试试GitHub和Travis。 91 | 92 | 【零碎工具】淘一淘java.nio.files里面有什么好用的东东,然后再淘一淘Apache Commons Lang和Commons IO里有什么好用的工具。Commons Logging就不要再用了,用SLF4j和Logback。 93 | 94 | 【XML】学学XML、DOM、XPath。XML这东西到处都可能用到。也可以用它写自己的配置文件。 95 | * 如果觉得不舒服了,就学学JSON和YAML。 96 | * 如果还是不舒服,就学学文本文件解析。 97 | 98 | 【语法分析和编译器】学学Antlr或者别的Parser Generator的用法 99 | * 如果觉得舒服,自己写一个计算器。 100 | * 如果还觉得舒服,自己写一种Domain-Specific Language (DSL)。 101 | 102 | 【高效容器】学学FastUtil或者Trove,如果你需要进行大量数值运算的话。 103 | 104 | 【分布式计算】学学MapReduce的思想以及它的实现。 105 | * 如果还舒服,学学Scala语言以及号称比MapReduce快得多的Apache Spark。 106 | 107 | 【进程间通信】看看ActiveMQ、MINA和RMI。 108 | 109 | 【其他语言(JVM)】学习另一门跑在JVM上的语言或者实现(如Groovy、Scala、Clojure、JRuby、Jython、JavaScript……) 110 | * 如果还舒服,学一学Java Scripting API(注意不是JavaScript。给个链接:http://docs.oracle.com/javase/6/docs/technotes/guides/scripting/programmer_guide/ ),然后试着在自己的Java程序里嵌入脚本。 111 | 112 | 【其他语言(非JVM)】学习另一门通用脚本语言(如Python、Ruby,其实perl也行,但不推荐),知道什么时候Java不是最好的选择。 113 | 114 | 【Java语言和Java虚拟机】通读一遍(一目十行地读,不用细读)Java Language Specification,以及Java Virtual Machine Specification。 115 | * 了解以下解释器(interpreter)、编译器(compiler)、即时编译器(just-in-time compiler)和优化器(optimiser)的概念。 116 | * 如果对编译器的话题不感到畏惧,了解一下method JIT和tracing JIT的概念和区别。 117 | 118 | 【内存管理】学学垃圾回收的几种基本算法,包括mark-sweep、mark-compact、semi-space、generational、mark-region等,各自的性能,以及为什么朴素的reference counting是不完整的。知道为什么finalizer性能很糟糕,而且标准并不要求finalizer在程序退出前一定会执行。 119 | * 如果还舒服,了解一下如何设置Java虚拟机的堆大小限制(如HotSpot虚拟机的-Xmx选项等)。 120 | * 了解一下Java里的WeakReference以及SoftReference和PhantomReference,以及它们什么时候有用,以及为什么它们实现起来有些困难。 121 | * 如果有精力,了解一下Hotspot虚拟机的内存管理算法是什么样的。 122 | 123 | 【动态装载】学学Java的动态装载(class loading) 124 | * 如果还舒服的话,学学OSGI以及它的一种实现(如Felix或者Equinox) 125 | * 如果仍然很舒服的话,学学写基于Eclipse平台的程序。不是Eclipse集成开发环境,只是利用他们的图形框架,写自己的应用程序。 126 | * 如果还觉得舒服的话,写Eclipse集成开发环境的插件。 127 | 128 | 【本地/外语接口】学习一下Java Native Interface(JNI),试着写一个Java和C语言混合编程的程序。 129 | * 如果觉得不舒服了或者觉得欲仙欲死,就学一学Java Native Access(JNA),试一试不用任何胶水代码而从Java直接装载C库,直接调用C函数。 130 | * 如果连JNA也懒得学,就学一学SWIG,自动生成绑定。 131 | * 如果觉得舒服,就学一学Java Virtual Machine Tooling Interface(JVMTI),用C程序来监视JVM的状态。 132 | 133 | 【密码学】学一学密码学,包括编码、密码分析、攻击、对称密钥、公钥系统、数字签名、哈希算法等,看看Java有没有实现。 134 | * 如果觉得有点不舒服(你应该不会觉得舒服吧,除非你是学密码学的,要不然总会觉得自己写的程序有安全漏洞),就写一个“人品计算器”来放松一下,要求每次输入同一个姓名,得到的人品值是固定的,但又要让人无法用别人的人品值猜自己的人品值。 135 | 136 | 【移动终端】学一学Android开发。 137 | * 如果有精力的话,看看Dalvik虚拟机是怎么回事。 138 | * 建议买一个iPhone或者iPad,或许你再也不想用Android手机或者平板了。 139 | 140 | 【历史】如果觉得有些无聊,就去挖一挖历史上比较火的可能和Java相关技术,比如: 141 | * Applet,想想它比起html5+css3+javascript的缺点在哪里。 142 | * AWT、Swing,想想为什么很少有人用Java写图形界面程序。你觉得Swing的程序看上去舒服吗?中国人和残疾人喜欢用Swing程序吗? 143 | * JNDI,想想它比起Spring Bean Container的缺点在哪里。 144 | * JSP,想想它比起MVC结构的缺点在哪里。 145 | * WSDL/SOAP,把它们和XML-RPC、RESTful Web Service比较一下。 146 | * XSLT,以及为什么它是图灵完备的。可是它真的比Java本身更好用吗? 147 | * Log4j、java.util.logging、Apache Commons Logging,各自有什么问题,以及Log4j的作者本人为什么又开发了SLF4j和Logback? 148 | * Java最早是为什么设计的? 149 | * Type erasure是怎么回事?为什么ArrayList不行但ArrayList就可以?挖一挖历史。 150 | --------------------------------------------------------------------------------