第一篇:Java的内存泄漏 总结分析
Java的内存泄漏
欧阳辰(yeekee@sina.com), 周欣(mailto:zhouxin@sei.pku.edu.cn), 简介: Java的一个重要优点就是通过垃圾收集器(Garbage Collection,GC)自动管理内存的回收,程序员不需要通过调用函数来释放内存。因此,很多程序员认为Java不存在内存泄漏问题,或者认为即使有内存泄漏也不是程序的责任,而是GC或JVM的问题。其实,这种想法是不正确的,因为Java也存在内存泄露,但它的表现与C++不同。本文的标签: j2se, java, java内存泄露, 内存, 内存泄漏, 内存泄露 标记本文!
发布日期: 2002 年 10 月 21 日 级别: 初级
访问情况: 18862 次浏览
评论: 4(查看 | 添加评论-登录)平均分(59个评分)为本文评分
问题的提出
Java的一个重要优点就是通过垃圾收集器(Garbage Collection,GC)自动管理内存的回收,程序员不需要通过调用函数来释放内存。因此,很多程序员认为Java不存在内存泄漏问题,或者认为即使有内存泄漏也不是程序的责任,而是GC或JVM的问题。其实,这种想法是不正确的,因为Java也存在内存泄露,但它的表现与C++不同。
随着越来越多的服务器程序采用Java技术,例如JSP,Servlet,EJB等,服务器程序往往长期运行。另外,在很多嵌入式系统中,内存的总量非常有限。内存泄露问题也就变得十分关键,即使每次运行少量泄漏,长期运行之后,系统也是面临崩溃的危险。
回页首
Java是如何管理内存
为了判断Java中是否有内存泄露,我们首先必须了解Java是如何管理内存的。Java的内存管理就是对象的分配和释放问题。在Java中,程序员需要通过关键字new为每个对象申请内存空间(基本类型除外),所有的对象都在堆(Heap)中分配空间。另外,对象的释放是由GC决定和执行的。在Java中,内存的分配是由程序完成的,而内存的释放是有GC完成的,这种收支两条线的方法确实简化了程序员的工作。但同时,它也加重了JVM的工作。这也是Java程序运行速度较慢的原因之一。因为,GC为了能够正确释放对象,GC必须监控每一个对象的运行状态,包括对象的申请、引用、被引用、赋值等,GC都需要进行监控。
监视对象状态是为了更加准确地、及时地释放对象,而释放对象的根本原则就是该对象不再被引用。
为了更好理解GC的工作原理,我们可以将对象考虑为有向图的顶点,将引用关系考虑为图的有向边,有向边从引用者指向被引对象。另外,每个线程对象可以作为一个图的起始顶点,例如大多程序从main进程开始执行,那么该图就是以main进程顶点开始的一棵根树。在这个有向图中,根顶点可达的对象都是有效对象,GC将不回收这些对象。如果某个对象(连通子图)与这个根顶点不可达(注意,该图为有向图),那么我们认为这个(这些)对象不再被引用,可以被GC回收。
以下,我们举一个例子说明如何用有向图表示内存管理。对于程序的每一个时刻,我们都有一个有向图表示JVM的内存分配情况。以下右图,就是左边程序运行到第6行的示意图。
Java使用有向图的方式进行内存管理,可以消除引用循环的问题,例如有三个对象,相互引用,只要它们和根进程不可达的,那么GC也是可以回收它们的。这种方式的优点是管理内存的精度很高,但是效率较低。另外一种常用的内存管理技术是使用计数器,例如COM模型采用计数器方式管理构件,它与有向图相比,精度行低(很难处理循环引用的问题),但执行效率很高。
回页首
什么是Java中的内存泄露
下面,我们就可以描述什么是内存泄漏。在Java中,内存泄漏就是存在一些被分配的对象,这些对象有下面两个特点,首先,这些对象是可达的,即在有向图中,存在通路可以与其相连;其次,这些对象是无用的,即程序以后不会再使用这些对象。如果对象满足这两个条件,这些对象就可以判定为Java中的内存泄漏,这些对象不会被GC所回收,然而它却占用内存。
在C++中,内存泄漏的范围更大一些。有些对象被分配了内存空间,然后却不可达,由于C++中没有GC,这些内存将永远收不回来。在Java中,这些不可达的对象都由GC负责回收,因此程序员不需要考虑这部分的内存泄露。
通过分析,我们得知,对于C++,程序员需要自己管理边和顶点,而对于Java程序员只需要管理边就可以了(不需要管理顶点的释放)。通过这种方式,Java提高了编程的效率。
因此,通过以上分析,我们知道在Java中也有内存泄漏,但范围比C++要小一些。因为Java从语言上保证,任何对象都是可达的,所有的不可达对象都由GC管理。
对于程序员来说,GC基本是透明的,不可见的。虽然,我们只有几个函数可以访问GC,例如运行GC的函数System.gc(),但是根据Java语言规范定义,该函数不保证JVM的垃圾收集器一定会执行。因为,不同的JVM实现者可能使用不同的算法管理GC。通常,GC的线程的优先级别较低。JVM调用GC的策略也有很多种,有的是内存使用到达一定程度时,GC才开始工作,也有定时执行的,有的是平缓执行GC,有的是中断式执行GC。但通常来说,我们不需要关心这些。除非在一些特定的场合,GC的执行影响应用程序的性能,例如对于基于Web的实时系统,如网络游戏等,用户不希望GC突然中断应用程序执行而进行垃圾回收,那么我们需要调整GC的参数,让GC能够通过平缓的方式释放内存,例如将垃圾回收分解为一系列的小步骤执行,Sun提供的HotSpot JVM就支持这一特性。下面给出了一个简单的内存泄露的例子。在这个例子中,我们循环申请Object对象,并将所申请的对象放入一个Vector中,如果我们仅仅释放引用本身,那么Vector仍然引用该对象,所以这个对象对GC来说是不可回收的。因此,如果对象加入到Vector后,还必须从Vector中删除,最简单的方法就是将Vector对象设置为null。
Vector v=new Vector(10);for(inti=1;i<100;i++){
} Object o=new Object();v.add(o);o=null;
//此时,所有的Object对象都没有被释放,因为变量v引用这些对象。
回页首
如何检测内存泄漏
最后一个重要的问题,就是如何检测Java的内存泄漏。目前,我们通常使用一些工具来检查Java程序的内存泄漏问题。市场上已有几种专业检查Java内存泄漏的工具,它们的基本工作原理大同小异,都是通过监测Java程序运行时,所有对象的申请、释放等动作,将内存管理的所有信息进行统计、分析、可视化。开发人员将根据这些信息判断程序是否有内存泄漏问题。这些工具包括Optimizeit Profiler,JProbe Profiler,JinSight , Rational 公司的Purify等。
下面,我们将简单介绍Optimizeit的基本功能和工作原理。
Optimizeit Profiler版本4.11支持Application,Applet,Servlet和Romote Application四类应用,并且可以支持大多数类型的JVM,包括SUN JDK系列,IBM的JDK系列,和Jbuilder的JVM等。并且,该软件是由Java编写,因此它支持多种操作系统。Optimizeit系列还包括Thread Debugger和Code Coverage两个工具,分别用于监测运行时的线程状态和代码覆盖面。
当设置好所有的参数了,我们就可以在OptimizeIt环境下运行被测程序,在程序运行过程中,Optimizeit可以监视内存的使用曲线(如下图),包括JVM申请的堆(heap)的大小,和实际使用的内存大小。另外,在运行过程中,我们可以随时暂停程序的运行,甚至强行调用GC,让GC进行内存回收。通过内存使用曲线,我们可以整体了解程序使用内存的情况。这种监测对于长期运行的应用程序非常有必要,也很容易发现内存泄露。
在运行过程中,我们还可以从不同视角观查内存的使用情况,Optimizeit提供了四种方式:
堆视角。这是一个全面的视角,我们可以了解堆中的所有的对象信息(数量和种类),并进行统计、排序,过滤。了解相关对象的变化情况。
方法视角。通过方法视角,我们可以得知每一种类的对象,都分配在哪些方法中,以及它们的数量。
对象视角。给定一个对象,通过对象视角,我们可以显示它的所有出引用和入引用对象,我们可以了解这个对象的所有引用关系。
引用图。给定一个根,通过引用图,我们可以显示从该顶点出发的所有出引用。在运行过程中,我们可以随时观察内存的使用情况,通过这种方式,我们可以很快找到那些长期不被释放,并且不再使用的对象。我们通过检查这些对象的生存周期,确认其是否为内存泄露。在实践当中,寻找内存泄露是一件非常麻烦的事情,它需要程序员对整个程序的代码比较清楚,并且需要丰富的调试经验,但是这个过程对于很多关键的Java程序都是十分重要的。
综上所述,Java也存在内存泄露问题,其原因主要是一些对象虽然不再被使用,但它们仍然被引用。为了解决这些问题,我们可以通过软件工具来检查内存泄露,检查的主要原理就是暴露出所有堆中的对象,让程序员寻找那些无用但仍被引用的对象。
第二篇:初探java内存机制_堆和栈
初探java内存机制_堆和栈
问题的引入:
问题一:
String str1 = “abc”;
String str2 = “abc”;
System.out.println(str1==str2);//true
问题二:
String str1 =new String(“abc”);
String str2 =new String(“abc”);
System.out.println(str1==str2);// false
问题三:
String s1 = “ja”;
String s2 = “va”;
String s3 = “java”;
String s4 = s1 + s2;
System.out.println(s3 == s4);//false
System.out.println(s3.equals(s4));//true
由于以上问题让人含糊不清,于是特地搜集了一些有关java内存分配的资料,以下是网摘:
Java 中的堆和栈
Java把内存划分成两种:一种是栈内存,一种是堆内存。
在函数中定义的一些基本类型的变量和对象的引用变量都在函数的栈内存中分配。
当在一段代码块定义一个变量时,Java就在栈中为这个变量分配内存空间,当超过变量的作用域后,Java会自动释放掉为该变量所分配的内存空间,该内存空间可以立即被另作他用。
堆内存用来存放由new创建的对象和数组。
在堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理。
在堆中产生了一个数组或对象后,还可以在栈中定义一个特殊的变量,让栈中这个变量的取值等于数组或对象在堆内存中的首地址,栈中的这个变量就成了数组或对象的引用变量。
引用变量就相当于是为数组或对象起的一个名称,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或对象。
具体的说:
栈与堆都是Java用来在Ram中存放数据的地方。与C++不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆。
Java的堆是一个运行时数据区,类的(对象从中分配空间。这些对象通过new、newarray、anewarray和multianewarray等指令建立,它们不需要程序代码来显式的释放。堆是由垃圾回收来负责的,堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。
栈的优势是,存取速度比堆要快,仅次于寄存器,栈数据可以共享。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。栈中主要存放一些基本类型的变量(,int, short, long, byte, float, double, boolean, char)和对象句柄。
栈有一个很重要的特殊性,就是存在栈中的数据可以共享。假设我们同时定义:
int a = 3;
int b = 3;
编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找栈中是否有3这个值,如果没找到,就将3存放进来,然后将a指向3。接着处理int b = 3;在创建完b的引用变量后,因为在栈中已经有3这个值,便将b直接指向3。这样,就出现了a与b同时均指向3的情况。这时,如果再令a=4;那么编译器会重新搜索栈中是否有4值,如果没有,则将4存放进来,并令a指向4;如果已经有了,则直接将a指向这个地址。因此a值的改变不会影响到b的值。要注意这种数据的共享与两个对象的引用同时指向一个对象的这种共享是不同的,因为这种情况a的修改并不会影响到b, 它是由编译器完成的,它有利于节省空间。而一个对象引用变量修改了这个对象的内部状态,会影响到另一个对象引用变量。
String是一个特殊的包装类数据。可以用:
String str = new String(“abc”);
String str = “abc”;
两种的形式来创建,第一种是用new()来新建对象的,它会在存放于堆中。每调用一次就会创建一个新的对象。
而第二种是先在栈中创建一个对String类的对象引用变量str,然后查找栈中有没有存放“abc”,如果没有,则将“abc”存放进栈,并令str指向”abc”,如果已经有”abc” 则直接令str指向“abc”。
比较类里面的数值是否相等时,用equals()方法;当测试两个包装类的引用是否指向同一个对象时,用==,下面用例子说明上面的理论。
String str1 = “abc”;
String str2 = “abc”;
System.out.println(str1==str2);//true
可以看出str1和str2是指向同一个对象的。
String str1 =new String(“abc”);
String str2 =new String(“abc”);
System.out.println(str1==str2);// false
用new的方式是生成不同的对象。每一次生成一个。
因此用第二种方式创建多个”abc”字符串,在内存中其实只存在一个对象而已.这种写法有利与节省内存空间.同时它可以在一定程度上提高程序的运行速度,因为JVM会自动根据栈中数据的实际情况来决定是否有必要创建新对象。而对于String str = new String(“abc”);的代码,则一概在堆中创建新对象,而不管其字符串值是否相等,是否有必要创建新对象,从而加重了程序的负担。
另一方面, 要注意: 我们在使用诸如String str = “abc”;的格式定义类时,总是想当然地认为,创建了String类的对象str。担心陷阱!对象可能并没有被创建!而可能只是指向一个先前已经创建的对象。只有通过new()方法才能保证每次都创建一个新的对象。由于String类的immutable性质,当String变量需要经常变换其值时,应该考虑使用StringBuffer类,以提高程序效率。
java中内存分配策略及堆和栈的比较
2.1 内存分配策略
按照编译原理的观点,程序运行时的内存分配有三种策略,分别是静态的,栈式的,和堆式的.静态存储分配是指在编译时就能确定每个数据目标在运行时刻的存储空间需求,因而在编译时就可以给他们分配固定的内存空间.这种分配策略要求程序代码中不允许有可变数据结构(比如可变数组)的存在,也不允许有嵌套或者递归的结构出现,因为它们都会导致编译程序无法计算准确的存储空间需求.栈式存储分配也可称为动态存储分配,是由一个类似于堆栈的运行栈来实现的.和静态存储分配相反,在栈式存储方案中,程序对数据区的需求在编译时是完全未知的,只有到运行的时候才能够知道,但是规定在运行中进入一个程序模块时,必须知道该程序模块所需的数据区大小才能够为其分配内存.和我们在数据结构所熟知的栈一样,栈式存储分配按照先进后出的原则进行分配。
静态存储分配要求在编译时能知道所有变量的存储要求,栈式存储分配要求在过程的入口处必须知道所有的存储要求,而堆式存储分配则专门负责在编译时或运行时模块入口处都无法确定存储要求的数据结构的内存分配,比如可变长度串和对象实例.堆由大片的可利用块或空闲块组成,堆中的内存可以按照任意顺序分配和释放.2.2 堆和栈的比较
上面的定义从编译原理的教材中总结而来,除静态存储分配之外,都显得很呆板和难以理解,下面撇开静态存储分配,集中比较堆和栈:
从堆和栈的功能和作用来通俗的比较,堆主要用来存放对象的,栈主要是用来执行程序的.而这种不同又主要是由于堆和栈的特点决定的:在编程中,例如C/C++中,所有的方法调用都是通过栈来进行的,所有的局部变量,形式参数都是从栈中分配内存空间的。实际上也不是什么分配,只是从栈顶向上用就行,就好像工厂中的传送带(conveyor belt)一样,Stack Pointer会自动指引你到放东西的位置,你所要做的只是把东西放下来就行.退出函数的时候,修改栈指针就可以把栈中的内容销毁.这样的模式速度最快, 当然要用来运行程序了.需要注意的是,在分配的时候,比如为一个即将要调用的程序模块分配数据区时,应事先知道这个数据区的大小,也就说是虽然分配是在程序运行时进行的,但是分配的大小多少是确定的,不变的,而这个“大小多少”是在编译时确定的,不是在运行时.堆是应用程序在运行的时候请求操作系统分配给自己内存,由于从操作系统管理的内存分配,所以在分配和销毁时都要占用时间,因此用堆的效率非常低.但是堆的优点在于,编译器不必知道要从堆里分配多少存储空间,也不必知道存储的数据要在堆里停留多长的时间,因此,用堆保存数据时会得到更大的灵活性。事实上,面向对象的多态性,堆内存分配是必不可少的,因为多态变量所需的存储空间只有在运行时创建了对象之后才能确定.在C++中,要求创建一个对象时,只需用 new命令编制相关的代码即可。执行这些代码时,会在堆里自动进行数据的保存.当然,为达到这种灵活性,必然会付出一定的代价:在堆里分配存储空间时会花掉更长的时间!这也正是导致我们刚才所说的效率低的原因,看来列宁同志说的好,人的优点往往也是人的缺点,人的缺点往往也是人的优点(晕~).2.3 JVM中的堆和栈
JVM是基于堆栈的虚拟机.JVM为每个新创建的线程都分配一个堆栈.也就是说,对于一个Java程序来说,它的运行就是通过对堆栈的操作来完成的。堆栈以帧为单位保存线程的状态。JVM对堆栈只进行两种操作:以帧为单位的压栈和出栈操作。
我们知道,某个线程正在执行的方法称为此线程的当前方法.我们可能不知道,当前方法使用的帧称为当前帧。当线程激活一个Java方法,JVM就会在线程的 Java堆栈里新压入一个帧。这个帧自然成为了当前帧.在此方法执行期间,这个帧将用来保存参数,局部变量,中间计算过程和其他数据.这个帧在这里和编译原理中的活动纪录的概念是差不多的.从Java的这种分配机制来看,堆栈又可以这样理解:堆栈(Stack)是操作系统在建立某个进程时或者线程(在支持多线程的操作系统中是线程)为这个线程建立的存储区域,该区域具有先进后出的特性。
每一个Java应用都唯一对应一个JVM实例,每一个实例唯一对应一个堆。应用程序在运行中所创建的所有类实例或数组都放在这个堆中,并由应用所有的线程共享.跟C/C++不同,Java中分配堆内存是自动初始化的。Java中所有对象的存储空间都是在堆中分配的,但是这个对象的引用却是在堆栈中分配,也就是说在建立一个对象时从两个地方都分配内存,在堆中分配的内存实际建立这个对象,而在堆栈中分配的内存只是一个指向这个堆对象的指针(引用)而已。
从上面的讲述中大概理清了最初三个问,希望高人能再补充一些您觉得重要的知识点,谢谢!
第三篇:java web 文件上传下载分析总结
个人理解:
上传下载文件根本是对流的处理,最基础的是不使用任何框架,直接对流进行获取和解析(对request.getInputStream()进行解析处理)。
其次用一些框架进行处理,目前文件上传的(框架)组件: Apache—-fileuploadOrialiy – COS,Jsp-smart-upload。以fileupload为例,提供了工具类解析request,不需要自己去解析流。
最后是使用一些J2EE流行框架,使用框架内部封装好的来完成上传下载。这些框架大体集成了fileupload等框架,在后者基础上进一步封装,不需要处理request就获取了封装好的文件,只需处理封装后的文件就可。
Struts2使用了fileupload,Springmvc则提供了commonsmultipartresolver进行解析。
在开发过程中文件的上传下载很常用。这里简单的总结一下: 1.文件上传必须满足的条件:
a、页面表单的method必须是post 因为get传送的数据太小了 b、页面表单的enctype必须是multipart/form-data类型的 c、表单中提供上传输入域
代码细节:客户端表单中:
第二步:遍历list publicclassUp3ServletextendsHttpServlet { publicvoiddoPost(HttpServletRequest request, HttpServletResponse response)throwsServletException, IOException { request.setCharacterEncoding(”UTF-8“);String path = getServletContext().getRealPath(”/up“);//声明disk
DiskFileItemFactory disk = newDiskFileItemFactory();disk.setSizeThreshold(1024*1024);disk.setRepository(new File(”d:/a“));//声明解析requst的servlet
ServletFileUpload up = newServletFileUpload(disk);try{ //解析requst
List
List
String fileName = file.getName();fileName = fileName.substring(fileName.lastIndexOf(”“)+1);String fileType = file.getContentType();InputStream in = file.getInputStream();int size = in.available();//使用工具类
FileUtils.copyInputStreamToFile(in,new File(path+”/“+fileName));mm.put(”fileName“,fileName);mm.put(”fileType“,fileType);mm.put(”size“,”“+size);
ups.add(mm);file.delete();} request.setAttribute(”ups“,ups);//转发
request.getRequestDispatcher(”/jsps/show.jsp“).forward(request, response);
}catch(Exception e){ e.printStackTrace();} } } 如上就是上传文件的常用做法。现在我们在来看看fileupload的其他查用API.判断一个fileItem是否是file(type=file)对象或是text(type=text|checkbox|radio)对象: booleanisFormField()如果是text|checkbox|radio|select这个值就是true.6.处理带说明信息的图片
publicclassUpDescServletextendsHttpServlet { publicvoiddoPost(HttpServletRequest request, HttpServletResponse response)throwsServletException, IOException { request.setCharacterEncoding(”UTF-8“);//可以获取中文的文件名 String path = getServletContext().getRealPath(”/up“);DiskFileItemFactory disk = newDiskFileItemFactory();disk.setRepository(new File(”d:/a“));try{ ServletFileUpload up = newServletFileUpload(disk);List
if(file.isFormField()){ String fileName = file.getFieldName();//=desc
String value = file.getString(”UTF-8“);//默认以ISO方式读取数据
System.err.println(fileName+”=“+value);}else{//说明是一个文件
String fileName = file.getName();fileName = fileName.substring(fileName.lastIndexOf(”“)+1);file.write(new File(path+”/“+fileName));System.err.println(”文件名是:“+fileName);System.err.println(”文件大小是:“+file.getSize());file.delete();} } }catch(Exception e){ e.printStackTrace();} } } 7.文件上传的性能提升
在解析request获取FileItem的集合的时候,使用: FileItemIterator it=up.getItemIterator(request);
比使用 List
性能上要好的多。示例代码:
publicclassFastServletextendsHttpServlet {
publicvoiddoPost(HttpServletRequest request, HttpServletResponse response)throwsServletException, IOException { request.setCharacterEncoding(”UTF-8“);String path = getServletContext().getRealPath(”/up“);DiskFileItemFactory disk = newDiskFileItemFactory();disk.setRepository(new File(”d:/a“));try{ ServletFileUpload up = newServletFileUpload(disk);//以下是迭代器模式
FileItemIterator it= up.getItemIterator(request);while(it.hasNext()){ FileItemStream item = it.next();String fileName = item.getName();fileName=fileName.substring(fileName.lastIndexOf(”“)+1);InputStream in = item.openStream();FileUtils.copyInputStreamToFile(in,new File(path+”/“+fileName));} }catch(Exception e){ e.printStackTrace();}
} } 8.文件的下载
既可以是get也可以是post。
publicvoiddoPost(HttpServletRequestreq, HttpServletResponseresp)throwsServletException, IOException { req.setCharacterEncoding(”UTF-8“);String name = req.getParameter(”name“);//第一步:设置响应的类型
resp.setContentType(”application/force-download“);//第二读取文件
String path = getServletContext().getRealPath(”/up/“+name);InputStreamin = newFileInputStream(path);//设置响应头
//对文件名进行url编码
name = URLEncoder.encode(name, ”UTF-8“);resp.setHeader(”Content-Disposition“,”attachment;filename=“+name);resp.setContentLength(in.available());
//第三步:开始文件copy
OutputStreamout = resp.getOutputStream();byte[] b = newbyte[1024];intlen = 0;while((len=in.read(b))!=-1){ out.write(b,0,len);} out.close();in.close();}
在使用J2EE流行框架时,使用框架内部封装好的来完成上传下载更为简单: Struts2完成上传.在使用Struts2进行开发时,导入的jar包不难发现存在 commons-fileupload-1.3.1.jar 包。通过上面的学习我们已经可以使用它进行文件的上传下载了。但Struts2在进行了进一步的封装。view
Controller FileUploadActionextendsActionSupport { private String username;
//注意,file并不是指前端jsp上传过来的文件本身,而是文件上传过来存放在临时文件夹下面的文件 private File file;
//提交过来的file的名字
//struts会自动截取上次文件的名字注入给该属性 private String fileFileName;//getter和setter此时为了节约篇幅省掉 @Override
public String execute()throws Exception { //保存上传文件的路径
String root = ServletActionContext.getServletContext().getRealPath(”/upload“);//获取临时文件输入流
InputStream is = newFileInputStream(file);//输出文件
OutputStreamos = newFileOutputStream(new File(root, fileFileName));//打印出上传的文件的文件名
System.out.println(”fileFileName: “ + fileFileName);
// 因为file是存放在临时文件夹的文件,我们可以将其文件名和文件路径打印出来,看和之前的fileFileName是否相同
System.out.println(”file: “ + file.getName());System.out.println(”file: “ + file.getPath());
byte[] buffer = newbyte[1024];int length = 0;
while(-1!=(length = is.read(buffer, 0, buffer.length))){ os.write(buffer);}
os.close();is.close();
return SUCCESS;} }
首先我们要清楚一点,这里的file并不是真正指代jsp上传过来的文件,当文件上传过来时,struts2首先会寻找struts.multipart.saveDir(这个是在default.properties里面有)这个name所指定的存放位置(默认是空),我们可以在我们项目的struts2中来指定这个临时文件存放位置。
如果没有设置struts.multipart.saveDir,那么将默认使用javax.servlet.context.tempdir指定的地址,javax.servlet.context.tempdir的值是由服务器来确定的,例如:假如我的web工程的context是abc,服务器使用Tomcat,那么savePath就应该是%TOMCAT_HOME%/work/Catalina/localhost/abc_,临时文件的名称类似于upload__1a156008_1373a8615dd__8000_00000001.tmp,每次上传的临时文件名可能不同,但是大致是这种样式。而且如果是使用Eclipse中的Servers里面配置Tomcat并启动的话,那么上面地址中的%TOMCAT_HOME%将不会是系统中的实际Tomcat根目录,而会是Eclipse给它指定的地址,例如我本地的地址是这样的:/home/wang/EclipseJavaCode/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/work/Catalina/localhost/abc/upload__1a156008_1373a8615dd__8000_00000001.tmp。Struts2完成下载.struts2的文件下载更简单,就是定义一个输入流,然后将文件写到输入流里面就行,关键配置还是在struts.xml这个配置文件里配置:
publicclassFileDownloadActionextendsActionSupport { //要下载文件在服务器上的路径 private String path;//要下载文件的文件名
private String downloadFileName;//写入getter和setter
publicInputStreamgetDownloadFile(){ return ServletActionContext.getServletContext().getResourceAsStream(path);} @Override
public String execute()throws Exception { //当前action默认在valuestack的栈顶 setDownloadFileName(xxx);return SUCCESS;} }
action只是定义了一个输入流downloadFile,然后为其提供getter方法就行,接下来我们看看struts.xml的配置文件:
attachment;fileName=”${downloadFileName}“
downloadFile
struts.xml配置文件有几个地方我们要注意,首先是result的类型,type一定要定义成stream类型_,告诉action这是文件下载的result,result元素里面一般还有param子元素,这个是用来设定文件下载时的参数,inputName这个属性就是得到action中的文件输入流,名字一定要和action中的输入流属性名字相同,然后就是contentDisposition属性,这个属性一般用来指定我们希望通过怎么样的方式来处理下载的文件,如果值是attachment,则会弹出一个下载框,让用户选择是否下载,如果不设定这个值,那么浏览器会首先查看自己能否打开下载的文件,如果能,就会直接打开所下载的文件,(这当然不是我们所需要的),另外一个值就是filename这个就是文件在下载时所提示的文件下载名字。在配置完这些信息后,我们就能过实现文件的下载功能了。
SpringMVC完成上传:
view于struts2示例中的完全一样。此出不在写出。Controller: @Controller @RequestMapping(value=”fileOperate“)publicclassFileOperateAction { @RequestMapping(value=”upload“)public String upload(HttpServletRequest request,@RequestParam(”file“)MultipartFilephotoFile){ //上传文件保存的路径
String dir = request.getSession().getServletContext().getRealPath(”/“)+”upload“;//原始的文件名
String fileName = photoFile.getOriginalFilename();//获取文件扩展名
String extName = fileName.substring(fileName.lastIndexOf(”.“));//防止文件名冲突,把名字小小修改一下
fileName = fileName.substring(0,fileName.lastIndexOf(”.“))+ System.nanoTime()+ extName;FileUtils.writeByteArrayToFile(new File(dir,fileName),photoFile.getBytes());return”success“;} }
SpringMVC完成下载:
@RequestMapping(”/download“)public String download(String fileName, HttpServletRequest request, HttpServletResponse response){ response.setCharacterEncoding(”utf-8“);response.setContentType(”multipart/form-data“);response.setHeader(”Content-Disposition“, ”attachment;fileName=“
+ fileName);try { InputStreaminputStream = newFileInputStream(new File(文件的路径);
OutputStreamos = response.getOutputStream();byte[] b = newbyte[2048];int length;while((length = inputStream.read(b))>0){ os.write(b, 0, length);}
// 这里主要关闭。
os.close();
inputStream.close();} catch(FileNotFoundException e){ e.printStackTrace();} catch(IOException e){ e.printStackTrace();} // 返回值要注意,要不然就出现下面这句错误!
//java+getOutputStream()has already been called for this response returnnull;}
另附Spring mvc的三种上传文件方法
前台:
<%@ page language=”java“contentType=”text/html;charset=utf-8“
pageEncoding=”utf-8“%>
第四篇:java总结
调用父类构造方法
在子类的构造方法中可使用super(argument_list)语句调用父类的构造方法
如果子类的构造方法中没有显示地调用父类构造方法,也没有使用this关键字调用重载的其它构造方法,则系统默认调用父类无参数的构造方法
如果子类构造方法中既未显式调用父类构造方法,而父类中又没有无参的构造方法,则编译出错
1public class Person {
3private String name;
4private int age;private Date birthDate;
7public Person(String name, int age, Date d){ 8this.name = name;
9this.age = age;
10this.birthDate = d;
11}
12public Person(String name, int age){ 13this(name, age, null);
14}
15public Person(String name, Date d){ 16this(name, 30, d);
17}
18public Person(String name){
19this(name, 30);}
21// ……
22}
1public class Student extends Person {
2private String school;
4public Student(String name, int age, String s){ 5super(name, age);
6school = s;
7}
8public Student(String name, String s){
9super(name);
10school = s;
11}
12public Student(String s){ // 编译出错: no super()13school = s;
14}
15}
对象构造和初始化细节
分配存储空间并进行默认的初始化
按下述步骤初始化实例变量
1.绑定构造方法参数
2.如有this()调用,则调用相应的重载构造方法,然后跳转到步骤5
3.显式或隐式追溯调用父类的构造方法(Object类除外)
4.进行实例变量的显式初始化操作
5.执行当前构造方法的方法体
==操作符与equals方法
==操作符与equals方法的区别:
引用类型比较引用;基本类型比较值;
equals()方法只能比较引用类型,“==”可以比较引用类型及基本类型;
特例:当用equals()方法进行比较时,对类File、String、Date及封装类(Wrapper Class)来说,是比较类型及内容而不考虑引用的是否是同一个实例;
用“==”进行比较时,符号两边的数据类型必须一致(可自动转换的基本数据类型除外),否则编译出错;
由装箱引发的——Integer比较的来龙去脉
前置知识: 众所周之,java是保留了int,char等基本数据类型的,也就是说int类型的并不是对象,然而有些方法却需要object 类型的变量,所以java使用了装箱机制,我们可一自豪的这样声明一个整型变量:Integer a = new Integer(10);那么整型的a也就是对象了,那这句是什么意思呢:Integer a= 10;java中可以这样声明一个对象吗?当然不是,从jdk1.5后,java实现了自动装箱,也就是自动将Integer a =10 中的int类型的10转化为了 Integer类型。好,有了前面的只是我们且先看一个题目:
Integer a = 127;
Integer b = 127;
Integer c = 128;
Integer d = 128;
System.out.println(a==b);
System.out.println(c==d);
答案是什么呢? 如果您回答true,false,那么很遗憾的告诉你,哈哈,其实你答对了!!
那我们晕了就相差1的两个数为啥走向了“反目成仇”的地步呢?凭啥127等于127,我128就不等于128呢?且听我慢慢道来,Integer a =127,Integer a=128。
127,128应该不会造成什么差异吧,难道是自动装箱的过程有猫腻?找下源码看看:
private static class IntegerCache {
private IntegerCache(){}
static final Integer cache[] = new Integer[-(-128)+ 127 + 1];static {
for(int i = 0;i < cache.length;i++)
cache[i] = new Integer(i128);
}
这是用一个for循环对数组cache赋值,cache[255] = new Integer(255-128),也就是newl一个Integer(127),并把引用赋值给cache[255],好了,然后是Integer b= 127,流程基本一样,最后又到了cache[255] = new Integer(255-128),这一句,那我们迷糊了,这不是又new了一个对象127吗,然后把引用赋值给cache[255],我们比较这两个引用(前面声明a的时候也有一个),由于是不同的地址,所以肯定不会相等,应该返回false啊!呵呵,这么想你就错了,请注意看for语句给cache[i]初始化的时候外面还一个{}呢,{}前面一个大大的static关键字大咧咧的杵在哪呢,对静态的,那么我们就可以回想下static有什么特性了,只能初始化一次,在对象间共享,也就是不同的对象共享同一个static数据,那么当我们Integer b = 127的时候,并没有new出一个新对象
来,而是共享了a这个对象的引用,记住,他们共享了同一个引用!!,那么我们进行比较a==b时,由于是同一个对象的引用(她们在堆中的地址相同),那当然返回true了!!
然后我们在看Integer c = 128;Integer d = 128;这两句。现在不用我说就应该能明白了吧,当数据不再-128到127之间时,是不执行return
IntegerCache.cache[i + offset];这句的,也就是不会返回一个static的引用,而是执行了return new Integer(i);于是当 Integer d = 128 时,又会重新返回一个引用,两个不同的引用
在做c==d 的比较时当然返回false了!
下面附上本程序的字节码以供喜欢底层的读者参考:
Compiled from “CompareInteger.java”
public class CompareInteger extends java.lang.Object{
public CompareInteger();
Code:
0:aload_0
1:invokespecial#1;//Method java/lang/Object.“
public static void main(java.lang.String[]);
Code:
0:bipush 127
2:invokestatic#2;//Method
java/lang/Integer.valueOf:(I)Ljava/lang/Int
eger;
5:astore_1
6:bipush 127
8:invokestatic#2;//Method
java/lang/Integer.valueOf:(I)Ljava/lang/Int
eger;
11: astore_2
12: sipush 128
15: invokestatic#2;//Method
java/lang/Integer.valueOf:(I)Ljava/lang/Int
eger;
18: astore_3
19: sipush 128
22: invokestatic#2;//Method
java/lang/Integer.valueOf:(I)Ljava/lang/Int
eger;
25: astore 4
27: getstatic#3;//Field
java/lang/System.out:Ljava/io/PrintStream;
30: aload_1
31: aload_2
32: if_acmpne39
35: iconst_1
36: goto40
39: iconst_0
40: invokevirtual#4;//Method java/io/PrintStream.println:(Z)V43: getstatic#3;//Field
java/lang/System.out:Ljava/io/PrintStream;
46: aload_3
47: aload4
49: if_acmpne56
52: iconst_1
53: goto57
56: iconst_0
57: invokevirtual#4;//Method java/io/PrintStream.println:(Z)V60: return
}
评论:呵呵,这么想你就错了,请注意看for语句给cache[i]初始化的时候外面还一个{}呢,{}前面一个大大的static关键字大咧咧的杵在哪呢,对静态的,那么我们就可以回想下static有什么特性了,只能初始化一次,在对象间共享,也就是不同的对象共享同一个static数据,那么当我们Integer b = 127的时候,并没有new出一个新对象来,而是共享了a这个对象的引用,记住,他们共享了同一个引用!!
呵呵,博主我被你这句话小小的误导了一下,其实你这里说的原理没错,但是把位置说错了,这段代码只是初始化cache:
static {
for(int i = 0;i < cache.length;i++)
cache[i] = new Integer(i-128);
}
但真正让cache[i]为static变量的是这句代码:
static final Integer cache[] = new Integer[-(-128)+ 127 + 1];
第五篇:Java总结
Java实验
1.调试HelloWorld程序
2.this,super,get ,set,把课本90页程序4.7中的name改成私有变量
3.继承,重写,父类引用指向子类对象
4.验证数组Arrays类和Collection类
5.编写一个自己的异常类并捕获之。
6.编写一个类,将该类的几个对象装入TreeSet容器中,并将该容器的内容通过输出流写入文件中。
前三章重点
0.java的数据类型:四类八种-(1)布尔类型Boolean;(2)字符类型char;(3)整数byte,short,int,long;(4)浮点类型:float,double;1.面向对象的3个基本特征:封装,继承,多态。
2.构造方法和普通方法的区别:对构造方法而言,它有以下特性---(1)方法名必须与要创建对象的类名相同。(2)不允许声明返回类型,即使声明为void也不被允许。
3.this关键字:是一个引用,this引用指向的是其本身所在方法的当前对象。this的使用方法:(1)调用成员变量;(2)可以用this()调用其他构造函数。
4.java中只对类成员变量进行自动初始化,而方法内部的局部变量在使用前必须手动初始化。
5.static 关键字:可用来修饰类的成员变量和成员方法,需要注意两点--(1)静态方法不能调用类的非静态方法,不能访问类的非静态变量。(2)静态方法和静态变量(非私有的)可以有两种调用方式,一是实例对象调用,二是类名直接调用。
6.类成员访问控制修饰符public、private、default(可不写,即缺省状态)、protected的使用:public-公用的;private-私有的,只在定义它的类内部使用;default-可以被同一包中的类访问;protected-既可以被同一包中的类访问,也可以被不在同一包中的子类访问。
7.方法的重载:指方法名相同,而方法的参数列表不相同。参数列表不同有三层意思:(1)参数类型不同。(2)参数顺序不同。(3)参数个数不同。另外需注意,在同一个类中,当方法名和参数列表都相同时,访问控制修饰符或方法返回类型不相同并不是方法的重载,而且这种情况在java中是不被允许的。
第四五章重点
1.继承:需使用关键字extends.在使用继承时需注意--(1)每个子类只能定义一个超类(父类),即extends后面应且仅应跟一个类名作为该类的父类。(2)父类中的私有属性和私有方法不能被继承。
2.方法的重写:即子类对超类中的方法保持方法名、返回类型和参数列表不变,重写了方法体,使子类和超类完成不同的工作。重写需注意下面几个关键点:(1)超类中的私有方法不能被重写。(2)访问限制符强度由低到高依次是:public、protected、default、private,在重写过程中,如果子类和父类中方法的返回值、方法名及方法的参数列表都相同,这时,要求子类中该方法的访问限制符强度不能超过父类的。即如果父类中为public时,子类也只能为public,而不能是余下的三种。
3.重载(overload)和覆盖(override)的区别:(1)重载—发生在一个类的内部或子类与父类之间,要求方法名相同而参数列表不一样。(2)覆盖—只能发生在继承过程中,要求子类方法的返回类型,方法名和参数列表同父类的都相同,而方法体不一样。
4.构造器的调用顺序:先祖先,再客人,最后自己。
5.多态:指在类继承中子类和父类中可以有同名但意义或实现方式不同的属性和方法。分为:覆盖和重载。多态的优点:因为多态,可以在程序中对类进行扩展,而不需改变那些操作基类接口的方法。
6.动态绑定:指在代码执行期间,判断所引用对象的实际类型,根据其实际类型调用相应方法。动态绑定存在的三个必要条件--(1)要有继承;(2)要有重写(覆盖);(3)父类引用指向子类对象(向上转型)。
7.Object中常用的方法总结:toString();wait();equals();notify();notifyAll();hashCode();getClass();clone();finalize();(呵呵,尽可能记几个,以防老师让咱们列举)注:java中Object类是所有类的父类,即java中所有的类都有上述9种方法。
8.对象的比较:注意关键字instanceof的使用。
9.抽象类:
抽象方法—用关键字abstract修饰的方法,该方法只需方法的声明,而不需方法的实现(即无方法体)。
抽象类——至少包含一个抽象方法的类,也用abstract关键字声明。(注:(1)抽象类中可以有一些具体方法。(2)抽象类不能实例化。(3)子类继承抽象类必须实现其抽象方法。)
10.接口:
(1)可以看成是高度抽象的抽象类,但是接口不是类。
(2)用关键字interface来声明接口,用关键字imlpements来实现接口。
(3)接口不能有具体方法,不能有实例数据,但可以定义常量。
(4)实现接口的非抽象类必须实现接口的所有方法。
(5)每个类可以实现多个接口,这些接口用逗号隔开,同时,一个接口可以被多个类实现。
第六章:重点看一下实验四
1.容器——Collection(接口)和Map(接口).Collection——Set(接口)和List(接口)。其中,List必须保持元素的特定顺序,常见的实现类有ArrayList和LinkedList;Set不能有重复元素,常见的实现类有HashSet和TreeSet。
Map——一组成对的“键值对”对象,即其元素是成对的对象,常见的实现类有HashMap和TreeMap。
第七章 1.异常类的根类是Throwable类,它的两个直接子类是Error类和Exception类。
2.异常中常用的5个关键字为:try,catch,finally,throw,throws.其中,try和catch:用于捕获异常;finally:无论try块中的异常是否抛出,finally中的代码块总能被执行;throw:抛出异常;throws:声明异常。
3.“未被检查的异常(Unchecked Exceptions)”和“受检查的异常(Checked Exceptions)”——
Unchecked Exceptions :编译器不检查方法是否处理或抛出的异常,即不做处理,编译时不报错。
Checked Exceptions:受编译器检查的异常,即不做处理编译时通不过。
4.常见的几种Checked Exceptions:ClassNotFoundExceptionIOExceptionInterruptedExceptionFileNotFoundException.(尽可能的记几个吧,以防不测)第八章
1.流--字节流和字符流;
流--节点流和处理流。
2.所有的输入流都是从抽象类InputStream和Reader继承而来。所有输出流都是从抽象类OutputStream和Writer继承而来。3.字节流:InputStream和OutputStream;字符流:Reader和Writer;
4.节点流:直接与文件等底层打交道,如FileInputStreamFileOutputStreamFileReaderFileWriter.处理流:相当于包装流,套在节点流上,方便数据处理。相关一些用法,具体参考最后一次实验。