本文共 39459 字,大约阅读时间需要 131 分钟。
而具有相等的hashcode的两个对象equals不一定成立
String workPath = request.getParameter("workPath"); String filename=workPath.substring(workPath.lastIndexOf("/")+1); StringBuffer s = new StringBuffer(fileRoot); //得到Web模块在服务器上的路径 s.append(workPath); //本地测试用下面[记住一点,文件必须在提供down服务的机器上,说白了就是服务必须和文件在同一台机器上]// StringBuffer s = new StringBuffer("G:\\KuGou\\LYn - Thank You My Dear.mp3"); System.out.println(s); File file = new File(s.toString()); FileInputStream fis = null ; if (file.exists()) { //如果文件存在这执行文件的读写操作 System.out.println("文件实际长度" + file.length()); fis = new FileInputStream(file); DataOutputStream oos = new DataOutputStream(response. getOutputStream()); //注意下面文件头的设置 response.setHeader( "Content-Disposition", "attachment;filename=" + new String( filename.getBytes("gb2312"), "ISO8859-1" ) ); //对于文件名是纯简体中文的文件可以使用new String( filename.getBytes("gb2312"), "ISO8859-1" )方法将filename字符串转化成ISO8859-1 //如果中间还有繁体字的话,就需要使用其他的方法来解决中文显示的问题 response.setContentType("application/x-msdownload"); //设置文件内容的类型 int cache_length=1024; byte[] filebyte = new byte[cache_length]; for (int k = 0; k < cache_length; k++) { filebyte[k] = 0; } int i = fis.read(filebyte); //如果i=-1就说明文件已经到头了,不要再读文件 while (i != -1) { oos.write(filebyte); for (int k = 0; k < cache_length; k++) { filebyte[k] = 0; } i = fis.read(filebyte); } oos.write(filebyte); } fis.close();远程下载:
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String workPath = request.getParameter("workPath"); String filename=workPath.substring(workPath.lastIndexOf("/")+1); if(!fileRoot.endsWith("/")){ fileRoot = fileRoot +"/"; }; if(workPath.startsWith("/")){ workPath = workPath.substring(1);//把前面的/去掉 } StringBuffer s = new StringBuffer(fileRoot); //得到Web模块在服务器上的路径 s.append(workPath); URL url = new URL(s.toString()); HttpURLConnection httpUrl = (HttpURLConnection) url.openConnection(); //连接指定的网络资源 httpUrl.connect(); //获取网络输入流 BufferedInputStream bis = new BufferedInputStream(httpUrl.getInputStream()); BufferedOutputStream bos = new BufferedOutputStream(response.getOutputStream()); //注意下面文件头的设置 response.setHeader( "Content-Disposition", "attachment;filename=" + new String( filename.getBytes("gb2312"), "ISO8859-1" ) ); //对于文件名是纯简体中文的文件可以使用new String( filename.getBytes("gb2312"), "ISO8859-1" )方法将filename字符串转化成ISO8859-1 //如果中间还有繁体字的话,就需要使用其他的方法来解决中文显示的问题 response.setContentType(contentType); //设置文件内容的类型 int cache_length=1024; byte[] filebyte = new byte[cache_length]; int len ; while((len=bis.read(filebyte))!=-1){ bos.write(filebyte,0,len); } bos.flush();//刷新输入流并强制写出所有缓冲的输出字符 bis.close(); bos.close(); }
FileUploadSlt com.jt.xiaoyang.web.XyFileUploadSlt uploadPath G://files uploadPathUrl http://localhost:8080/files FileUploadSlt /upload
//先上传到临时文件夹中
public void init(ServletConfig config) throws ServletException { uploadPath = config.getInitParameter("uploadPath"); uploadPathUrl= config.getInitParameter("uploadPathUrl"); tempPath = uploadPath + "/_temp"; }
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Mapparams=new HashMap (); try { DiskFileUpload fu = new DiskFileUpload(); // 设置最大文件尺寸,这里是4MB// fu.setSizeMax(4194304); // 设置缓冲区大小,这里是4kb fu.setSizeThreshold(4096); // 设置临时目录: fu.setRepositoryPath(tempPath); // 得到所有的文件: List fileItems = fu.parseRequest(request); Iterator i = fileItems.iterator(); // 依次处理每一个文件: while (i.hasNext()) { FileItem fi = (FileItem) i.next(); if(fi.isFormField()){ params.put(fi.getFieldName(), fi.getString()); } else { // 获得文件名,这个文件名包括路径: String fileExtName = FileUtil.getFileExtName(fi.getName()); String targetFileName = StringUtil.getRandomString(8) + "." + fileExtName; params.put("targetFileName", targetFileName);// 先写入临时目录 params.put("tempPath", tempPath); fi.write(new File(tempPath+"/" + targetFileName)); } } } catch (Exception e) { // 可以跳转出错页面 e.printStackTrace(); } //保存成功 saveDone(response, params); }
protected void saveDone(HttpServletResponse response,Mapparams) { //复制文件到目标目录 String targetUri=getTargetUri(params); String tempPath=params.get("tempPath"); String targetFileName=params.get("targetFileName");
下面是自己写的工具类,把刚上传到服务器临时文件夹中文件,拷贝一份到自己想要的目录中
FileUtil.copyFile(new File(tempPath+"/" + targetFileName), new File(uploadPath+"/"+targetUri+"/" + targetFileName));
// 返回上传文件的url try { response.getWriter().write(uploadPathUrl+"/"+targetUri+"/"+ targetFileName); } catch (IOException e) { e.printStackTrace(); } } protected String getTargetUri(Mapparams){ //按日期分目录 String targetUri=DateUtil.convertDateToString(new Date()); try { FileUtil.buildDirectory(uploadPath+"/"+targetUri); } catch (IOException e) { e.printStackTrace(); } return targetUri; }
import java.util.*;
public class D { public static void main(String []abc) { int y,m,d,h,mi,s; Calendar cal=Calendar.getInstance(); y=cal.get(Calendar.YEAR); m=cal.get(Calendar.MONTH); //注意这里获取的月份比真实的少1,应该加上1才是真实的当前月份 d=cal.get(Calendar.DATE); h=cal.get(Calendar.HOUR_OF_DAY); mi=cal.get(Calendar.MINUTE); s=cal.get(Calendar.SECOND); System.out.println("现在时刻是"+y+"年"+m+"月"+d+"日"+h+"时"+mi+"分"+s+"秒"); } } ########################################################################## public class Main{ public static void main(String[] args){ java.util.Calendar c=java.util.Calendar.getInstance(); java.text.SimpleDateFormat f=new java.text.SimpleDateFormat("yyyy年MM月dd日hh时mm分ss秒"); System.out.println(f.format(c.getTime())); } } ##########################################################################public String GetNowDate(){
String temp_str=""; Date dt = new Date(); //最后的aa表示“上午”或“下午” HH表示24小时制 如果换成hh表示12小时制 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss aa"); temp_str=sdf.format(dt); return temp_str; }jQuery(document).ready(function() { var urlStr = "http://ip.taobao.com/service/getIpInfo.php?ip="+'${userProperty.lastLoginIp}'; $.post(urlStr,function(data){ alert(data); }); });
import java.io.BufferedReader;import java.io.DataOutputStream;import java.io.IOException;import java.io.InputStreamReader;import java.io.UnsupportedEncodingException;import java.net.HttpURLConnection;import java.net.URL; /** * 根据IP地址获取详细的地域信息 * @project:personGocheck * @class:AddressUtils.java */ public class IPUtils { public static String getAddress(String ip) throws UnsupportedEncodingException{ return getAddresses("ip="+ip, "utf-8"); }; /** * @param content * 请求的参数 格式为:name=xxx&pwd=xxx * @param encoding * 服务器端请求编码。如GBK,UTF-8等 * @return * @throws UnsupportedEncodingException */ public static String getAddresses(String content, String encodingString) throws UnsupportedEncodingException { // 这里调用pconline的接口 String urlStr = "http://ip.taobao.com/service/getIpInfo.php"; // 从http://whois.pconline.com.cn取得IP所在的省市区信息 String returnStr = getResult(urlStr, content, encodingString); if (returnStr != null) { // 处理返回的省市区信息 System.out.println(returnStr); String[] temp = returnStr.split(","); if(temp.length<3){ return "0";//无效IP,局域网测试 } String region = (temp[5].split(":"))[1].replaceAll("\"", ""); region = decodeUnicode(region);// 省份 String country = ""; String area = ""; // String region = ""; String city = ""; String county = ""; String isp = ""; for (int i = 0; i < temp.length; i++) { switch (i) { case 1: country = (temp[i].split(":"))[2].replaceAll("\"", ""); country = decodeUnicode(country);// 国家 break; case 3: area = (temp[i].split(":"))[1].replaceAll("\"", ""); area = decodeUnicode(area);// 地区 break; case 5: region = (temp[i].split(":"))[1].replaceAll("\"", ""); region = decodeUnicode(region);// 省份 break; case 7: city = (temp[i].split(":"))[1].replaceAll("\"", ""); city = decodeUnicode(city);// 市区 break; case 9: county = (temp[i].split(":"))[1].replaceAll("\"", ""); county = decodeUnicode(county);// 地区 break; case 11: isp = (temp[i].split(":"))[1].replaceAll("\"", ""); isp = decodeUnicode(isp); // ISP公司 break; } } System.out.println(country+/*" "+area+*/" "+region+" "+city+" "+county+" "+isp); return country+/*" "+area+*/" "+region+" "+city+" "+county+" "+isp; } return null; } /** * @param urlStr * 请求的地址 * @param content * 请求的参数 格式为:name=xxx&pwd=xxx * @param encoding * 服务器端请求编码。如GBK,UTF-8等 * @return */ private static String getResult(String urlStr, String content, String encoding) { URL url = null; HttpURLConnection connection = null; try { url = new URL(urlStr); connection = (HttpURLConnection) url.openConnection();// 新建连接实例 connection.setConnectTimeout(2000);// 设置连接超时时间,单位毫秒 connection.setReadTimeout(2000);// 设置读取数据超时时间,单位毫秒 connection.setDoOutput(true);// 是否打开输出流 true|false connection.setDoInput(true);// 是否打开输入流true|false connection.setRequestMethod("POST");// 提交方法POST|GET connection.setUseCaches(false);// 是否缓存true|false connection.connect();// 打开连接端口 DataOutputStream out = new DataOutputStream(connection .getOutputStream());// 打开输出流往对端服务器写数据 out.writeBytes(content);// 写数据,也就是提交你的表单 name=xxx&pwd=xxx out.flush();// 刷新 out.close();// 关闭输出流 BufferedReader reader = new BufferedReader(new InputStreamReader( connection.getInputStream(), encoding));// 往对端写完数据对端服务器返回数据 // ,以BufferedReader流来读取 StringBuffer buffer = new StringBuffer(); String line = ""; while ((line = reader.readLine()) != null) { buffer.append(line); } reader.close(); return buffer.toString(); } catch (IOException e) { e.printStackTrace(); } finally { if (connection != null) { connection.disconnect();// 关闭连接 } } return null; } /** * unicode 转换成 中文 * @param theString * @return */ public static String decodeUnicode(String theString) { char aChar; int len = theString.length(); StringBuffer outBuffer = new StringBuffer(len); for (int x = 0; x < len;) { aChar = theString.charAt(x++); if (aChar == '\\') { aChar = theString.charAt(x++); if (aChar == 'u') { int value = 0; for (int i = 0; i < 4; i++) { aChar = theString.charAt(x++); switch (aChar) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': value = (value << 4) + aChar - '0'; break; case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': value = (value << 4) + 10 + aChar - 'a'; break; case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': value = (value << 4) + 10 + aChar - 'A'; break; default: throw new IllegalArgumentException( "Malformed encoding."); } } outBuffer.append((char) value); } else { if (aChar == 't') { aChar = '\t'; } else if (aChar == 'r') { aChar = '\r'; } else if (aChar == 'n') { aChar = '\n'; } else if (aChar == 'f') { aChar = '\f'; } outBuffer.append(aChar); } } else { outBuffer.append(aChar); } } return outBuffer.toString(); } // 测试 public static void main(String[] args) { IPUtils addressUtils = new IPUtils(); // 测试ip 219.136.134.157 中国=华南=广东省=广州市=越秀区=电信 String ip = "125.70.11.136"; String address = ""; try { address = addressUtils.getAddresses("ip="+ip, "utf-8"); } catch (UnsupportedEncodingException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(address); // 输出结果为:广东省,广州市,越秀区 } }==================================== 今天记录下类加载器classloader======================================= 类加载器任务:负责加载java类的字节码文件代码到java虚拟机中。
表 1. ClassLoader 中与加载类相关的方法
方法 | 说明 |
---|---|
getParent() | 返回该类加载器的父类加载器。 |
loadClass(String name) | 加载名称为 name 的类,返回的结果是 java.lang.Class 类的实例。 |
findClass(String name) | 查找名称为 name 的类,返回的结果是 java.lang.Class 类的实例。 |
findLoadedClass(String name) | 查找名称为 name 的已经被加载过的类,返回的结果是 java.lang.Class 类的实例。 |
defineClass(String name, byte[] b, int off, int len) | 把字节数组 b 中的内容转换成 Java 类,返回的结果是 java.lang.Class 类的实例。这个方法被声明为 final 的。 |
resolveClass(Class<?> c) | 链接指定的 Java 类。 |
name
参数的值是类的二进制名称。需要注意的是内部类的表示,如 com.example.Sample$1
和 com.example.Sample$Inner
等表示方式。这些方法会在下面介绍类加载器的工作机制时,做进一步的说明。下面介绍类加载器的树状组织结构。 类加载器的树状组织结构
- a.引导类加载器【bootstrap class loader】: 用来加载java核心库,用源代码实现,不继承java.lang.ClassLoader
- b.扩展类加载器【extensions class loader】:用来加载java扩展库,java虚拟机的实现会提供一个扩展目录。该加载器在此目录里查找并加载java类
- c.系统类加载器【system class loader】根据java应用类类路径(classpath)来加载java类。一般来说java应用的类都是它来完成加载的
- 通过Classloader.getSystemClassLoader()来获取它
sun.misc.Launcher$AppClassLoader@9304b1 sun.misc.Launcher$ExtClassLoader@190d11
类加载器的代理模式
清单 3. com.example.Sample 类
package com.example; public class Sample { private Sample instance; public void setSample(Object instance) { this.instance = (Sample) instance; } }
如 所示,com.example.Sample
类的方法 setSample
接受一个 java.lang.Object
类型的参数,并且会把该参数强制转换成com.example.Sample
类型。测试 Java 类是否相同的代码如 所示。
清单 4. 测试 Java 类是否相同
package classloader;import java.lang.reflect.Method;public class ClassIdentity { public static void main(String[] args) { new ClassIdentity().testClassIdentity(); } public void testClassIdentity() { String classDataRootPath = "G:\\xiaoyang\\classloader\\bin"; //这里的FileSystemClassLoader就是自己开发的类加载器 FileSystemClassLoader fscl1 = new FileSystemClassLoader(classDataRootPath); FileSystemClassLoader fscl2 = new FileSystemClassLoader(classDataRootPath); String className = "com.example.Sample"; try { Class class1 = fscl1.loadClass(className); Object obj1 = class1.newInstance(); Class class2 = fscl2.loadClass(className); Object obj2 = class2.newInstance(); Method setSampleMethod = class1.getMethod("setSample", java.lang.Object.class); setSampleMethod.invoke(obj1, obj2); } catch (Exception e) { e.printStackTrace(); } }}
中使用了类 FileSystemClassLoader
的两个不同实例来分别加载类 com.example.Sample
,得到了两个不同的 java.lang.Class
的实例,接着通过 newInstance()
方法分别生成了两个类的对象 obj1
和 obj2
,最后通过 Java 的反射 API 在对象 obj1
上调用方法 setSample
,试图把对象 obj2
赋值给 obj1
内部的 instance
对象。的运行结果如 所示。
清单 5. 测试 Java 类是否相同的运行结果
java.lang.reflect.InvocationTargetException at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)at java.lang.reflect.Method.invoke(Method.java:597) at classloader.ClassIdentity.testClassIdentity(ClassIdentity.java:26) at classloader.ClassIdentity.main(ClassIdentity.java:9) Caused by: java.lang.ClassCastException: com.example.Sample cannot be cast to com.example.Sample at com.example.Sample.setSample(Sample.java:7) ... 6 more
从 给出的运行结果可以看到,运行时抛出了 java.lang.ClassCastException
异常。虽然两个对象 obj1
和 obj2
的类的名字相同,但是这两个类是由不同的类加载器实例来加载的,因此不被 Java 虚拟机认为是相同的。
===========ok准备工作结束============
了解了这点以后,就可以理解代理模式的设计动机了。
代理模式为保证java核心库的类型安全,所有java应用都至少需要引用java.lang.Object类,也就是说
在运行的时候,java.lang.Object类需要被加载到java虚拟机中。如果这个加载过程由java应用自己的类加载器来完成的话,
很可能存在多个版本的java.lang.Object类,而且这些类是不兼容的。
通过代理模式,对java核心库的类加载工作由引导加载器来统一完成,保证java应用所使用的都是同一版本的java核心库类,
不同的类加载器为相同名称的类创建了额外的名称空间。
相同名称的类可以并存在java虚拟机中,只需要不同的类加载器来加载他们即可。
不同类加载器加载的类之间是不兼容的。这就相当于在java虚拟机内部创建了一个个相互隔离的java类空间
这种技术在许多框架中被用到
加载类的过程
类的加载分两个过程:方法:loadClass() 抛出的是java.lang.classNotFoundException
方法:defineClass() 抛出的是java.lang.NoClassDefFoundeError
类加载器在成功加载某个类后,会把得到的java.lang.Class类的实例缓存起来,下次再请求加载该类的时候,
类加载器直接使用缓存的类的实例。而不会尝试再次加载。也就是
对于一个类加载实例来说,相同全名的类只加载一次,【loadClass方法不会被重复调用】
下面讨论另外一种类加载器:线程上下文类加载器
线程【Thread】上下文类加载器
线程上下文类加载器(context class loader)从JDK1.2开始有
类java.lang.Thread中的方法:获取和设置线程的上下文类加载器;
getContextClassLoader()
setContextClassLoader(ClassLoader cl)
如果没有通过setContextLoader设置的话,线程会 继承父线程的上下文类加载器。
java应用运行的初始【线程上下文类加载器】是【系统类加载器】,在线程中运行代码可以通过此类来加载类和资源
前面提到的类加载器的代理模式并不能解决java应用开发中遇到的类加载器的全部问题。
java提供了很多服务提供者接口SPI(service provider interface)允许第三方为这些接口提供实现
常见的SPI有JDBC JCE JNDI JAXP JBI等,
这些SPI的接口由java核心库提供实现。如JAXP的SPI接口定义包含在javax.xml.parsers包中
这些SPI的实现很可能作为java应用依赖的jar包被包含进来,通过类路径(classpath)来找到,
SPI接口的代码经常需要加载具体的实现类。
这里的实例的真正的类是继承【extends】自javax.xml.parsers.DocumentBuilderFactory,由SPI的实现所提供
如JAXP中现实类是 javax.Xml.Parsers.DocumentBuilderFactory类中的newInstance()方法用来生成一个新的DocumentBuilderFactory实例
如ApacheXerces中实现类是org.apache.xerces.jaxp.DocumentBuilderFactoryImpl
而问题在于, SPI的接口是ava核心库的一部分,是由引导类加载器来加载的,
spi的实现java类一般是由系统类加载器来加载的
引导类加载器 无法找到spi实现类,因为它只加载java核心库,但是线程上下文加载器正好解决了这个问题
java应用的【线程上下文类加载器】默认就是系统的上下文类加载器。
在spi接口的代码中使用线程上下文类加载器,可以成功的加载到spi实现的类
下面介绍另外一种加载类的方法:Class.forName
。
Class.forName
name
表示的是类的全名 initialize
表示是否初始化类 oader
表示加载时使用的类加载器 虽然在绝大多数情况下,系统默认提供的类加载器实现客运满足需求:
但某些情况下,您还是需要为应用开发出自己的类加载器。
比如你的应用通过网络来传输java类的字节代码,为了保证安全,这些代码经过了加密处理,这个时候您需要自己的类加载器来
1.从某个网络地址读取加密后的字节代码
2.接着精心解密和验证,
3.最后定义出要在java虚拟机中运行的类来。下面是两个实例
文件系统类加载器
自己开发的类加载器用来加载存储在文件系统上的 Java 字节代码。完整的实现如 所示。
清单 6. 文件系统类加载器
public class FileSystemClassLoader extends ClassLoader { private String rootDir; public FileSystemClassLoader(String rootDir) { this.rootDir = rootDir; } protected Class findClass(String name) throws ClassNotFoundException { byte[] classData = getClassData(name); if (classData == null) { throw new ClassNotFoundException(); } else { return defineClass(name, classData, 0, classData.length); } } private byte[] getClassData(String className) { String path = classNameToPath(className); try { InputStream ins = new FileInputStream(path); ByteArrayOutputStream baos = new ByteArrayOutputStream(); int bufferSize = 4096; byte[] buffer = new byte[bufferSize]; int bytesNumRead = 0; while ((bytesNumRead = ins.read(buffer)) != -1) { baos.write(buffer, 0, bytesNumRead); } return baos.toByteArray(); } catch (IOException e) { e.printStackTrace(); } return null; } private String classNameToPath(String className) { return rootDir + File.separatorChar + className.replace('.', File.separatorChar) + ".class"; } }
如 所示,类 FileSystemClassLoader
继承自类 java.lang.ClassLoader
。在 中列出的 java.lang.ClassLoader
类的常用方法中,一般来说,自己开发的类加载器只需要覆写 findClass(String name)
方法即可。
java.lang.ClassLoader
类的方法 loadClass()
封装了前面提到的代理模式的实现。
loadClass()的调用步骤:
1.该方法会首先调用 findLoadedClass()
方法来检查该类是否已经被加载过;
2.如果没有加载过的话,会调用父类加载器的 loadClass()
方法来尝试加载该类;
3.如果父类加载器无法加载该类的话,就调用 findClass()
方法来查找该类。
因此,为了保证类加载器都正确实现代理模式,在开发自己的类加载器时,
最好不要覆写 loadClass()
方法,而是覆写 findClass()
方法。
类 FileSystemClassLoader
的 findClass()
方法首先根据类的全名在硬盘上查找类的字节代码文件(.class 文件),然后读取该文件内容,最后通过 defineClass()
方法来把这些字节代码转换成 java.lang.Class
类的实例。
网络类加载器
下面将通过一个【网络类加载器】来说明如何通过类加载器来实现组件的动态更新。
即基本的场景是:
Java 字节代码(.class)文件存放在服务器上,
客户端通过网络的方式获取字节代码并执行。当有版本更新的时候,只需要替换掉服务器上保存的文件即可。
通过类加载器可以比较简单的实现这种需求。
客户端用自己开发的类加载器【NetworkClassLoader】:
类 NetworkClassLoader
负责通过网络下载 Java 类字节代码并定义出 Java 类。它的实现与 FileSystemClassLoader
类似。
在通过NetworkClassLoader
加载了某个版本的类之后,一般有两种做法来使用它。
第一种做法是使用 Java 反射 API。
第二种做法是使用接口。
需要注意的是,并不能直接在客户端代码中引用从服务器上下载的类,因为客户端代码的类加载器找不到这些类。
使用 Java 反射 API 可以直接调用 Java 类的方法。
而使用接口的做法则是把接口的类放在客户端中,从服务器上加载实现此接口的不同版本的类。在客户端通过相同的接口来使用这些实现类。网络类加载器的具体代码见 。
在介绍完如何开发自己的类加载器之后,下面说明类加载器和 Web 容器的关系。
类加载器与 Web 容器
对于运行在 Java EE™容器中的 Web 应用来说,类加载器的实现方式与一般的 Java 应用有所不同。不同的 Web 容器的实现方式也会有所不同。以 Apache Tomcat 来说,每个 Web 应用都有一个对应的类加载器实例。该类加载器也使用代理模式,所不同的是它是首先尝试去加载某个类,如果找不到再代理给父类加载器。这与一般类加载器的顺序是相反的。这是 Java Servlet 规范中的推荐做法,其目的是使得 Web 应用自己的类的优先级高于 Web 容器提供的类。这种代理模式的一个例外是:Java 核心库的类是不在查找范围之内的。这也是为了保证 Java 核心库的类型安全。
绝大多数情况下,Web 应用的开发人员不需要考虑与类加载器相关的细节。下面给出几条简单的原则:
- 每个 Web 应用自己的 Java 类文件和使用的库的 jar 包,分别放在
WEB-INF/classes
和WEB-INF/lib
目录下面。 - 多个应用共享的 Java 类文件和 jar 包,分别放在 Web 容器指定的由所有 Web 应用共享的目录下面。
- 当出现找不到类的错误时,检查当前类的类加载器和当前线程的上下文类加载器是否正确。
在介绍完类加载器与 Web 容器的关系之后,下面介绍它与 OSGi 的关系。
类加载器与 OSGi
OSGi™是 Java 上的动态模块系统。它为开发人员提供了面向服务和基于组件的运行环境,并提供标准的方式用来管理软件的生命周期。OSGi 已经被实现和部署在很多产品上,在开源社区也得到了广泛的支持。Eclipse 就是基于 OSGi 技术来构建的。
OSGi 中的每个模块(bundle)都包含 Java 包和类。模块可以声明它所依赖的需要导入(import)的其它模块的 Java 包和类(通过 Import-Package
),也可以声明导出(export)自己的包和类,供其它模块使用(通过 Export-Package
)。也就是说需要能够隐藏和共享一个模块中的某些 Java 包和类。这是通过 OSGi 特有的类加载器机制来实现的。OSGi 中的每个模块都有对应的一个类加载器。它负责加载模块自己包含的 Java 包和类。当它需要加载 Java 核心库的类时(以 java
开头的包和类),它会代理给父类加载器(通常是启动类加载器)来完成。当它需要加载所导入的 Java 类时,它会代理给导出此 Java 类的模块来完成加载。模块也可以显式的声明某些 Java 包和类,必须由父类加载器来加载。只需要设置系统属性 org.osgi.framework.bootdelegation
的值即可。
假设有两个模块 bundleA 和 bundleB,它们都有自己对应的类加载器 classLoaderA 和 classLoaderB。在 bundleA 中包含类com.bundleA.Sample
,并且该类被声明为导出的,也就是说可以被其它模块所使用的。bundleB 声明了导入 bundleA 提供的类com.bundleA.Sample
,并包含一个类 com.bundleB.NewSample
继承自 com.bundleA.Sample
。在 bundleB 启动的时候,其类加载器 classLoaderB 需要加载类 com.bundleB.NewSample
,进而需要加载类 com.bundleA.Sample
。由于 bundleB 声明了类 com.bundleA.Sample
是导入的,classLoaderB 把加载类 com.bundleA.Sample
的工作代理给导出该类的 bundleA 的类加载器 classLoaderA。classLoaderA 在其模块内部查找类 com.bundleA.Sample
并定义它,所得到的类 com.bundleA.Sample
实例就可以被所有声明导入了此类的模块使用。对于以 java
开头的类,都是由父类加载器来加载的。如果声明了系统属性 org.osgi.framework.bootdelegation=com.example.core.*
,那么对于包com.example.core
中的类,都是由父类加载器来完成的。
OSGi 模块的这种类加载器结构,使得一个类的不同版本可以共存在 Java 虚拟机中,带来了很大的灵活性。不过它的这种不同,也会给开发人员带来一些麻烦,尤其当模块需要使用第三方提供的库的时候。下面提供几条比较好的建议:
- 如果一个类库只有一个模块使用,把该类库的 jar 包放在模块中,在
Bundle-ClassPath
中指明即可。 - 如果一个类库被多个模块共用,可以为这个类库单独的创建一个模块,把其它模块需要用到的 Java 包声明为导出的。其它模块声明导入这些类。
- 如果类库提供了 SPI 接口,并且利用线程上下文类加载器来加载 SPI 实现的 Java 类,有可能会找不到 Java 类。如果出现了
NoClassDefFoundError
异常,首先检查当前线程的上下文类加载器是否正确。通过Thread.currentThread().getContextClassLoader()
就可以得到该类加载器。该类加载器应该是该模块对应的类加载器。如果不是的话,可以首先通过class.getClassLoader()
来得到模块对应的类加载器,再通过Thread.currentThread().setContextClassLoader()
来设置当前线程的上下文类加载器。
总结
类加载器是 Java 语言的一个创新。它使得动态安装和更新软件组件成为可能。本文详细介绍了类加载器的相关话题,包括基本概念、代理模式、线程上下文类加载器、与 Web 容器和 OSGi 的关系等。开发人员在遇到 ClassNotFoundException
和 NoClassDefFoundError
等异常的时候,应该检查抛出异常的类的类加载器和当前线程的上下文类加载器,从中可以发现问题的所在。在开发自己的类加载器的时候,需要注意与已有的类加载器组织结构的协调。
文章出处:http://www.ibm.com/developerworks/cn/java/j-lo-classloader/
java中有三种移位运算符[<<左移的位数]
<< : 左移运算符,num << 1,相当于num乘以2
>> : 右移运算符,num >> 1,相当于num除以2
>>> : 无符号右移,忽略符号位,空位都以0补齐
下面来看看这些移位运算都是怎样使用的
public class Test { public static void main(String[] args) { int number = 10; printInfo(number);//原始数二进制 number = number << 1;//左移一位
printInfo(number); number = number >> 1;//右移一位 printInfo(number); } /** * 输出一个int的二进制数 * @param num */ private static void printInfo(int num){ System.out.println(Integer.toBinaryString(num)); }}运行结果为:
1010101001010结论:
如面试官问你:HashMap和HashTable有什么区别,一个比较简单的回答是:
1、HashMap是非线程安全的,HashTable是线程安全的。
2、HashMap的键和值都允许有null值存在,而HashTable则不行。
3、因为线程安全的问题,HashMap效率比HashTable的要高。
Java中的另一个线程安全的与HashMap极其类似的类是什么Java中数据存储方式最底层的两种结构,
一种是数组,特点:连续空间,寻址迅速,但是在删除或者添加元素的时候需要有较大幅度的移动,所以查询速度快,增删较慢
一种是链表,特点:空间不连续,寻址困难,增删元素只需修改指针,所以查询慢、增删快。有没有一种数据结构来综合一下数组和链表,以便发挥他们各自的优势?答案是肯定的!就是:哈希表。哈希表具有较快(常量级)的查询速度,及相对较快的增删速度,所以很适合在海量数据的环境中使用。一般实现哈希表的方法采用“拉链法”,我们可以理解为“链表的数组”,如下图:
从上图中,我们可以发现哈希表是由数组+链表组成的,一个长度为16的数组中,【这里为什么用16,因为默认的大小是capacity大小是16】
每个元素存储的是一个链表的头结点。那么这些元素是按照什么样的规则存储到数组中呢。一般情况是通过hash(key)%len获得,也就是元素的key的哈希值对数组长度取模得到。比如上述哈希表中,
定位置:
12%16=12,
28%16=12,
108%16=12,
140%16=12。
所以12、28、108以及140都存储在数组下标为12的位置。
它的内部其实是用一个Entity数组来实现的,属性有key、value、next。接下来我会从初始化阶段详细的讲解HashMap的内部结构。
1、初始化 首先来看三个常量:
static final int DEFAULT_INITIAL_CAPACITY = 16; 初始容量:16 static final int MAXIMUM_CAPACITY = 1 << 30; 最大容量:2的30次方:1073741824 左移30位也就是2的30次幂 static final float DEFAULT_LOAD_FACTOR = 0.75f; 装载因子,扩充数组大小的时候用 来看个无参构造方法,也是我们最常用的:- public HashMap() {
- this.loadFactor = DEFAULT_LOAD_FACTOR;
- threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
- table = new Entry[DEFAULT_INITIAL_CAPACITY];
- init();
- }
loadFactor、threshold的值在此处没有起到作用,不过他们在后面的扩容方面会用到,此处只需理解table=new Entry[DEFAULT_INITIAL_CAPACITY].说明,默认就是开辟16个大小的空间。另外一个重要的构造方法:
- public HashMap(int initialCapacity, float loadFactor) {
- if (initialCapacity < 0)
- throw new IllegalArgumentException("Illegal initial capacity: " +
- initialCapacity);
- if (initialCapacity > MAXIMUM_CAPACITY)
- initialCapacity = MAXIMUM_CAPACITY;
- if (loadFactor <= 0 || Float.isNaN(loadFactor))
- throw new IllegalArgumentException("Illegal load factor: " +
- loadFactor);
- // Find a power of 2 >= initialCapacity
- int capacity = 1;
- while (capacity < initialCapacity)
- { capacity <<= 1; }
- this.loadFactor = loadFactor;
- threshold = (int)(capacity * loadFactor);
- table = new Entry[capacity];
- init();
- }
就是说传入参数的构造方法,我们把重点放在:【这里很重要,因为是左移2的n次幂,所以一定是偶数。。】
- while (capacity < initialCapacity)
- capacity <<= 1;
因为是循环,所以多次循环后的值为比参数大的2的n次幂的数值
上面,该代码的意思是,实际的开辟的空间要大于传入的第一个参数的值。举个例子: new HashMap(7,0.8),loadFactor为0.8,capacity为7,通过上述代码后,capacity的值为:8.(1 << 2的结果是4,2 << 2的结果为8)。
所以,最终capacity的值为8,最后通过new Entry[capacity]来创建大小为capacity的数组,所以,这种方法最红取决于capacity的大小。
2、put(Object key,Object value)操作
当调用put操作时,首先判断key是否为null,如下代码1处:- <p>public V put(K key, V value) {
- if (key == null)
- return putForNullKey(value);
- int hash = hash(key.hashCode());
- int i = indexFor(hash, table.length); //这里就是上图hashMap结构中数据的大小
- for (Entry<K,V> e = table[i]; e != null; e = e.next) {
- Object k;
- if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
- V oldValue = e.value;
- e.value = value;
- e.recordAccess(this);
- return oldValue;
- }
- }</p><p> modCount++;
- addEntry(hash, key, value, i);
- return null;
- }</p>
如果key是null,则调用如下代码:
- private V putForNullKey(V value) {
- for (Entry<K,V> e = table[0]; e != null; e = e.next) {
- if (e.key == null) {
- V oldValue = e.value;
- e.value = value;
- e.recordAccess(this);
- return oldValue;
- }
- }
- modCount++;
- addEntry(0, null, value, 0);
- return null;
- }
就是说,获取Entry的第一个元素table[0],并基于第一个元素的next属性开始遍历,直到找到key为null的Entry,将其value设置为新的value值。 如果没有找到key为null的元素,则调用如上述代码的addEntry(0, null, value, 0);增加一个新的entry,代码如下:
- void addEntry(int hash, K key, V value, int bucketIndex) {
- Entry<K,V> e = table[bucketIndex];
- table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
- if (size++ >= threshold)
- resize(2 * table.length);
- }
先获取第一个元素table[bucketIndex],传给e对象,新建一个entry,key为null,value为传入的value值,next为获取的e对象。如果容量大于threshold,容量扩大2倍。 如果key不为null,这也是大多数的情况,重新看一下源码:
- public V put(K key, V value) {
- if (key == null)
- return putForNullKey(value);
- int hash = hash(key.hashCode());//---------------2---------------
- int i = indexFor(hash, table.length);
- for (Entry<K,V> e = table[i]; e != null; e = e.next) { //--------------3-----------
- Object k;
- if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
- V oldValue = e.value;
- e.value = value;
- e.recordAccess(this);
- return oldValue;
- }
- }//-------------------4------------------
- modCount++;//----------------5----------
- addEntry(hash, key, value, i);-------------6-----------
- return null;
- }
看源码中2处,首先会进行key.hashCode()操作,获取key的哈希值,hashCode()是Object类的一个方法,为本地方法,内部实现比较复杂,我们 会在后面作单独的关于Java中Native方法的分析中介绍。hash()的源码如下:
- static int hash(int h) {
- // This function ensures that hashCodes that differ only by
- // constant multiples at each bit position have a bounded
- // number of collisions (approximately 8 at default load factor).
- h ^= (h >>> 20) ^ (h >>> 12);
- return h ^ (h >>> 7) ^ (h >>> 4);
- }
int i = indexFor(hash, table.length);的意思,相当于int i = hash % Entry[].length;得到i后,就是在Entry数组中的位置,(上述代码5和6处是如果Entry数组中不存在新要增加的元素,则执行5,6处的代码,如果存在,即Hash冲突,则执行 3-4处的代码,此处HashMap中采用链地址法解决Hash冲突。此处经网友bbycszh指正,发现上述陈述有些问题)。重新解释:其实不管Entry数组中i位置有无元素,都会去执行5-6处的代码,如果没有,则直接新增,如果有,则将新元素设置为Entry[0],其next指针指向原有对象,即原有对象为Entry[1]。具体方法可以解释为下面的这段文字:(3-4处的代码只是检查在索引为i的这条链上有没有key重复的,有则替换且返回原值,程序不再去执行5-6处的代码,无则无处理)
上面我们提到过Entry类里面有一个next属性,作用是指向下一个Entry。如, 第一个键值对A进来,通过计算其key的hash得到的i=0,记做:Entry[0] = A。一会后又进来一个键值对B,通过计算其i也等于0,现在怎么办?HashMap会这样做:B.next = A,Entry[0] = B,如果又进来C,i也等于0,那么C.next = B,Entry[0] = C;这样我们发现i=0的地方其实存取了A,B,C三个键值对,他们通过next这个属性链接在一起,也就是说数组中存储的是最后插入的元素。
到这里为止,HashMap的大致实现,我们应该已经清楚了。当然HashMap里面也包含一些优化方面的实现,这里也说一下。比如:Entry[]的长度一定后,随着map里面数据的越来越长,这样同一个i的链就会很长,会不会影响性能?HashMap里面设置一个因素(也称为因子),随着map的size越来越大,Entry[]会以一定的规则加长长度。
=======================================ThreadLocal总结===============================
今天比较忙,先引出个标题,等今天问题解决再考虑这个
参考文档:http://www.iteye.com/topic/103804
自己的代码:
import com.jt.core4.util.StringUtil;public class AuthenticatorHelper { public static ThreadLocal=============================== 异常总结,============================treadSession=new ThreadLocal (); public static ThreadLocal treadUser=new ThreadLocal (); public static String getSessionId() { String sessionId=(String) treadSession.get(); if (sessionId == null||sessionId.length()==0) { sessionId=StringUtil.getRandomString(32); treadSession.set(sessionId); } return sessionId; } public static void setSessionId(String sessionId){ treadSession.set(sessionId); } public static int getUserId() { Integer userId=(Integer) treadUser.get(); if(userId==null) return 0; else return userId; } public static void setUserId(int userId){ treadUser.set(userId); }}
首先,得导入jar包 jsp-api-2.2-sources.jar
(如果你的项目中使用了maven可以在pom.xml文件中添加
- <dependency>
- <groupId>javax.servlet.jsp</groupId>
- <artifactId>jsp-api</artifactId>
- <version>2.2</version>
- <scope>provided</scope>
- </dependency>
jar文件引用。
)
第二步,定义一个用来实现标签功能的java类,例如:DateConvert.java
- import java.io.IOException;
- import java.text.SimpleDateFormat;
- import java.util.Date;
- import javax.servlet.jsp.JspException;
- import javax.servlet.jsp.tagext.TagSupport;
- /**
- * 数据类型转换
- * @author LiDuanqiang
- *
- */
- @SuppressWarnings("serial")
- public class DateConvert extends TagSupport{
- private String longTime;
- @Override
- public int doStartTag() throws JspException {
- long l = 0l;
- if (longTime!=null&&!longTime.equals("")) {
- l = Long.parseLong(longTime);
- }
- Date date = new Date(l);
- SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
- String targetTime = format.format(date);
- try {
- super.pageContext.getOut().write(targetTime);
- } catch (IOException e) {
- e.printStackTrace();
- }
- return super.doStartTag();
- }
- //setter and getter
- public void setLongTime(String longTime) {
- this.longTime = longTime;
- }
- }
第三步,可在WEB-INF目录下定义一个*.tld文件,例如dateConvert.tld:
- <?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN"
- "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">
- <taglib>
- <tlib-version>1.0</tlib-version>
- <jsp-version>1.2</jsp-version>
- <short-name>ct</short-name>
- <uri>/dateConvert</uri>
- <tag>
- <name>longStr</name>
- <tag-class>org.up.snapshot.utils.DateConvert</tag-class>
- <body-content>JSP</body-content>
- <attribute>
- <name>longTime</name>
- <required>true</required>
- <rtexprvalue>true</rtexprvalue>
- </attribute>
- </tag>
- </taglib>
第四步,在web.xml文件中引用你的*.tld文件:
- <taglib>
- <taglib-uri>/dateConvert</taglib-uri>
- <taglib-location>dateConvert.tld</taglib-location>
- </taglib>
- <welcome-file-list>
- <welcome-file>dateConvert.jsp</welcome-file>
- </welcome-file-list>
第五步,在你的页面引入自定义标签库进行使用,例如:dateConvert.jsp:
- <%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
- <%@ taglib uri="/dateConvert" prefix="ct"%>
- <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
- <html>
- <head>
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
- <title>数据类型转换</title>
- </head>
- <body>
- <ct:longStr longTime="1314842011312"></ct:longStr>
- </body>
- </html>
以上代码实现的是将长整型的数据通过自定义标签转换成指定日期格式进行输出。当然,大家可以定义功能更加强大的java类来实现你的标签功能。
转载地址:https://blog.csdn.net/lixld/article/details/36876177 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!