代码拉取完成,页面将自动刷新
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title><![CDATA[Linux目录结构]]></title>
<url>%2Fblog%2F2019%2F01%2F17%2FLinux%E7%9B%AE%E5%BD%95%E7%BB%93%E6%9E%84%2F</url>
<content type="text"><![CDATA[Linux目录结构1. /bin/ 存放系统命令的目录,普通用户和 root 都可以执行。不过放在 /bin 下的命令在单用户模式下也可以执行。2. /sbin/保存与系统环境设置相关的命令,只有 root 可以使用这些命令进行系统环境设置,但是有些命令可以允许普通用户查看。3. /usr/bin/存放系统命令的目录,普通用户和 root 都可以执行。这些命令和系统启动无关。在单用户模式下不能执行。4. /usr/sbin/存放根文件系统不必要的系统管理命令,如多数服务程序,只有 root 可使用。所有 “sbin” 目录中可以保存的命令只有 root 可以使用,“bin” 目录中保存的命令所有用户都可以使用。5. /boot/系统启动目录,保存与系统启动相关的文件。如内核文件和穹顶引导程序(grub)文件等6. /dev/设备文件保存位置。7. /etc/配置文件保存位置。系统内所有采用默认安装方式(rpm 安装)的服务配置文件全部保存在此目录中,如用户信息、服务的启动脚本、常用服务的配置文件等。8. /home/普通用户的宿主目录。在创建用户时。每个用户要有一个默认登录和保存自己数据的位置,就是用户的宿主目录,所有普通用户的宿主目录是在 /home/ 下建立一个和用户名相同的目录。如用户 zhangsnke 的宿主目录就是 /home/zhansnke9. /lib/系统调用的函数库保存位置。 10. /lost+found/ 当系统意外崩溃或意外关机时,产生的一些文件碎片会存放在这里。在系统启动的过程中, fsck 工具会检查这里,并修复已经损坏的文件系统。这个目录只在每个分区中出现,例如 /lost+found/ 就是根据分区的备份恢复目录,/boot/lost+found/ 就是 /boot 分区的备份恢复目录 11. /media/ 挂载目录。系统建议是用来挂载媒体设备的,如软盘和光盘 12. /mnt/ 挂载目录。早期 Linux 中只有这一个挂载目录,并没有细分。现在系统建议这个目录用来挂载额外的设备,如 U 盘、移动硬盘和其他操作系统的分区。 13. /misc/ 挂载目录。系统建议用来挂载 NFS 服务的共享目录。虽然系统准备了三个默认挂载目录 /media/ 、/mnt/、/misc/,但是到底在哪个目录中挂载什么设备可以有管理员自己决定。如:在默认挂载目录下新建目录, /mnt/cdrom/ 挂载光盘,/mnt/usb/ 挂载U 盘 14. /opt/ 第三方安装的软件保存位置。这个目录是放置和安装其他软件的位置,手工安装的源码包软件都可以安装在这个目录中。但是也可以由用户自己定义安装目录,如安装在 /usr/local/ 目录中。 15. /proc/ 虚拟文件系统。该目录中的数据并不保存在硬盘上,而是保存到内存中。主要保存系统的内核、进程、外部设备状态和网络状态等。如 /proc/cpuinfo 是保存 CPU 信息的,/proc/devices 是保存设备驱动列表的,/proc/filesystems 是保存文件系统列表的,/proc/net/ 是保存网络协议信息的。 16. /sys/ 虚拟文件系统。和 /proc/ 目录相似,该目录中的数据都保存在内存中,主要保存与内核相关的信息。 17. /root/ root 的宿主目录。普通用户宿主目录在 /home/ 下,root 宿主目录直接在 “/” 下。 18. /srv/ 服务数据目录。一些系统服务启动之后,可以在这个目录中保存所需要的数据。 19. /temp/ 临时目录。系统存放临时文件的目录,在该目录下,所有的用户都可以访问和写入。建议此目录中不保存重复数据,最好每次开机都吧这个目录清空。 20. /usr/ 系统软件资源目录。注意 usr 不是 user 的缩写,而是“UNIX Software Resource” 的缩写,所以不是存放用户数据目录,而是存放系统软件资源的目录。系统中安装的软件大多数保存在这里。 21. /usr/lib/ 应用程序调用的函数库保存位置。 22. /usr/XIIR6/ 图形界面系统保存位置。 23. /usr/local/ 手工安装的软件保存位置。我们一般建议源码包软件安装在这个位置。 24. /usr/share/ 应用程序的资源文件保存位置,如帮助文档、说明文档和字体目录。 25. /usr/src/ 源码包保存位置。我们手工下载的源码包和内核源码包都可以保存到这里。 26. /var/ 动态数据保存位置。主要保存缓存、日志以及软件运行所产生的文件。 27. /var/www/ RPM 包安装的 Apache 的网页主目录。 28. /var/lib/ 程序运行中需要调用或改变数据保存位置。如 Mysql 的数据库保存在 /var/lib/mysql/ 目录中。 29. /var/log/ 系统日志保存位置。 30. /var/run/ 一些服务和程序运行后,它们的 PID (进程 ID )保存位置 31. /var/spool/ 防止队列数据的目录。就是排队等候其他程序使用的数据,比如邮件队列和打印队列。 32. /var/spool/mail/ 新收到的邮件队列保存位置。系统新收到的邮件会保存在此目录。 33. /var/spool/cron/ 系统的定时任务队列保存位置。系统的计划会保存在这里。]]></content>
<categories>
<category>Linux基础知识</category>
</categories>
<tags>
<tag>Linux</tag>
<tag>目录结构</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Linux使用注意事项]]></title>
<url>%2Fblog%2F2019%2F01%2F17%2FLinux%E4%BD%BF%E7%94%A8%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9%2F</url>
<content type="text"><![CDATA[Linux注意事项1. Linux 是严格区分大小写 Linux 是严格区分大小写的,包括文件名和目录名、命令、命令选项、配置文件设置选项等。 2.Linux 中所有内容以文件形式保存,包括硬件设备 Linux 中所有内容都是以文件的形式保存和管理的,硬件设备也是文件,这和 Windows 完全不同,Windows 是通过设备管理器来管理硬件的。Linux 的设备文件保存在 /dev/ 目录中,硬盘文件是 /dev/sd[a-p],光盘文件是 /dev/hdc 等。 3.Linux 不靠扩展名区分文件类型 Windows 是依赖扩展名区分文件类型的,比如 “.txt”是文本文件,“.exe是执行文件”,“.ini”是配置文件,“.mp4”是电影格式等。但 Linux 不是靠扩展名区分文件类型的,而是靠权限位标识来确定文件类型的,常见的文件类型只有普通文件、目录、链接文件、块设备文件、字符设备文件等几种。Linux 的可执行文件不过就是普通文件被赋予了可执行权限而已。 但 Linux 中一些特殊的文件还是要写“扩展名”的,并不是 Linux 一定要靠扩展名来识别文件类型,写这些扩展名是为了帮助管理员来区分不同的文件类型。这样文件扩展名主要有以下几种: 压缩包:Linux 下常见的压缩文名有.gz、.bz2、.zip、.tar.gz、.tar.bz2、.tgz等。为什么压缩包一定要写扩展名呢?如果不写扩展名,那么管理员不容易判断压缩包的格式,直观一点更方便。就算是没写扩展名,在Linux 中一样可以压缩,不影响使用。 二进制软件包:CentOS 中所使用的二进制安装包是 RPM 包,所有的 RPM 包都用 “.rpm”扩展名结尾,目的是让管理员一目了然。 程序文件:Shell 脚本一般用 “.sh”扩展名结尾,其他还有用“.c”扩展名结尾的 C 语言文件等。 网页文件:网页文件一般使用“*.php”等结尾,这是网页服务器的要求。 4.Linux 中所有的存储设备都必须挂载之后才能使用 Linux 中所有的存储设备都有自己的设备文件名,这些设备必须在挂载之后才能使用,包括硬盘、U盘和光盘。挂载就是给这些存储设备分配盘符,只不过Windows 中的盘符用英文字母表示,而Linux 中的盘符则是一个已经建立的空目录。我们把这些空目录叫做 挂载点(可以理解为 Windows的盘符),把设备文件(如 /dev/sdb )和挂载点(已建立的空目录)连接的过程叫做 挂载。 5.Windows 下的程序不能直接在 Linux 中使用 Linux 和 Windows 是不同的操作系统,可以安装和使用的软件也是不同的,所以在 Windows 中安装和使用的软件是不能在 Linux 中安装的。]]></content>
<categories>
<category>Linux基础知识</category>
</categories>
<tags>
<tag>Linux</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Java多线程]]></title>
<url>%2Fblog%2F2019%2F01%2F15%2FJava%E5%A4%9A%E7%BA%BF%E7%A8%8B%2F</url>
<content type="text"><![CDATA[引言线程是进程中执行的最小运算单位,也是执行处理机调度的基本单位。一个线程只属于一个进程,一个进程包含多个线程。 多线程:指的是这个程序(一个进程)运行时产生了多个线程。 并行与并发: 并行(Parallel):多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时。 并发(Concurrent):通过CPU调度算法,让用户看上去同时执行,实际上从CPU操作层面不是真正的同时。并发往往在场景中有公共的资源,那么针对这个公共资源往往产生瓶颈,一般会用TPS或者QPS来反应这个系统的处理能力。 线程安全与同步: 线程安全:经常用来描述一段代码。指在并发的情景下,该代码经过多线程使用,线程的调度顺序不影响任何结果。 同步:Java中的同步指的是通过人为的控制和调度,保证共享资源的多线程访问成为线程安全,来保证记过的准确。线程安全优先级高于性能。 线程状态线程状态 线程转换 blocked状态线程在 Running 的过程中可能会遇到阻塞(Blocked): 调用 join() 和 sleep() 方法,sleep() 方法时间结束或被打断,join() 终端,I/O 完成都会回到 Runnable 状态,等待JVM 调度。 调用 wait(),使该线程处于等待池(wait blocked pool),直到 notify()/notifyAll(),线程被唤醒被放到锁定池(lock blocked pool),释放同步锁使线程回到可运行状态(Runable)。 对 Running 状态的线程加同步锁(Synchronized)使其进入锁定池(lock blocked pool),同步锁释放可以进入可运行状态(Runnable) 此外,在 Runable 状态的线程是处于被调度的线程,此时的调度顺序是不一定的。Thread类中的 yield() 方法可以让一个running 状态的线程转入 runnable。 每个对象都有的方法(机制)Synchronized、wait、notify 是任何对象都具有的同步工具。 monitor点击查看详情是应用于同步问题的人工线程调度工具。讲其本质,首先就要明确 monitor 的概念,Java 中的每个对象都有一个监视器,来监测并发代码的重入。在非多线程编码时,该监视器不发挥作用,反之如果在 synchronized 范围内,监视器发挥作用。 wait()/notify() 必须存在于 synchronized 块中。并且,这三个关键字针对的是同一个监视器(某对象的监视器)。这意味着 wait 之后,其他的线程可以进入同步块执行。 当某代码并不持有监视器的使用权时(如图中5的状态,即脱离同步块)去 wait 或 notify ,会抛出 java.lang.IllegalMonitorStateException。也包括在 synchronized 块中去调用另一个对象的 wait /notify ,因为不同对象的监视器不同,也会抛出此异常。 用法: synchronized 单独使用: 代码块:如下,在多线程环境下,synchronized 块中的方法获取了 lock 实例的 monitor,如果实例相同,那么只有一个线程能执行该代码块内容12345678public class Thread1 implements Runnable { Object lock; public void run(){ synchronized(lock){ ..do something }} } 直接用于方法:相当于上面代码中用 lock 来锁定的效果,实际获取的是 Thread1 类的 monitor 。更进一步,如果直接修饰的是 static 方法,则锁定该类所有实例。12345public class Thread1 implements Runnable{ public synchronized void run(){ ..do something }} synchronized,wait,notify 结合:景点场景生产者和消费者问题123456789101112131415161718192021222324252627282930/*** 生产者产出产品*/public synchronized void produce(){ if (this.product >= MAX_PRODUCT){ try{ }catch(InterruptedException e){ e.printStackTrace(); } return; } this.product++; System.out.println("生产者生产第"+ this.product + "个产品"); notifyAll(); //通知等待区的消费者可以取出产品}/*** 消费者消费产品*/public synchronized void consume(){ if (this.product <= MIN_PRODUCT){ try{ }catch(InterruptedException e){ e.printStackTrace(); } return; } System.out.println("消费者取走了第"+ this.product +"个产品"); this.product--; notifyAll(); //通知等待区的生产者可以生产产品了} volatile多线程的内存模型:main memory(主存)、working memory(线程栈),在处理数据时,线程会把值从主存 load 到本地栈,完成操作后再 save 回去(volatile关键字的作用:每次针对该变量的操作都会激发一次 load and save) 针对多线程使用的变量,如果不是 volatile 或者 final 修饰的,很有可能产生不可预知的结果(另一个线程修改了这个值,但是之后再某线程看到的是修改之前的值)。其实道理上讲同一个属性本身只有一个副本。但是多线程会缓存值,本质上,volatile 就是不去缓存,直接取值。在线程安全的情况下加 volatile 会牺牲性能。 基本线程类基本线程类指的是 Thread 类,Runnable 接口,Callable 接口Thread 类实现了 Runnable 接口,启动一个线程的方法12MyThread my = new MyThread();my.start(); Thread类相关方法12345678 //当线程可以转让CPU控制权,让别的就绪态的线程运行(切换)public static Thread.yield()//暂停一段时间public static Thread.sleep()//在一个线程中调用other.join(),将等待other执行完后才继续本线程public join()//后两个函数皆可以被打断public interrupte() 关于中断它并不像 stop 方法那样会中断一个正在运行的线程。线程会不时地检测中断标识位,以判断线程是否应该被中断(中断标示值是否为 true )。中断只会影响到 wait 状态、sleep 状态和 join 状态。被打断的线程会抛出 InterruptedException。Thread.interrupted() 检查当前线程是否发生中断,返回booleansynchronized 在获取锁的过程中是不能被中断的。 中断是一个状态,interrupt() 方法只是将这个状态置为 true 而已。所以说正常运行的程序不去检查状态,就不会终止,而 wait 等阻塞方法会去检查并抛出异常。如果在正常运行的程序中添加 while(!Thread.interruped()),则同样可以在中断后离开代码体。 Thread类最佳实践写的时候最好要设置线程名称 Thread.name ,并设置线程组 ThreadGroup,目的是方便管理,如果出现问题,打印线程栈(jstack-pid)一眼就能看出是哪个线程出现问题,这个线程的主要作用是什么 如何获取线程中的异常线程是独立执行的代码片段,线程的问题应该由线程自己来解决,而不要委托到外部。 线程方法的异常应该在线程代码边界之内(run方法内)进行try catch并处理掉,不能捕获从线程中逃逸的异常。 JDK1.5之后有了一个 Thread.UncaughtExceptionHandler 新接口,它允许我们再每一个 Thread 对象上添加一个异常处理器。Thread.UncaughtExceptionHandler.uncaughtException() 方法会在线程因未捕获的异常而面临死亡时被调用。123456public class MyUncheckedExceptionHandler implements UncaughtExceptionHandler{ @Override public void uncaughtException(Thread t , Throwable e){ System,out.println("捕获异常处理方法"+ e); }} 三种方式使用该线程异常捕获器: 在创建线程的时候进行设置 123Thread t = new Thread(new ExceptionThread());t.setUncaughtExceptionHandler(new MyUncheckedExceptionHandler());t.start(); 使用 Executors 创建线程时,还可以在 ThreadFactory 中设置 123456789ExecutorService exec = Executors.newCachedThreadPool(new ThreadFactory(){ @Override public Thread newThread(Runnable r){ Thread thread = new Thread(r); thread.setUncaughtExceptionHandler(new MyUncheckedExceptionHandler()); return thread; }});exec.excute(new ExceptionThread()); 如果只需要一个线程异常处理器处理线程的异常,可以设置一个默认的线程异常捕获器;如果没有指定线程的异常捕获器,而且线程组也没有设置(线程组不用考虑,因为这是一个不成功的尝试),那么就会使用默认的线程异常捕获器 12//设置默认的线程异常捕获器Thread.setDefaultUncaughtExceptionHandler(new MyUncheckedExceptionHandler()); Runnable与Thread类似 Callablefuture 模式:并发模式的一种,可以有两种形式,即无阻塞和阻塞,分别是 isDone 和 get,其中 Future 对象用来存放 该线程的返回值以及状态。12345ExecutorService e = Executors.newFixedThreadPool(3)//submit 方法有多重参数版本,以及支持callable 也能够支持 runnable 接口类型Future future = e.submit(new myCallable());future.isDone(); //return boolean 无阻塞future.get(); //return 返回值,阻塞直到该线程运行结束 高级多线程控制类Java1.5提供了一个非常高效实用的多线程包:java.util.concurrent,提供了大量高级工具,可以帮助开发者编写高效、易维护、结构清晰的Java多线程程序。 1.ThreadLocal类 用处:保存线程的独立变量。对一个线程类(继承自 Thread )当使用 ThreadLocal 维护变量时,ThreadLocal 为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响到其他线程所对应的副本。常用于用户登录控制,如记录 session 信息。 实现:每个 Thread 都持有一个 ThreadLocalMap 类型的变量(该类是一个轻量级的 Map ,功能与 map 一样,区别是桶里放的是 entry 而不是 entry 的链表。功能还会死一个 map)以本身为 key,以目标为 value。主要方法式 get() 和 set(T a),set 之后在 map 里维护一个 threadLocal ->a ,get时将 a 返回。ThreadLocal 是一个特殊的容器。 2.原子类(AtomicInteger、AutoBoolean…)如果使用 atomic wrapper class 如 atomicInteger,或者使用自己保证原子的操作,等同于 synchronized12//返回值为booleanAtomicInteger.compareAndSet(int expect,int update) 该方法可用于实现乐观锁123456if (b.value.compareAndSet(old,value)){ return;}else{ //try again // if that fails,rollback and log} AtomicReference对于 AtomicReference 来讲,也许对象会出现 属性丢失的情况,即 oldObject == current,但是 oldObject.getPropertyA != current.getPropertyA。这时候 AtomicStampedReference派上用处,加上了版本号。 3.Lock类locak 在 java.util.concurrent 包内。123ReentrantLockReentrantReadWriteLock.ReadLockReentrantReadWriteLock.WriteLock 主要目的是和 synchronized 一样,两者都是为了解决同步问题,处理资源争端而产生的技术。区别:lock 更灵活,可以自由定义多把锁的枷锁解锁顺序(synchronized 要按照先加的后解锁顺序)提供多种枷锁方案,lock 阻塞式,trylock 无阻塞式,lockInterruptily 可打断式,还有 trylock 带超时版本。本质上和监视器锁(即 synchronized是一样的)和Condition类的结合,性能更高。 ReentrantLock可重入的意义在于持有锁的线程可以继续持有,并且要释放对等的次数后才是真正释放该锁。使用方法: 先 new 一个实例 1static ReentrantLock r = new ReentrantLock(); 枷锁 1r.lock()或r.lockInterruptibly(); 释放锁(必须做,要放在finally里面,防止异常跳出正常流程) 1r.unlock(); ReentrantReadWriteLock可重入读写锁(读写锁的实现)123ReentrantReadWriteLock lock = new ReentrantReadWriteLock();ReadLock r = lock.readLock();WriteLock w = lock.writeLock(); 两者都有 lock ,unlock 方法。写写,写读互斥;读读不互斥,实现并发读的高效线程安全代码。 4.容器类比较常用的:BlockingQueue 和 ConcurrentHashMap BlockingQueue阻塞队列。改类是 java.util.concurrent 包下重要类,通过对 Queue 的学习可以得知,这个 queue 是单向队列,可以在队列头添加元素和在队尾删除或取出元素。类似一个 管道 ,特别适合用于先进先出策略的一些应用场景。普通的 queue 接口主要实现由 PriorityQueue(优先队列)除了传统的 queue 功能(表格左边的两列)之外,还提供了阻塞接口 put 和 take,带超时功能的阻塞接口 offer 和 poll。put 会在队列满的时候阻塞,知道有空间时被唤醒;take 在队列空的时候阻塞,知道有东西拿的时候才被唤醒。用于生产者-消费者模型尤其好用。常见的阻塞队列有:1234ArrayListBlockingQueueLinkedListBlockingQueueDelayQueueSynchronousQueue ConcurrentHashMap高效的线程安全HashMap。 5.管理类管理类用于管理线程。本身不是多线程的,但是提供了一些机制来利用上述的工具做一些封装。ThreadPoolExecutor 和 JMX 框架下的系统级管理类 ThreadMXBean ThreadPoolExecutor1234567//可变大小的线程池,按照任务数来分配线程ExecutorService e = Executors.newCachedThrreadPool();//单线程池,相当于 FixedThread(1)ExecutorService e = Executors.newSingleThreadExecutor();//固定大小的线程池ExecutorService e = Executors.newFixedThreadPool(3);e.execute(new MyRunnableImpl()); 该类内部是通过 ThreaPoolExecutor 实现的,本质上都是 ThreadPoolExecutor 类的各种实现版本翻译:corePoolSize:池内线程初始值和最小值,就算是空闲状态,也会保持该数量线程。maximumPoolSize:线程最大值,线程的增长始终不会超过该值keepAliveTime:当线程池数高于 corePoolSize 时,经过多少时间多余的空闲池才会被回收,回收前处于 wait 状态 unit:时间单位,可以使用 TimeUnit 实例,如 TimeUnit.MILLISECONDSworkQueue:等待入任务(Runnable)的等待场所,该参数主要影响调度策略,如公平与否,是否会产生饿死(starving)threadFactory:线程工厂类,有默认的实现,如果有自定义的需要则需要自己实现 ThreadFactory 接口并作为参数传入。]]></content>
<categories>
<category>java基础知识</category>
</categories>
<tags>
<tag>java</tag>
<tag>多线程</tag>
</tags>
</entry>
<entry>
<title><![CDATA[必知必会Java面试题]]></title>
<url>%2Fblog%2F2019%2F01%2F11%2F%E9%9B%86%E5%90%88%E7%B1%BB%2F</url>
<content type="text"><![CDATA[集合类1.能否用任何类作为 Map 的 Key? 如果类重写了 equals()方法,也应该重写 hashCode()方法 类的所有实例需要遵循与 equals() 和 hashCode() 相关的规则 如果一个类灭有使用 equals(),不应该在 hashCode()中使用它 用户自定义 Key 类最佳实践是使之为不可变,这样 hashCode()值可以被缓存起来,拥有更好的性能 分析:如果有一个类 MyKey ,在 HashMap中使用它:12345678HashMap<MyKey,String> myHashMap = new HashMap<MyKey,String>();//传递给 MyKey 的 name 参数被用于 equals()和hashCode()中MyKey mykey = new MyKey("zxk"); //假设当前 hashCode = 111myHashMap.put(mykey,"value");//以下代码会改变 key 的 hashCode() 和 equals()值mykey.setName("Change"); //新的 hashCode = 222//西面会返回null,因为 HashMap 会尝试查找存储同样索引的 key,而 key已经被改变了,匹配失败System.out.print(myHashMap.get(new MyKey("zxk"))); 这就是为什么String经常会被作为 HashMap的 Key,因为 String 的设计不可变。 2.插入数据时,ArrayList、LinkedList、Vector谁的速度比较快? ArrayList、Vector 底层的实现都是数组,数组元素大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及到元素移动等内存操作,所以索引数据(查找数据)比插入数据更快 Vector 中方法由于加了 synchronized 修饰,因此 Vector 是线程安全容器,但性能上比 ArrayList 差些 LinkedList 底层的实现是双向链表,按序号索引数据需要进行向前或向后的遍历,但是插入数据时只需要记录当前项的前后项即可,所以 LinkedList 插入数据速度快,是三者中最快的,但是查找数据比较慢 3.多线程场景下如何使用 ArrayList?ArrayList 是线程不安全的,如果遇到多线程情况下,可以通过 Collections 的 synchronizedList 方法将其装换成线程安全的容器后在使用。123456List<String> synchronizedList = Collections.synchronizedList(list);synchronizedList.add("aaa");synchronizedList.add("bbb");for(int i = 0;i<synchronizedList.size();i++){ System.out.println(synchronizedList.get(i));} 4.ArrayList优缺点 优点: ArrayList 底层是以数组实现,是一种随机访问模式,ArrayList 实现了 RandomAccess 接口,因此查找的时候非常快速。 ArrayList 在顺序添加一个元素的时候非常方便。 缺点: 删除元素的时候,需要做一次元素复制的操作,如果复制到元素很多,那么就会比较耗费性能。 插入元素的时候,也需要做一次元素复制操作,缺点同上。 ArrayList 比较是否 顺序添加、随机访问的场景。 5.为什么 ArrayList 的 elementData 加上 transient 修饰 ArrayList中数组定义:1private transient Object[] elementData; ArrayList 的定义:1public class ArrayList<E> extends AbsratctList<E> implements List<E>,RandomAccess,Cloneable,java.io.Serializable 可以看到 ArrayList 实现了 Serializable 接口,这意味着 ArrayList 支持序列化。transient 的作用是说为了 elementData 数组被序列化,重写了 writeObject 实现:12345678910111213private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{ //Write out element count, and any hidden stuff int expectedModCount = modCount; s.defaultWriteObject(); //write out array length s.writeInt(elementData.length); //write out all elements in the proper order for(int i=0;i<size;i++) s.writeObject(elementData[i]); if(modCount != expectedModCount){ throw new ConcurrentModificationException(); }} 每次序列化时,先调用 defaultWriteObject()方法序列化 ArrayList 中的非 transient 元素,然后遍历 elementData ,只序列化已存入的元素,这样即加快了序列化的速度,又减小序列化之后的文件大小。 6.遍历一个List有哪些方式,每种方式实现原理是什么,Java中遍历List最佳实践是什么? for 循环遍历:基于计数器,在集合外部维护一个计数器,然后依次读取每一个位置的元素,当读取到最后一个元素后停止。 迭代器遍历:Iterator 是面向对象的一个设计模式,目的是屏蔽不同数据集合的特点,统一遍历集合的接口。Java 在 Collections 中支持了 Iterator 模式。 foreach 循环遍历:foreach 内部采用了 Iterator 的实现方式,使用时不需要显式声明 Iterator 或计数器。优点是代码简洁,不易出错;缺点是只能简单的遍历,不能在遍历过程中操作元素。例如删除、替换。 最佳实践: Java Collections 框架中提供了一个 RandomAccess 接口,用来标记 List 实现是否支持 Random Access。 如果一个数据集合实现了该接口,就意味着它支持 Random Access,按照位置读取元素的平均时间复杂度 O(1),如ArrayList。 如果没有实现该接口,表示不支持 Random Access ,如 LinkedList。推荐做法:支持 Random Access 的列表可以用 for 循环遍历,否则建议用 Iterator 或 foreach 遍历。 7.如何一边遍历一边移除 Collection 中的元素? 采用 Iterator.remove()方法。12345Iterator<Integer> it = list.iterator();while(it.hasNext()){ // do something it.remove();} 特别指出一种错误的方式:1234//这种错误会报 ConcurrentModificationException异常for(Integer i : list){ list.remove(i)} 因为在使用 foreach(for(Integer i : list)) 语句时,会自动生成一个 Iterator 来遍历该 list ,但同时该 list 正在被 Iterator.remove() 修改。 Java 一般不允许一个线程 在遍历 Collection 时另一个线程修改它。 封装类、String、String Pool、StringBuilder1.写出下面代码的运行结果1234int src = 65536;Integer dst = new Integer(65536);System.out.println( src == dst);System.out.println( dst.equals(src) ); 都是 true 。Integer 的 equals 实现。65536 装箱为 Integer 对象后,dst.equals() 方法比较的是 obj.intValue()。 123456public boolean equals(Object obj){ if (obj instanceof Integer){ return value == ((Integer) obj).intValue(); } return false;} 2.写出下面代码的运行结果12345678public class Example{ private static void sayHello(){ System.out.println("hello!"); } public static void main(String[] args){ ((Example)null).sayHello(); }} 最终输出“hello!”。null 作为非基本类型,可以做类型转换,转换后调用静态方法输出字符串。基本类型,比如 int ,类型转换时会报空指针异常,比如 int a= (Integer)null;原因是转换过程中会调用 intValue(),因此会报出异常。 3.String 类能被继承么不能。因为 String 类定义为 final class ,被 final 修饰的类不能被继承。1public final class String String pool:这是方法(method)区域里一个特殊的存储区域,创建一个 String 时,如果已经在 String pool 中存在,那么会返回已存在的 String 引用。 允许 String 缓存 hashCode:String 定义中,有 hash 成员变量 private int hash (默认为0)对 hashCode 进行缓存。 安全性:保证不会被恶意篡改。 4.String s3 = new String(“a”)会创建几个 String 对象1个或 2 个。String pool 机制。如果 Spring pool在执行语句之前 没有 “a” 对象,那么会创建 2 个String,如果已经存在,“a”会从 String pool 中直接返回对象 5.String、StringBuffer、StringBuilder区别 String 是不可变得,StringBuffer、StringBuilder 是可变的 String、StringBuffer 是线程安全的,StringBuilder 不是线程安全的 StringBuilder 比 StringBuffer 有速度优势。但是应用在要求线程安全情况下,需要使用 StringBuffer 6.为什么针对安全保密高的信息,char[]比String更好?String 是不可变的,一旦创建,就不能再改变,直到垃圾收集器将它回收。而字符数组中的元素是可以更改的,这就意味着可以在使用完后将其更改,而且不会保留原始数据, 3.常见问题1.Object类包含哪些方法?在 java.lang 包中,Object 类位于类层次结构的顶端。每个类都是 Object 类直接或间接的子类。以下是从 Object 类中继承的方法: protected Object clone() throws CloneNotSupportedException 创建并返回此对象的副本 public boolean equals(Object o) 判断另一个对象是否与此对象相等 protected void finalize() throws Throwable 当垃圾回收机制确定该对象不再被调用时,垃圾回收器会调用此方法 public final Class getClass() 返回此对象的运行时类 public int hashCode() 返回此对象的散列码值 public String toString() 返回此对象的字符串表示形式 2.final、finally、finalize 三者不同 final 关键字用于多个语境下定义只能分配一次的实体 finally 代码块用于执行重要代码(如关闭连接、流等)的代码块,无论是否异常处理,finally 代码块总会被执行,finally 代码块紧随着 try 代码块或者 catch 代码块 finalize 这是在删除或销毁对象之前垃圾回收器总会调用的方法,该方法是的垃圾回收机制能够执行清理活动 3.菱形继承问题(钻石继承问题)jdk1.8之前不允许出现多继承(extends),但是1.8之后,由于接口添加定义方法(default method)可以实现接口多继承。 4.如何使一个类不可改变 将类声明为 final,使其无法被继承。 所有域都用 private 修饰,不允许直接访问 不提供变量的 setter 方法 所有可变域都用 final 修饰,使它的值只能分配一次 通过构造函数执行深克隆初始化所有域 对 getter 方法获取的对象执行克隆返回副本,而不是返回实际的对象引用 5.单例模式单例模式指的一个类仅允许创建其自身的一个实例,并提供对该实例的访问权限。它包含静态变量,可以容纳其自身的唯一和私有实例。一般应用场景:用户希望类的实例被约束为一个对象。 饿汉模式(静态常量)[可用] 123456789//优点:写法比较简单,就是在类加载的时候就完成实例化,避免了线程同步问题//缺点:类装载的时候就完成了实例化,没有达到懒加载(Lazy Loading)效果,如果从始至今都没用到郭这个实例,就会出现内存浪费public class Singleton{ private final static Singleton INSTANCE = new Singleton(); private Singleton(){} public static Singleton getInstance(){ return INSTANCE; }} 饿汉模式(静态代码块)[可用] 1234567891011//该方式和上述方式类似,只不过将类的实例化放到了静态代码块中,也是在类加载的时候,执行静态代码块,初始化实例,优缺点和上述一样。public class Singleton{ private static Singleton instance; static { instance = new Singleton(); } private Singleton(){} public static Singleton getInstance(){ return instance; }} 懒汉模式(线程不安全)[不可用] 1234567891011//这种写法起到了懒加载(Lazy Loading)效果,但是只能在单线程下使用。如果多线程下,一个线程进入了 if(singleton == null)判断语句块,还未来得及往下执行,另一个线程也进入了这个判断语句,便会产生多个实例,所以在多线程下不可以用public class Singleton{ private static Singleton singleton; private Singleton(){} public static Singleton getInstance(){ if (singleton == null){ singleton = new Singleton(); } return singleton; }} 懒汉式(线程安全,同步方法)[不推荐使用] 123456789101112//加了同步锁(synchronized),解决第三种方式的线程不安全。//缺点:效率太低,每个线程在获得类实例的时候,需要执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,方法进行同步效率太低。public class Singleton{ private static Singleton singleton; private Singleton(){} public static synchronized Singleton getInstance(){ if (singleton == null){ singleton = new Singleton(); } return singleton; }} 懒汉式(线程安全,同步代码块)[不可用] 12345678910111213//由于第四种实现方式同步效率太低,所以摒弃了同步方法,改为同步产生实例化的代码块。但是这种同步不能起到线程同步的作用。跟第三种实现方式情形一致。如果一个线程进入了 if(singleton == null)判断语句块,另一个语句也进入了,也会产生多个实例public class Singleton{ private static Singleton singleton; private Singleton{} public static Singleton getInstance(){ if (singleton == null){ synchronized (Singleton.class){ singleton = new Singleton(); } } return singleton; }} 双重检查[推荐用] 12345678910111213141516//Double-Check,进行两次 if(singleton == null)检查,这样可以保证线程安全,实例化代码只进行一次,后面咋次访问时,依旧判断 if (singleton == null),直接return 实例化对象//优点:线程安全,延迟加载,效率更高。public class Singleton{ private static volatile Singleton singleton; private Singleton(){} public static Singleton getInstance(){ if (singleton == null){ synchronized(Singleton.class){ if(singleton == null){ singleton = new Singleton(); } } } return singleton; }} 静态内部类[推荐用] 123456789101112//这种方式跟饿汉方式采用的机制类似,又有区别。两者都是采用类加载的机制来保证实例化时只有一个线程,不同的地方是饿汉方式只要 Singleton类被加载就会实例化,没有懒加载(Lazy Loading)的作用,而静态内部类方式在Singleton类被加载时并不会直接实例化,而是调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton实例化//类的静态属性只有第一次加载类的时候才会初始化,这里,JVM保证了线程安全,在类进行初始胡时,别的线程是无法进入的//优点:避免了线程不安全,延迟加载,效率高。public class Singleton{ private Singleton(){} private static class SingletonInstance{ private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance(){ return SingletonInstance.INSTANCE; }} 枚举[推荐用] 1234567//借助JDK1.5添加的枚举来实现单例,不仅避免了多线程同步问题,而且还能防止反序列化重新创建的对象public enum Singleton{ INSTANCE; public Singleton getInstance(){ return INSTANCE; }} 点击=>查看枚举实现单例 6.什么是依赖注入?Dependency Injection 依赖注入和 Inversion of Controller 控制反转是同一个概念的不同角度描述。 具体含义:当某个角色(可能是一个 Java 实例,调用者)需要另一个角色(另一个 Java 实例,被调用者)的协助时,传统的程序设计中,通常是由调用者来创建被调用者的实例。在Spring里,创建被调用者的工作不再由调用者来完成,因此成为控制反转,创建被调用者 实例工作 通常由Spring容器来完成,然后注入调用者,因此成为依赖注入。依赖注入IOC和DI]]></content>
<categories>
<category>java面试题</category>
</categories>
<tags>
<tag>java</tag>
<tag>面试</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Java集合]]></title>
<url>%2Fblog%2F2019%2F01%2F10%2F1.%20%E7%AE%80%E4%BB%8B%EF%BC%9A%2F</url>
<content type="text"><![CDATA[简介:JDK1.2引入了Java集合框架,包含一组数据结构。与数组不同,这些数据结构的存储空间会随着元素的添加而动态增加。 所有这些数据结构在 java.util 包里,包含了 Collection 、List、Set、Map、SortedMap 接口。这些接口的实现类有 LinkedList、TreeSet、ArrayList、HashMap等。除了这些数据结构,java.util 包还提供了 Date、GregorianCalender、StringTokenizer、Random这样的工具类。 分类:按照接口、实现、算法三个方面对集合框架中的数据结构进行分类: 接口: Collection 、 List 、 Map 组成了集合框架中所有具体实现类的接口,它们定义了子类必须实现的方法。 实现: 所有实现上述三个接口的类,都被称为集合框架,实际上就是数据结构。比如 LinkedList 、 TreeSet等。 算法: 集合框架提供了很多可以直接调用的算法,比如求最小值和最大值、排序、填充等。 优缺点3个优点: 减少了工作量的同时也增加了软件的可用性: 不需要每个程序猿手动实现排序、查找等。 执行速度更快更持久: 集合框架的底层数据结构分为两类:基于节点和基于数组的,前者在频繁添加时效率更高,后者是在频繁读取时速度更快。一些数据结构是synchronized 线程安全的,但是会影响速度,另一些存在线程不安全。 互操作与转换: 由于实现了 Collection 接口,数据结构之间是可以相互转换的。可以 clone ,可以把现有的结构转成 synchronized 版本,还可以将基于链表的数据结构转为基于数组的机构。 2个缺点: 需要注意类型转换: 在集合框架类之间进行的转换时需要小心,特别是要考虑泛型类型的兼容性 运行时类检查: 在集合框架运行时会抛出异常,需要编程时多注意。 继承体系java.util 数据结构继承体系分为两大类,一类是实现了 Collection 接口,一类实现了 Map 接口。 集合框架核心接口及实现类: Collection: 根接口,大部分数据结构都实现了Collection接口的方法 Set::实现 Set 接口的数据结构不允许有重复的元素,例如 HashSet 、LinkedHashSet SortedSet: 实现 SortedSet 接口的数据默认按 升序打印元素,例如 TreeSet List:实现 List 接口的数据允许存在重复元素,并且可以通过 index 访问元素,例如 LinkedList、ArrayList、Vector Map:实现 Map 接口的数据结构存储键值对,不允许重复的 key,例如 HashMap、LinkedHashMap、Hashtable SortedMap:继承了 Map 接口,存储键值对,不允许重复的 key,默认按 key 升序打印元素,例如 TreeMap SortedSet 与 SortedMap 默认的排序是 自然序,可以通过 Comparator 或 Comparable 接口实现自定义排序。 在接口 与具体的实现类之间还存在一些抽象类,如下图 这些抽象类为集合增加了很多功能: HashSet: 实现 Set 接口,不允许重复的元素,底层数据结构 hash table LinkedHashSet: 实现 Set 接口,不允许重复的元素,底层数据结构 hash table 和 双链表 TreeSet:实现 NavigableSet 接口,不允许重复的元素,底层数据结构 红黑树 ArrayList:实现 List 接口,允许重复元素,底层数据结构 可变数组 LinkedList:实现 List 接口,允许重复元素,底层数据结构 双链表 Vector:实现 List 接口,允许重复元素,添加了synchronized,底层数据结构 可变数组 HashMap:实现 Map 接口,不允许重复的 Key,底层数据结构 hash table LinkedHashMap:实现 Map 接口,不允许重复的 Key , 底层数据结构 hash table 和双链表 HashTable:实现 Map 接口,不允许重复的 Key,底层数据结构 hash table TreeMap:实现 Map 接口,不允许重复的 Key,底层数据结构 红黑树 需要注意的是 HashTable 和 HashMap 都是实现 Map 接口,HashMap 是 非 Synchronized 的,并且可以接受 null(HashMap 可以接受 null 为 key 和 null 为 value ,而HashTable 不可以。)HashTable 是 Synchronized 的。HashMap的迭代器(Iterator)是fail-fast 迭代器,而 HashTable 的 enumerator 迭代器不是fail-fast*。 fail-fast 快速失败机制:是 java 集合中的一种错误机制。抛出 异常java.util.ConcurrentModificationException 为什么用HashMap? HashMap 是一个散列桶 (数组 和 链表),它存储的内容是键值对 key - value 映射 HashMap 采用了数组和链表的数据结构,能在查询和修改方便继承了数组的线性查找和链表的寻址修改 HashMap 是非 synchronized ,所以 HashMap 很快 HashMap 可以接受 null 作为键(key)和值(value),而HashTable不能 HashMap的工作原理是什么?HashMap 是基于 hashing 的原理 我们使用 put(key,value)存储对象到 HashMap 中,使用 get(key) 从 HashMap 中获取对象。当我们给 put( )方法传递键和值时,我们先对键调用 hashCode( ) 方法,计算并返回的 hashCode 适用于找到 Map 数组的 bucket 位置来存储 Node 对象。HashMap 是在 bucket 中存储对象和值对象,作为 Map.Node 1234567Node[] table = new Node[16];//散列桶初始化,tableclass Node{ hash; //hash值 key; //键 value; //值 node next; //用于指向链表的下一层(产生冲突,用拉链法)} put具体过程(JDK1.8) 对 Key 求 Hash 值,然后在计算下标 如果没有碰撞,直接放入桶中(碰撞的意思是 计算得到的 Hash 值相同,需要放到同一个 bucket 中) 如果碰撞了,以链表的方式链接到后面 如果链表长度超过了阀值( TREEIFY THRESHOLD == 8),就把链表转为红黑树,链表长度低于6,就把红黑树转回为链表 如果节点已经存在就覆盖(替换旧值) 如果桶满了(容量16*加载因子0.75),就需要 resize (扩容2倍后重排) get具体过程(JDK1.8) 当我们调用 get( )方法, HashMap 会使用键对象的 hashcode 找到 bucket 位置,找到 bucket 位置后,会调用 keys.equals( ) 方法找到链表中正确的节点,最终找到值对象。 减少碰撞扰动函数可以减少碰撞原理:如果两个不相等的对象返回不同的 hashcode 的话,那么碰撞的几率就会小些。这意味着 存链表结构减小,这样取值的时候不会频繁的调用 equal 方法,从而提高 HashMap 性能。(扰动 即 Hash 方法内容的算法实现,目的是让不同的对象返回不用的 hashcode)。 使用不可变的、声明作 final 对象,并且采用何式的 equals() 和 hashCode()方法不可变性使得能够缓存不同键的 hashcode ,这将提高整个获取对象的速度,使用 String 、Integer 这样的wrapper 类(封装类)作为键。 为什么 String、Integer 这样的封装类(wrapper类)适合作为键因为 String 是 final ,而且已经重写了 equals() 和 hashCode() 方法了。不可变性是必要的,因为为了要集散 hashCode(),就要防止键值改变。 HashMap 中 hash 函数怎么实现?由于 HashMap 的数据结构是数组+量表的结构,所以我们希望这个 HashMap 里的元素位置尽量分布均匀些,尽量使每个位置上的元素只有一个,这样一来不需要再去遍历链表。123456789101112//JDK 1.8static final int hash(Object key){ if (key == null){ return 0; } int h; h = key.hashCode();//返回散列值就是 hashcode // ^ : 按位异或 // >>> : 无符号右移,忽略符号位,空位都以0补齐 //其中 n是数组的长度,即 Map的数组部分初始化长度 return (n-1)&(h^(h>>> 16));} 高 16 bit 不变,低16 bit 和高 16 bit 做了一个 异或(得到 hashcode 转为32位二进制,前16位和后16位做了一个异或) (n-1)& hash ==> 得到下标 拉链法导致的链表过深,为什么不用二叉查找树代替,而选用红黑树,为什么不一直使用红黑树?之所以选择红黑树就是为了解决二叉查找树的缺陷:二叉查找树在特殊情况会变成一条线性结构,这样一来和原来使用链表结构一样了,造成层次很深的问题。红黑树再插入新数据后需要通过左旋、右旋、变色等一些列操作来保持树的平衡(树的平衡对查找树非常重要)。引入红黑树就是为了查找数据块,解决链表查询深度的问题。但是由于红黑树属于平衡二叉树(AVL树:一棵空树或它的左右两个子树的高度差的绝对值不超过1),为了保持“平衡”需要付出代价,但是该代价所损耗的比起遍历线性链表要少。所以长度大于8的时候会使用红黑树,如果链表很短的时候不需要引入红黑树。 HashMap在jdk1.8之后引入了红黑树的概念,表示若桶中链表元素超过8时,会自动转化成红黑树;若桶中元素小于等于6时,树结构还原成链表形式。 原因:红黑树的平均查找长度是log(n),长度为8时,查找长度log(8) =3,链表的平均长度为n/2,当长度为8时,平均查找长度为8/2=4,这才有转变树的必要。但是当长度为6的时候,两者效率一样,但是转化为树需要耗时,所以不必要。 选择6和8的原因:中间有个差值7可以防止链表和树之间频繁的转换。假设一下,如果设计成链表个数超过8则链表转换成树结构,链表个数小于8则树结构转换成链表,如果一个HashMap不停的插入、删除元素,链表个数在8左右徘徊,就会频繁的发生树转链表、链表转树,效率会很低。 红黑树 每个节点非红即黑 根节点一定是黑的 如果节点是红色的,则它的子节点必须是黑色的(反之则不一定) 每个叶子结点都是黑色的空节点(NIL节点) 从根节点到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点(相同的黑色高度) 如果 HashMap 的大小超过了负载因子(load factor)定义的容量怎么办?HashMap 默认的负载因子大小为 0.75。也就是说,当一个 Map 填满了 75% 的 bucket 的时候,和其他集合类一样(如 ArrayList 等),将会创建一个是原来 HashMap 大小的两倍的 bucket 数组来重新调整 Map 大小,并将原来的对象放入新的 bucket 数组,这个过程叫做 rehashing。 因为调用 hash 方法找到新的 bucket 位置。这个值只可能在两个地方,一个是原下标的位置,另一个是在下标为<原下标+原容量>的位置。 重新调整 HashMap 大小存在什么问题?重新调整 HashMap 大小的时候,确实存在条件竞争。 因为如果两个线程都发现 HashMap 需要调整大小了,它们会同时试着调整大小。在调整大小的过程中,存储在链表中的元素的次序会反过来。因为移动到新的 bucket 位置的时候,HashMap 并不会将元素放在链表的尾部,而是放在头部,这是避免了尾部遍历(tail traversing)。如果条件竞争发生了,name就死循环了,所以在多线程环境下,不使用 HashMap。 为什么多线程会导致死循环? HashMap 的容量是有限的。当经过多次元素插入,使得 HashMap 达到一定饱和度时,Key 映射位置发生冲突的几率会逐步提高。这时候,HashMap 需要扩展它的长度,进行 Resize。 HashTable 数组+链表方式存储 默认容量: 11(质数为宜) put操作:首先进行索引计算(key.hashCode() & 0x7FFFFFFF)% table.length;若在链表中找到了,则替换旧值,若未找到则继续。当总元素个数超过 容量 * 加载因子 时,扩容为原来的两倍,并重新散列;将新元素加到链表头部 对修改 HashTable 内部共享数据的方法添加了 synchronized,保证线程安全。 HashMap 与 HashTabled区别 默认容量不同,扩容不同 线程安全性:HashTable 安全 效率不同:HashTable 要慢,因为有锁 可以使用 CurrentHashMap 来代替 HashTable? HashTable 是 synchronized 的,但是 ConcurrentHashMap 同步性能更好,因为它仅根据同步级别对 map的一部分进行加锁 CurrentHashMap 当然可以替代 HashTable ,但是 HashTable 提供更强的线程安全 都可以用在多线程环境,但是当 HashTable 大小增加到一定的时候,性能会急剧下降,因为迭代时需要被锁定很长时间。由于ConcurrentHashMap 引入了分割(segmentation),不论它变得多么大,仅仅需要锁定 Map 的某个部分,其他线程不需要迭代完成才能访问 Map。 简而言之,CurrentHashMap 只是 锁定 Map 的某个部分,而 HashTable 锁定了整个 Map。]]></content>
<categories>
<category>java基础知识</category>
</categories>
<tags>
<tag>java</tag>
<tag>集合</tag>
</tags>
</entry>
<entry>
<title><![CDATA[JVM基础知识]]></title>
<url>%2Fblog%2F2019%2F01%2F09%2FJVM%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%2F</url>
<content type="text"><![CDATA[1. 运行时数据区域:线程私有: 程序计数器 虚拟机栈 本地方法栈 线程共享: 堆 方法区 直接内存 2.1 程序计数器程序计数器是一块较小的内存空间,可以看做是当前线程所执行的字节码的行号指示器。字节码解释器工作是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等功能都需要依赖这个计数器。另外为了线程切换后能恢复到正确执行的位置,每条线程都需要一个独立的程序计数器,各线程之间计数器互相不影响,独立存储,称这类内存区域为“线程私有”的内存。 程序计数器作用 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。 在多线程的情况下,程序计数器用于记录当前线程执行位置,从而当前线程被切回来的时候能够知道该线程上次运行到哪儿了。注意程序计数器是唯一不会出现OutOfMemoryError的内存区域,它的生命周期随着线程的创建而创建,随着线程的结束而死亡。 2.2 Java虚拟机栈与程序计数器一样,Java虚拟机栈也是线程私有的,它的生命周期和线程相同,描述的是Java方法执行的内存模型。 Java内存可以粗糙的区分为堆内存(Heap)和栈内存(Stack)。其中栈就是现在说的虚拟机栈,或者说是虚拟机栈中局部变量表部分。(实际上,Java虚拟机栈是由一个个栈帧组成的,而每个栈帧中都有局部变量表、操作数栈、动态链接、方法出口信息)。 局部变量表主要存放了编译器可知对的各种数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他榆于此对象相关的位置)。 Java虚拟机栈会出现两种异常:StackOverFlowError 和 OutOfMemoryError。 StackOverFolwError:若Java虚拟机栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前Java虚拟机栈的最大深度的时候,就会抛出 StackOverFlowError 异常。 OutOfMemoryError:若Java虚拟机栈的内存大小允许动态扩展,且当前线程请求栈时内存用完了,无法再动态扩展,此时会抛出 OutOfMemoryError 异常。 Java虚拟机栈也是线程私有的,每个线程都会有各自的Java虚拟机栈,而且随着线程的创建而创建,随着线程的结束而死亡。 2.3 本地方法栈和虚拟机栈发挥的作用非常类似,区别是:虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native服务,在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。 本地方法被执行时候,在本地方法栈中会创建一个栈帧,用于存放该本地方法的局部变量表、操作数栈、动态链接、出口信息。 方法执行完后响应的栈帧会出栈并释放内存空间,也会出现 StackOverFlowError 和 OutOfMemoryError 两种异常。 2.4 堆Java虚拟机所管理的内存中最大一块,Java堆是所有线程共享的一块内存区域,在虚拟机启东时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。 Java堆是垃圾收集器管理的主要区域,因此也被称作 GC 堆( Garbage Collected Heap )。从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以Java堆还可以细分为:新生代和老年代;再细致一点:Eden空间、From Survivor、To Survivor空间等。进一步的划分目的是为了更好的回收内存,或者更快的分配内存。 在JDK1.8中移除了整个永久代,取而代之的是一个叫元空间(Metaspace)的区域(永久代使用的是JVM的堆内存空间,而元空间使用的是物理内存,直接受到本机的物理内存限制)。 Java8内存模型——永久代(PermGen)和元空间(Metaspace) 2.5 方法区方法区和 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆一个逻辑部分,但它却又一个别名叫做 Non-Heap(非堆),目的应该是与 Java 堆区分开来。 HotSpot 虚拟机中方法区也常被称为“永久代”,本质上两者并不等价,仅仅因为 HotSpot 虚拟机设计团队用永久代来实现方法区而已,这样 HotSpot 虚拟机的垃圾收集器就可以像管理Java堆一样管理这部分的内存,但是这样容易遇到内存溢出问题。相对而言,垃圾收集行为在这个区域比较少出现,当并非数据进入了方法区就是“永久存在”的。 2.6 运行时常量池运行时常量池是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有常量池信息(用于存放编译期间产生的各种字面量和符号引用)。 既然运行时常量池时方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存时会抛出 OutOfMemoryError 异常。 JDK1.7及之后版本的 JVM 已经将运行时常量池从方法区中移了出来,在 Java 堆(Heap)中开辟了一块区域存放运行时常量池。Java中集中常量池的区分 2.7 直接内存直接内存并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,但是这部分的内存也被频繁使用,而且也可能导致 OutOfMemoryError 异常。 JDK1.4中新加入的 NIO(New Input/Output) 类,引入了一种基于通信(Channel)与缓存区(Buffer)的 I/O 方式,它可以直接使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作,这样可以在一些场景中显著提高性能,避免了 Java 堆和 Native 堆之间来回复制数据。 本机直接内存的分配不会受到Java堆的限制,但是,既然是内存就会受到本机总内存大小以及处理器寻址空间的限制。 3.java 创建对象过程: 1.类加载检查 虚拟机遇到new指令,首先检查这个指令的参数能否在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有,那必须执行相应的类加载过程。 2.分配内存 在类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需要的内存大小在类加载完后可以确定。为对象分配空间的认为等同于把一块确定大小的内存从Java堆中划分出来,分配的方式有 “碰撞指针”和“空闲列表”两种。选择哪种分配方式由Java堆是否规整决定,Java堆是否规整由所采用的垃圾收集器是否带有压缩整理功能决定。 内存分配的两种方式 内存分配并发问题 在创建对象的时候需要保证线程安全,一般虚拟机采用以下两种方式:1.CAS+失败重试: CAS是乐观锁的一种实现方式。所谓乐观锁就是每次不加锁而是假设没有冲突去完成某项操作,如果因为冲突失败就重试,知道成功为止,虚拟机采用CAS配上失败重试的方式是为了保证操作的原子性。 2.TLAB: 为每一个线程预先在Eden区分配一块内存。JVM在给线程中的对象分配内存时,首先在TLAB分配,当对象大于TLAB中的剩余内存或TLAB的内存已用尽时,再采用上述的CAS+失败重试进行内存分配。 3.初始化零值 内存分配完成后,虚拟机需要将分配的内存空间都初始化零值(不包括对象头)。这一步操作保证了对象的实例字段在Java代码中可以不赋初始值就可以直接使用,程序访问到这些字段的数据类型所对应的零值。 4.设置对象头 初始化零值完成后,虚拟机需要对对象进行必要的设置,例如这个对象是哪个类的实例,如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。这些信息都存放在对象头中。另外根据虚拟机当前运行状态的不同,如何启用偏向锁等,是否启用偏向锁等,对象头会有不同的设置方式。 5.执行Init方法 在上述工作完成后,从虚拟机的视角看,一个新的对象已经产生了,但从Java程序的视角看,对象创建才刚刚开始,Init方法还没有执行,所有的字段都还是零。所以一般来说,执行new指令之后会接着执行Init方法,把对象按照程序员的意愿进行初始化。 3.1 对象的内存布局在 HotSpot 虚拟机中,对象在内存中的布局可以分为3块区域:对象头、实例数据和对齐填充。 HotSpot 虚拟机的对象头包括两部分信息: 用于存储对象自身运行时数据(哈希码、GC分代年龄、锁状态标志等等) 类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。 实例数据部分是对象真正存储的有效信息,也是在程序中所定义的各种类型的字段内容。 对齐填充部分不是必然存在的,也没有特别的含义,仅仅是占位作用。因为Hotspot虚拟机的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说就是对象的大小必须是8字节的整数倍。而对象头正好是8字节的倍数(1倍或2倍),因此对象实例数据部分没有对齐时,需要通过对齐填充来补全。 3.2 对象的访问定位建立对象是为了使用对象,Java程序通过栈上的 reference 数据来操作堆上的具体对象。对象的访问方式有虚拟机实现而定,目前主流的访问方式有句柄 和 指针。 句柄:如果使用句柄的话,那么 Java 堆中将会划分出一块内存来作为句柄池,reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息。 直接指针:如果使用直接指针访问,那么 Java 堆对象的布局中就必须要考虑如何防止访问类数据的相关信息,而reference 中存储的直接就是对象的地址。 这两种对象访问方式各有优势。使用句柄来访问的最大好处是 reference 中存的是稳定的句柄地址,在对象被移动时只改变句柄中的实例数据指针,而 reference 本身不需要修改。使用指针访问最大的好处就是速度快,节省了一次指针定位的时间开销。 4. String 类和常量池1. String 对象的两种创建方式: 123String str1 = "abcd";String str2 = new String("abcd");System.out.print(str1==str2); // false 这两种创建方式有差别:第一种 str1 是从常量池中拿对象,第二种 str2 是直接在堆内存空间创建一个新的对象 记住: 只要 new 方法,就需要创建新的对象。 2. String 类型的常量池比较特殊。 直接使用双引号声明出来的 String 对象会直接存储在常量池中。 如果不适用双引号声明的 String 对象,可以使用 String 提供的 intern 方法。String.intern( )是一个 Native 方法,它的作用是:如果运行时常量池已经包含了一个等于此 String 对象内容的字符串,则返回常量池中该字符串的引用;如果没有,则在常量池中创建与此 String 内容相同的字符串,并返回常量池中创建的字符串的引用。 123456String s1 = new String("计算机");String s2 = s1.intern();String s3 = "计算机";System.out.print(s2);//计算机System.out.print(s1 == s2);//false,因为s1是一个堆内存的String对象,s2是常量池中的String对象System.out.print(s3 == s2);//true,因为两个都是常量池中的String对象 3. String 字符串拼接123456789String str1 = "str";String str2 = "ing";String str3 = "str" + "ing"; //常量池中的对象String str4 = str1 + str2; //在堆上创建的新的对象String str5 = "string";//常量池中的对象System.out.print(str3 == str4); //falseSystem.out.print(str3 == str5); //trueSystem.out.print(str4 == str5);//false 尽量避免对个字符串拼接,因为这样会重复创建对象。如果需要改变字符串的话,可以使用 StringBuilder (线程不安全)或者 StringBuffer(线程安全)。 1String s1 = new String("abc"); 上述代码创建了两个对象。 1234String s1 = new String("abc");//堆内存的地址值String s2 = "abc";System.out.print(s1 == s2);//false,以为一个是堆内存,一个是常量池的内存System.out.print(s1.equals(s2));//true 先有字符串 “abc” 放入常量池,然后 new了一份字符串 “abc” 放入 Java 堆(字符串常量 “abc” 在编译期间已经确定放入常量池,而 Java 堆上的 “abc” 是在运行初始化阶段确定),然后 Java 栈中的 s1指向 Java 堆上的 “abc”。 4.1 8种基本类型的包装和常量池 java 基本类型的包装类大部分都实现了常量池技术,即 Byte 、Short 、Integer 、Long 、Character 、Boolean ;这六种包装类默认创建了数值 [-128,127]的相应的缓存数据,但是超出此范围仍然回去创建新的对象。 两种浮点数类型的包装类 Float、Double 并没有是实现常量池技术。 PS: 所以包装类在比较是否相等时候,应该使用equals,而不是==。123456789Integer i1 = 33;Integer i2 = 33;System.out.println(i1 == i2);// 输出trueInteger i11 = 333;Integer i22 = 333;System.out.println(i11 == i22);// 输出falseDouble i3 = 1.2;Double i4 = 1.2;System.out.println(i3 == i4);// 输出false Integer 缓存源代码:12345678/** *此方法将始终缓存-128到127(包括端点)范围内的值,并可以缓存此范围之外的其他值。 */public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i);} 应用场景: integer i1 = 40; Java在编译的时候会直接将代码封装成 Integer i1 = Integer.valueOf(40);从而使用常量池中的对象。 Integer i1 = new Integer(40);这种情况下会创建新的对象 123Integer i1 = 40;Integer i2 = new Integer(40);System.out.println(i1==i2); //输出false Integer比较(==) 1234567891011121314151617181920Integer i1 = 40;Integer i2 = 40;Integer i3 = 0;Integer i4 = new Integer(40);Integer i5 = new Integer(40);Integer i6 = new Integer(0); System.out.println("i1=i2 " + (i1 == i2));System.out.println("i1=i2+i3 " + (i1 == i2 + i3));System.out.println("i1=i4 " + (i1 == i4));System.out.println("i4=i5 " + (i4 == i5));System.out.println("i4=i5+i6 " + (i4 == i5 + i6)); System.out.println("40=i5+i6 " + (40 == i5 + i6));//结果i1=i2 truei1=i2+i3 truei1=i4 falsei4=i5 falsei4=i5+i6 true40=i5+i6 true 语句 i4 == i5 + i6; 因为 “+”这个操作符不适用于Integer对象,首先将i5和i6自动拆箱操作,进行数值相加,即 i4 = =40 ,然后 Integer对象无法与数值进行直接比较,所以将 i4 自动拆箱转为 int值 40 ,最终语句变成: 40 == 40进行数值比较。]]></content>
<categories>
<category>java基础知识</category>
</categories>
<tags>
<tag>java</tag>
<tag>jvm</tag>
</tags>
</entry>
<entry>
<title><![CDATA[java api调用]]></title>
<url>%2Fblog%2F2018%2F04%2F12%2Fjava_api%2F</url>
<content type="text"><![CDATA[Java Api调用 java api调用代码如下: 首先URL restURL = new URL(url);这其中的url就是需要调的目标接口地址,URL类是java.net.*下的类,这个不陌生。 setRequestMethod(“POST”);请求方式是有两个值进行选择,一个是GET,一个是POST,选择对应的请求方式。 setDoOutput(true);setDoInput(true);setDoInput() : // 设置是否向httpUrlConnection输出,因为这个是post请求,参数要放在http正文内,因此需要设为true, 默认是false;setDoOutput(): // 设置是否从httpUrlConnection读入,默认情况下是true; setAllowUserInteraction();allowUserInteraction 如果为 true,则在允许用户交互(例如弹出一个验证对话框)的上下文中对此 URL 进行检查。 下面代码的query是以 属性=值 传输的,若是多个则是 属性=值&属性=值 这种形式传递的,传递给服务器,让服务器自己去处理。如何去生成这种形式的呢?最简单最快的方式在这里 Java将Map拼接成“参数=值&参数=值” close();创建流进行写入或读取返回值,创建用完后记得关闭流。 有事需要生成时间戳,可以参考java时间类各种方法及用法,这个里面介绍了各种时间方法获取想要的时间戳 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364package com.c;import java.io.BufferedReader;import java.io.InputStreamReader;import java.io.PrintStream;import java.net.HttpURLConnection;import java.net.URL;import java.util.HashMap;import java.util.Map;public class RestUtil { public String load(String url,String query) throws Exception { URL restURL = new URL(url); /* * 此处的urlConnection对象实际上是根据URL的请求协议(此处是http)生成的URLConnection类 的子类HttpURLConnection */ HttpURLConnection conn = (HttpURLConnection) restURL.openConnection(); //请求方式 conn.setRequestMethod("POST"); //设置是否从httpUrlConnection读入,默认情况下是true; httpUrlConnection.setDoInput(true); conn.setDoOutput(true); //allowUserInteraction 如果为 true,则在允许用户交互(例如弹出一个验证对话框)的上下文中对此 URL 进行检查。 conn.setAllowUserInteraction(false); PrintStream ps = new PrintStream(conn.getOutputStream()); ps.print(query); ps.close(); BufferedReader bReader = new BufferedReader(new InputStreamReader(conn.getInputStream())); String line,resultStr=""; while(null != (line=bReader.readLine())) { resultStr +=line; } System.out.println("3412412---"+resultStr); bReader.close(); return resultStr; } public static void main(String []args) {try { RestUtil restUtil = new RestUtil(); String resultString = restUtil.load( "http://192.168.10.89:8080/eoffice-restful/resources/sys/oaholiday", "floor=first&year=2017&month=9&isLeader=N"); } catch (Exception e) { // TODO: handle exception System.out.print(e.getMessage()); } }}]]></content>
<categories>
<category>java实例代码</category>
</categories>
<tags>
<tag>java</tag>
</tags>
</entry>
<entry>
<title><![CDATA[垃圾回收机制]]></title>
<url>%2Fblog%2F2018%2F04%2F12%2F1.%E4%BB%80%E4%B9%88%E6%97%B6%E5%80%99%2F</url>
<content type="text"><![CDATA[1.什么时候堆是JVM所管理的最大的一块内存空间,主要用于存放各种类的实例对象。JVM中的堆一般分为三个部分:新生代、老年代、永久代。 堆的内存模型 JVM中每次只会使用Eden区和其中一块survivor区域来为对象服务,所以无论什么时候总有一块survivor区域空闲着。因此新生代实际可用内存空间为90%的新生代空间。 新生代 主要用来存放新生的对象,一般占据堆得1/3空间。由于频繁创建对象,索引新生代会频繁发出minor gc进行垃圾回收。新生代又被划为三个区:Eden区、from survivor、to survivor。Eden区:java新生对象的出生地(如果新创建的对象占用内存很大,则直接分配到来年代)。当Eden区内存不够的时候会触发minor gc,对新生代代区域进行一次垃圾回收。 Minor GC的过程:采用复制算法,首先把Eden区和from survivor区域中存货的对象复制到to survivor区域,然后再把Eden区和from survivor区域清除。to survivor区域将这些存活的对象年龄+1(如果to survivor不够位置了就放到老年区)。以后对象在survivor区域中每熬过一次gc则年龄+1,当年龄到达某个值(默认为15),这些对象就会成为老年代。最后from survivor 和 to survivor互换,原to survivor成为下一次GC时的from survivor区。 老年代 主要存放应用程序中生命周期长的内存对象,对于一些较大的对象(急需要分配一块较大的连续内存空间),是直接进入老年代的,还有一些是从新生代survivor区域熬过来的对象。 老年代的对象比较稳定,所以 full gc不会频繁执行,升到老年代的对象大于老年代剩余空间时full gc,或者小于时被HandlePromotionFailure参数强制full gc。 Full gc采用标记-清除算法:首先扫描一次所有的老年代,标记处存活对象,然后挥手没有被标记的对象。Full gc耗时长,因为要扫描再挥手,会产生内存碎片,为了减少内存损耗,可采用标记整理法。当老年代也装不下的时候,就会抛出OOM异常。 永久代 指内存的永久保存区域,主要存放class和原数据信息。类在被加载的时候放入永久代。他和堆区域不一样,gc不会在主程序运行期间对永久代进行清理。这也导致了永久代的区域随着加载类的增多而胀满,最终抛出OOM异常(堆内存溢出)。 java8中,永久代已经被移除,被一个称为“元空间”的区域所取代。元空间的本质和永久代类似,都是对jvm规范中方法区的实现。不过元空间和永久代的最大区别在于:元空间不在jvm,而是使用本地内存。默认情况下,元空间的大小仅受本地内存限制。类的元数据放入本地内存,字符串池和静态变量放入java堆中。这样加载多少类的元数据是由系统的实际可用空间来控制的。 采用元空间取代永久代的原因:A. 为了解决永久代OOM问题,元数据和类对象存放在永久代中,容易出现性能问题和内存溢出。B. 类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出(因此对控件有限,此消彼长)。 2.对什么对象Java并不采用引用计数法来判定对象是否可用,因为引用计数法不能很好地解决循环引用问题。而是采用根搜索法,思想是通过一系列名为“GC ROOTS”的对象作为起始点,从节点开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC ROOTS没有任何引用链相连,即从GC ROOTS到这个对象不可达。则证明此对象不可用,可回收。从root搜索不到,且经过第一次标记清理后,仍然没有复活的对象。 可作为GC ROOTS的对象:虚拟机栈中局部变量引用的对象。类静态属性引用的对象。常量引用的对象,JNI中引用的对象。 3.做了什么主要做了清理对象、整理内存的工作。新生代采用了复制算法,过程如上。复制算法优点:无内存碎片化,浪费可用内存。老年代采用的是标记整理法,首先扫描一次所有老年代,标记处存活对象,然后把存活对象再移动到一端,最后回收没有被标记的对象。标记的过程其实就是遍历所有gc roots,然后将所有gc roots可到达的对象标记为存活对象。缺点:标记和整理过程效率不高,因为还需要移动对象,成本相对较高。优点:不会产生内存碎片化。 垃圾回收算法: 标记清除法:先标记要回收的对象,再统一进行回收操作。简单方便、内存碎片化严重。 复制算法:将对分成2部分,一份空白(A),一份存放对象(B),GC后,将B中存活的对象复制到A中,然后B变成空白。 标记-整理:先标记可回收对象,将存活的对象移动到一端,再回收对象。无内存碎片。成本相对较高 分代回收:年轻代用复制算法,老年代用标记整理算法。 根据对象存活特性,合理使用回收算法,商业jvm都用分代回收。 既然jvm采用分代回收,name年轻代和老年代各自使用的垃圾收集器也不一样。年轻代使用的垃圾收集器是:Serial,ParNew,Parallel Scavenge。老年代使用的是:CMS,SerialOld,Parallel Old。通用的收集器G1。 串行的,也就是采用单线程(比较老了),分类:serial new(收集年轻代,复制算法)和serial old(收集老年代,标记整理),缺点:单线程,进行垃圾回收时暂时所有的用户线程。优点:实现简单。 并行的,采用多线程,对于年轻代有两个:ParNew和Parallel scavenge,ParNew回收期间暂停其他所有工作线程。parallel scavenge是一个针对年轻代的垃圾回收器,采用复制算法,主要的优点是进行垃圾回收时不会停止用户线程。Parallel Old是老年代的垃圾回收器,使用多线程,标记整理法。 CMS收集器:已获得最短回收暂停时间为目标,允许垃圾收集线程与用户线程并行。主要有以下4个阶段: 初始标记,标记GC ROOTS可关联的用户对象,会暂停用户线程。 并发标记,根搜索过程中,用户线程并行。 重新标记,由于在并发标记过程中,用户线程导致的对象状态变化,会暂停用户线程。 并清理,清理可回收对象,用户线程并行。 G1:一款先进的通用垃圾收集器,他针对整个堆,并将其分为大小相等的区域,记录每个区占用信息,优先回收价值更高的区域。]]></content>
<categories>
<category>java基础知识</category>
</categories>
<tags>
<tag>java</tag>
<tag>GC</tag>
<tag>jvm</tag>
</tags>
</entry>
<entry>
<title><![CDATA[2017java面试题(技术面试)]]></title>
<url>%2Fblog%2F2018%2F04%2F10%2FjavaInterview1%2F</url>
<content type="text"><![CDATA[1.Servlet执行流程客户端发出http请求,web服务器将请求转发到servlet容器,servlet容器解析url并根据web.xml找到对应的servlet,并将request、response对象传递给找到的servlet,servlet根据request就可以知道是谁发出请求,请求信息及其他信息,当servlet处理完业务逻辑后将信息放入到response并响应到客户端。 2.SpringMVC的执行流程SpringMVC是由dispatchservlet为核心的分层控制框架。首先客户端发出一个请求web服务器解析请求URL并去匹配dispatchservlet的映射URL,如果匹配的上就将这个请求放入到dispatchservlet,dispatchservlet根据mapping映射配置去寻找到相应得handle,然后把处理权交给找到的handle,handle封装了处理业务逻辑的代码,当handle处理完后会返回一个逻辑视图modelandview给dispatchservlet,此时的modelandview是一个逻辑视图不是一个正式视图,所以dispatchservlet会通过viewersource视图资源去解析modelandview,然后将解析后的参数放到view中返回到客户端并展现。 3.给定一个txt文件,如何得到某字符出现的次数123456789101112File file = new File("E://test.txt");InputStream is = new FileInputStream(file);byte[] b= new byte[1024]; int a = is.read(b);String str[] = new String(b,0,a).split("");int count = 0;for(int i =0;i<str.length;i++){ if("a".equals(str[i])){ count ++; }}System.out.println(count); 4.Java设计模式思想(单例模式,工厂模式,策略模式,共23中设计模式) 单例模式:单例模式核心只需要new一个实例对象的模式,比如数据库连接,在线人数等,一些网站上看到的在线人数统计就是通过单利模式实现的,把一个计时器存放在数据库或者内存中,当有人登录的时候取出来+1再放回去,有人退出的时候取出来-1再放回去。但是当有两个人同时登录的时候,会同时取出计数器,同时+1,同时放回去,这样的话数据就会错误,所以需要一个全局变量的对象给全部人使用,只需啊哟new出一个实例对象,这就是单例模式的应用,并且单例模式节省资源,因为它控制了实例对象迭代个数,有利于gc回收。 123456789101112public class SingletonClass{ private static volatile SingletonClass instance=null; public static SingletonClass getInstance(){ synchronized(SingletonClass.class){ if(instance==null){ instance=new SingletonClass(); } } return instance; } private SingletonClass(){}} 策略模式:就是将几个类中公共的方法提取到一个新的类中,从而使扩展更容易,保证代码的可移植性,可维护性强。比如有个需求是写鸭子对象,鸭子有叫、飞、外形这三种方法,如果每个鸭子类都写这三个方法会出现代码冗余,这时候我们把鸭子中的叫、飞、外形这三个方法提取出来,放到鸭子父类中,让每个鸭子都继承这个鸭父类。重新这三个方法,这样封装的代码可移植性强,当用户提出新的需求时,比如说鸭子游泳,那么对于我们程序员来讲就非常简单,只需要在鸭父类中添加一个游泳的方法,所有继承这个父类的鸭子都有了这个方法,重写游泳方法即可。 123456789101112131415161718192021222324252627282930313233343536//简单工厂public class Factory{ public static ISample creator(int which){ if (which==1) return new SampleA(); else if (which==2) return new SampleB(); }}//抽象工厂public abstract class Factory{ public abstract Sample creator(); public abstract Sample2 creator(String name);}public class SimpleFactory extends Factory{ public Sample creator(){ ......... return new SampleA } public Sample2 creator(String name){ ......... return new Sample2A }} public class BombFactory extends Factory{ public Sample creator(){ ...... return new SampleB } public Sample2 creator(String name){ ...... return new Sample2B }} 以上两种为简单和抽象工厂模式代码 工厂模式:简单的工厂模式主要是统一提供实例对象的引用,通过工厂模式接口获取实例对象的引用。比如一个登录功能,后端有三个类,Controller类、interface类、实现接口的实现类。当客户端发出一个请求,当请求传到Controller类中时,Controller获取接口的引用对象,而实现接口的实现类中封装好了登录的业务逻辑代码。当你需要加一个注册需求的时候只需要在接口类中加一个注册方法,实现类中实现方法,Controller获取接口的引用对象即可,不需要改动原来的代码,这种做法可拓展性强。 5.冒泡排序、二分查找 冒泡 123456789101112public static void mp(int a[]){ int swap = 0; for(int i=0;i<a.length;i++){ for(int j=i;j<a.length;j++){ if(a[j]>a[i]){ swap = a[i]; a[i] = a[j]; a[j] = swap; } } }} 二分法查找 1234567891011121314151617public static int ef(int a[],int tag){ int first = 0; int end = a.length; for(int i=0;i<a.length;i++){ int middle = (first+end)/2; if(tag == a[middle]){ return middle; } if(tag>a[middle]){ first = middle + 1; } if(tag<a[middle]){ end = middle - 1; } } return 0;} 代码如上所示 6.对ajax的理解Ajax为 异步请求,即局部刷新技术,在传统的页面中,用户需要点击按钮或者事件触发请求,到刷新页面,而异步技术为不需要点击即可触发事件。(不需要刷新整个网页,而刷新部分网页数据) 7.父类与子类之间的调用顺序(打印结果) 父类静态代码块 子类静态代码块 父类的构造方法 子类的构造方法 子类的普通方法 重写父类的方法,则打印重写后的方法 8.内部类与外部类的调用 内部类可以直接调用外部类包括平private的成员变量,使用外部类引用的this.关键字调用即可 外部类调用内部类需要建立内部类对象 9.多线程 一个进程是一个独立的运行环境,可以看做是一个程序,而线程可以看做是进程的一个任务,比如QQ是一个进程,而一个QQ窗口是一个线程。 在多线程程序中,多线程并发可以提高程序的效率,CPU不会因为某个线程等待资源而进入空闲状态,它会把资源让给其他的线程。 用户线程就是我们开发程序是创建的线程,而守护线程为系统线程,如JVM虚拟中的GC。 线程的优先级别:每一个线程都有优先级别,有限制级高的可以先获取CPU资源使该线程从就绪状态转为运行状态。也可以自定义线程的优先级别。 死锁:至少两个以上线程争取两个以上CPU资源,避免死锁就避免使用嵌套锁,只需要在他们需要同步的地方加锁和避免无限等待。(发生死锁的四个条件:互斥条件、请求和保存条件、不剥夺条件、环路等条件)。1 互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某个资源只由一个进程占用。若果此时还有其他进程请求资源,则请求者只能等待,直到战友的进程用完释放。2 请求和保持条件:指进程以及保持了至少一个资源,但又提出了新的资源请求,而该资源已经被其他进程占有,此时请求进程阻塞,但又对自己已获得的其他资源保持不放。3 不剥夺条件:指进程已获得的资源,在未使用完之前,不能剥夺,只能在使用完时由自己释放。4 环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{p0,p1,p2,….,pn}中的p0正在等待一个p1占用的资源;p1正在等待p2占用的资源,….,pn增在等待已被p0占用的资源。 10.AOP与IOC的概念(即spring的核心) IOC:Spring是开源框架,使用框架可以使我们减少工作量,提高工作效率并且它是分层结构,即相对应的层处理对应的业务逻辑,减少代码的耦合度。而spring的核心是IOC控制反转和AOP面向切面编程。IOC控制反转主要强调的是程序之间的关系是由容器控制的,容器控制对象,控制了对外部资源的获取。而反转即为,在传统的编程中都是由我们创建对象获取依赖对象,而在IOC中是由容器帮我们创建好对象并注入依赖对象,正是容器帮我们查找和注入对象,对象是被获取,所以叫反转。 AOP:面向切面编程,主要是管理系统层的业务,比如日志,权限,事物等。AOP是将封装好的对象剖开,找出其中对多个对象产生影响的公共行为,并将其封装为一个可重用的模块,这个模块被命名为切面(aspect),切面讲那些与业务逻辑无关,却被业务模块共同调用的逻辑提取并封装起来,减少了系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。 11.hibernate的核心思想hibernate的核心思想就是ROM对象关系映射机制,它是将表与表之间的操作映射成对象与对象之间的操作。也就是从数据库中提取的信息会自动按照你设置的映射要求封装成特定的对象。所以hibernate就是通过将数据表实体类的映射,使得对对象的修改对应数据行的修改。 12.最优删除某字符串的某个字符123456789101112131415161718192021222324252627282930313233343536373839404142/////////////////////////////////////////////////////////////////////// // Delete all characters in pStrDelete from pStrSource /////////////////////////////////////////////////////////////////////// void DeleteChars(char* pStrSource, const char* pStrDelete) { if(NULL == pStrSource || NULL == pStrDelete) return; // Initialize an array, the index in this array is ASCII value. // All entries in the array, whose index is ASCII value of a // character in the pStrDelete, will be set as 1. // Otherwise, they will be set as 0. const unsigned int nTableSize = 256; int hashTable[nTableSize]; memset(hashTable, 0, sizeof(hashTable)); const char* pTemp = pStrDelete; while ('\0' != *pTemp) { hashTable[*pTemp] = 1; ++ pTemp; } char* pSlow = pStrSource; char* pFast = pStrSource; while ('\0' != *pFast) { // if the character is in pStrDelete, move both pStart and // pEnd forward, and copy pEnd to pStart. // Otherwise, move only pEnd forward, and the character // pointed by pEnd is deleted if(1 != hashTable[*pFast]) { *pSlow = *pFast; ++ pSlow; } ++pFast; } *pSlow = '\0'; } 代码如上 13.ArrayList与LinkedList的区别都是实现List接口的列表。ArrayList是基于数组的数据结构,LinkedList是基于链表的数据结构,当获取特定元素时,ArrayList效率比较快,它通过数组下标即可获取,而LinkedList则需要移动指针。当存储和删除元素时LinkedList效率比较快,只需要移动指针指定位置即可增加和删除,而ArrayList需要移动数据。 14.数据库优化 选择合适的字段,比如邮箱字段可以设定char(6),尽量把字段设置为not null,这样查询的时候数据库就不需要比较null值 使用关联查询(Left join on)查询代替子查询 使用union联合查询手动创建临时表 开启事物,当数据库执行多条语句出现错误时,事物会回滚,可以维护数据库的完整性 使用外键,事物可以维护数据的完整性但是它却不能保证数据的关联性,使用外键可以保证数据的关联性 使用索引,索引是提高数据库性能的常用方法,它可以令数据库服务器比没有索引快的多的速度检索特定的行,特别是对于max,min,order by查询时,效果更明显 优化的查询语句,绝大多数情况下,使用索引可以提高查询的速度,但如果sql语句使用不恰当的话,索引无法发挥它的特性 15.Tomcat服务器优化(内存,并发连接数,缓存) 内存优化:主要是对Tomcat启动参数进行优化,我们可以在Tomcat启动脚本中修改它的最大内存数等 线程数优化:Tomcat的并发链接参数,主要在Tomcat配置文件中server.xml中配置,比如修改最小空闲连接线程数,用于提高系统处理能力等 优化缓存:打开压缩功能,修改参数,比如压缩的输出内容大小默认为2KB,可以适当地修改 16.HTTP协议 常用的请求方法:get、post Get和Post的区别:传送数据,get携带参数与访问地址传送,用户是可以看见,这样的话信息会不安全,导致信息泄露。而Post则将字段与对应值封装在实体中传送,这个过程用户是不可见的。Get传递参数有限制,而Post无限制 17.TCP/UDP协议TCP和UDP协议都属于传输层协议。其中TCP提供IP环境下的数据可靠传输。它提供的服务包括了数据流传送、可靠性、有效流控、全双工操作和多路复用。通过面向连接、端到端和可靠的数据包发送。通俗说,它事先为发送的数据开辟出连接好的通道,然后再进行数据发送。UDP则不为IP提供可靠性、流控或差错恢复功能。一般来说,TCP对应的可靠性要求高的应用,UDP对应的是可靠性要求低,传输经济的应用。 特点 TCP UDP 是否连接 面向连接 面向非连接 传输可靠性 可靠地 不可靠的 应用场合 传输大量的数据 少量数据 速度 慢 快 18.Java集合类框架的基本接口有哪些总共有两大接口:Collection 和Map ,一个元素集合,一个是键值对集合; 其中List和Set接口继承了Collection接口,一个是有序元素集合,一个是无序元素集合; 而ArrayList和 LinkedList 实现了List接口,HashSet实现了Set接口,这几个都比较常用; HashMap 和HashTable实现了Map接口,并且HashTable是线程安全的,但是HashMap性能更好。 java.util.Collection [I]12345678910111213141516171819java.util.Collection [I]|—java.util.List [I] |—java.util.ArrayList [C] |—java.util.LinkedList [C] |—java.util.Vector [C] |—java.util.Stack [C]|—java.util.Set [I] |—java.util.HashSet [C] |—java.util.SortedSet [I] |—java.util.TreeSet [C] java.util.Map [I]1234567891011|—java.util.SortedMap [I] |—java.util.TreeMap [C]|—java.util.Hashtable [C]|—java.util.HashMap [C] |—java.util.LinkedHashMap [C]|—java.util.WeakHashMap [C] Java集合类里最基本的接口有:Collection:单列集合的根接口List:元素有序 可重复ArrayList:类似一个长度可变的数组 。适合查询,不适合增删LinkedList:底层是双向循环链表。适合增删,不适合查询。Set:元素无序,不可重复HashSet:根据对象的哈希值确定元素在集合中的位置TreeSet: 以二叉树的方式存储元素,实现了对集合中的元素排序Map:双列集合的根接口,用于存储具有键(key)、值(value)映射关系的元素。HashMap:用于存储键值映射关系,不能出现重复的键keyTreeMap:用来存储键值映射关系,不能出现重复的键key,所有的键按照二叉树的方式排列 19.类加载过程遇到一个新的类时,首先会到方法区中去找class文件,如果没有找到就回去硬盘中找class文件,找到后返回,将class文件加载到方法区中。其次在类加载的时候,静态成员变量会被分配到方法区的静态区域,非静态成员变量分配到非静态区域,然后开始给静态成员变量初始化,赋默认值,赋完默认值后,会根据静态成员变量书写的位置赋显示值,然后执行静态代码。当所有的静态代码执行完,类加载才算完成。 20.对象的创建 遇到一个新类时,会进行类的加载,定位到class文件 对所有静态成员变量初始化,静态代码块也会执行,而且只有类加载的时候执行一次 New对象时,JVM会在堆中分配一个足够大的存储空间 存储空间清空时,为所有的变量赋默认值,所有的对象引用赋值为null 根据书写的位置给一些字段初始化操作 调用构造器方法(没有继承) 21.JVM优化 设置参数,设置JVM的最大内存数 垃圾回收器的选择 22.高并发处理了解一点高并发性问题,比如一万个人抢一张票时,如何保证票在没买走的情况下所有人都能看见这张票。显然,不能用同步机制,因为Synchronize是锁同步一次只能一个人进行。这时候可以用到锁机制,采用乐观锁可以解决这个问题。乐观锁的简单意思是在不锁定表的情况下,利用业务的控制来解决并发问题这样既保证了数据的可读性,又保证数据的排他性,保证性能的同事解决了并发带来的脏读问题。(注:乐观锁的实现原理是cas操作,java中轻量级锁也是基于cas实现的。悲观锁最大的问题就是阻塞问题。) 23.事物的理解事物一般具有原子性、一致性、持久性、隔离性。 原子性:是指一个事物中,要么全部执行成功,要么全部失败滚回。 一致性:事物执行之前和执行之后都处于一致性状态 持久性:事物多数据的操作是永久的 隔离性:当一个事物正在对数据进行操作时,另一个事物不可以对数据进行操作,也就是多个并发书屋之间相互隔离 24.Struts工作流程 客户端发出一个请求到servlet容器 请求经过一系列过滤杯filterdispatcher调用,filterdispatch通过actionMapper去找相对应的action Actionmapper找到对应的action返回给filterdispatch,dispatch吧处理权交给actionproxy Actionproxy通过配置文件找到对应的action类 Actionproxy创建一个actioninvocation的实例处理业务逻辑 一旦action处理完毕,actioninvocation负责根据struts.xml的配置找到对应的返回结果,返回结果一般是jsp页面]]></content>
<categories>
<category>java面试题</category>
</categories>
<tags>
<tag>java</tag>
<tag>面试</tag>
</tags>
</entry>
<entry>
<title><![CDATA[java内存管理以及常量池]]></title>
<url>%2Fblog%2F2018%2F04%2F04%2Fjava2%2F</url>
<content type="text"><![CDATA[java内存管理以及常量池java的内存管理就是对象的分配与释放问题。 在java中内存的分配由程序完成的,而内存的释放是由垃圾回收器(Garbage Collection,GC)完成的。 java虚拟机将其管辖的内存大致分为了三个逻辑部分: 1.方法区(Method Area)2.Java栈(Stack)3.Java堆(Heap) 目录: [TOC] 1.方法区方法区是静态分配的,编译器将变量绑定在某个存储位置上,而且这些绑定不会在运行时改变。常量池、源代码中的命名常量、String常量、代码段和数据段、静态域(用来存放static定义的静态成员)都放在方法区。 常量池(constant pool)常量池指的是在编译期就被确定,并保存在已编译的.class文件中的一些数据。处理包含代码中所定义的各类基本类型(如int、long等等)和对象型(如String及数组)的常量值(final)还包含一些以文本形式出现的符号引用,比如: 类和接口的全限定名 字段的名称和描述符 方法和名称和描述符 类和接口的全限定名 2.Java栈Java栈是一个逻辑感念,特点是后进先出。一个栈的空间可能是连续的,也可能不是。用来保存局部变量的值,包括:1.用来保存基本数据类型的值;2.存放基本类型的数据和对象的引用,但对象本身不存放在栈中,而是存放在堆中。栈的优势:存取速度要比堆快。缺点:存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。 3.Java堆堆分配意味着随意的顺序,在运行时进行存储空间分配和收回的内存管理模型。Java堆用来存放动态产生的数据,比方通过new关键字创建出来的对象。注意创建出来的对象只包含属于各自的成员变量,并不包括成员方法,因为同一个类的对象拥有各自的成员变量,存储在各自的堆中,但是他们共享该类的方法,并不是每创建一个对象就把成员方法复制一次。堆是由垃圾回收来负责的,堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的,Java的垃圾收集器会自动地收走这些不再使用的数据,但缺点是在运行时动态分配内存,存取速度较慢。 4.Java的内存垃圾回收机制Java的内存垃圾回收机制是从程序的主要运行对象开始检查引用链,当遍历一遍发现没有被引用的孤立对象就作为垃圾回收。GC为了能够正确释放对象,必须监控每一个对象的运行状态,包括对象的申请、引用、被引用、赋值等。监视对象状态是为了更加准确、及时地释放对象,而释放对象的根本原则就是该对象不在被引用 5.内存泄露在Java中,内存泄露就是存在一些被分配的对象,这些对象有以下两个特点: 这些对象是可达的,即引用指向 这些对象是无用的,即程序以后不会再使用这些对象 如果满足这两个条件,这些对象就可以被判定为Java中的内存泄露,这些对象不会被GC所回收,但是它却仍然占用内存 6.字符串的内存分配与常量池对于字符串,其对象的引用都是存储在栈中的,如果是编译期已经创建好的就存储在常量池(如:直接用双引号定义的或者final修饰并且能在编译期就能确定的),如果在运行期间才能确定好就存储在堆中(如:new关键字创建出来的)。对于equals相等的字符串,在常量池中永远只有一份,在堆中有多份。 如以下代码:1234567String s1 = "Hello";String s2 = "Hello";String s3 = "Hello";String s4 = new String("Hello");String s5 = new String("Hello");String s6 = new String("Hello"); 分析:s1、s2、s3在编译期就能确定,在创建对象时,现在厂里吃重寻找到相同的值,若没有,则在常量池中创建一个对象。对于通过new产生的一个字符串,会先去常量池中查找是否已经有这个对象(此处”Hello”),如果没有则在常量池中创建一个此字符串对象,然后堆中创建一个常量池中的此对象(此处“Hello”)的拷贝对象。 若要问:?String s = new String(“Hello”)这个语句产生了几个对象?答案:一个或两个,若果常量池中原来没有“Hello”,那就是两个。 存在于.class文件中的常量池,在运行期被JVM装载,并且可以扩充String类的intern()方法就是扩充常量池的一个方法;当一个String实例str调用intern()方法时,Java查找常量池中是否有相同Unicode的字符串常量,若果有,则返回其引用,如果没有,则在常量池中增加一个Unicode等于str的字符串并返回它的引用。 如下代码:123456789String s0= "Java";String s1=new String("Java");String s2=new String("Java");s2=s2.intern(); //把常量池中"Hello"的引用赋给s2System.out.println( s0==s1);System.out.println( s0==s1.intern() );System.out.println( s0==s2); 运行结果 falsetruetrue String常量池问题的例子:12345678910111213141516171819202122【1】String s0 = "HelloWorld";String s1 = "World";String s2 = "Hello" + s1;System.out.println((s0 == s2)); //结果为false【2】String s0 = "HelloWorld";final String s1 = "World";String s2 = "Hello" + s1;System.out.println((s0 == s2)); //结果为true【3】String s0 = "HelloWorld";final String s1 = getWorld();String s2 = "Hello" + s1;System.out.println((s0== s2)); //结果为falseprivate static String getWorld() { return "World";} 分析:【1】中,JVM对于字符串引用(由于在字符串的”+”连接中),有字符串引用存在,而引用的值在程序编译期是无法确定的,即“Hello”+s1无法被编译期优化,只有在程序运行期来动态分配并将连接后的新地址赋给s2。 【2】和【1】中唯一不同的是bb字符串加入了final修饰,对于final修饰的变量,它在编译时被解析为一个常量值的一个本地拷贝存储到自己的常量池中或嵌入到它的字节码流中。所以此时的“Hello”+s1和“Hello”+”World”效果是一样的。 【3】JVM对于字符串引用s1,虽然它为final修饰,但是它的值在编译期无法确定,只有在运行程序期调用方法后,将方法的返回值和“Hello”来动态连接并分配给地址s2。 对于字符串的操作,“abc”+”def”为三个字符串对象,第三个为“abcdef”。也就是说,在Java中对字符串的一切操作,都会产生一个新的字符串对象。String创建字符串时不可变的,任何对String的改变都会引发新的String对象的生产,而不是那些不用的未被引用的对象则为作为垃圾回收。 7.基本类型的变量和常量在内存中的分配对于基本类型的变量和常量,变量和引用存储在栈中,常量存储在常量池中。 如以下代码:1234567int i1 = 9;int i2 = 9;int i3 = 9;final int INT1 = 9;final int INT2 = 9;final int INT3 = 9; 如图 编译器先处理int i1 = 9; 首先它会在栈中创建一个变量为i1的引用,然后查找栈中是否有9这个值,如果没找到,就将9存放进来,然后i1指向9。接着处理int i2 = 9;创建完i2的引用变量后,因为栈中已经有9这个值,便将i2直接指向9。这样,就出现了i1与i2同时均指向9的情况。最后i3也指向这个9。 基本类型和基本类型的包装类。基本类型有:byte、short、char、int、long、boolean。基本类型的包装类分别对应的是:Byte、Short、Character、Integer、Long、Boolean(注意区分大小写) 二者区别:基本类型体现在程序中的普通变量,基本类型的包装类是类,体现在程序中的引用变量。因此二者在内存中的存储位置不同:基本类型存储在栈中,而基本类型包装类存储在堆中。上边提到的这些包装类都实现了常量池技术,另外两种浮点数类型的包装类则没有实现。 如下代码:12345678910Integer i1=10;Integer i2=10;Integer i3=new Integer(10);Integer i4=new Integer(10);System.out.print(i1==i2); //输出为trueSystem.out.print(i2==i3); //输出为falseSystem.out.print(i3==i4); //输出为falseDouble d1=1.0;Double d2=1.0;System.out.print(d1==d2); //输出为false 分析: i1和i2均是引用类型,在栈中存储指针,因为Integer是包装类,由于Integer包装类实现了常量池技术,因此i1和i2的10均是从常量池中获取的,均指向同一个地址,因此i1=i2 i3和i4均是引用类型,在栈中存储指针,因为Integer是包装类。但是由于他们各自都是new出来的,因此不再从常量池寻找数据,而是从堆中各自new一个对象,然后各自保存指向对象的指针,所以i3和i4不相等,因此他们所存指针也不同,所指向的对象不同。 d1和d2均是引用类型,在栈中存储指针,因为Double是包装类,但Double包装类没有实现常量池技术,因此Double d1 = 1.0;相当于Double d1 = new Double(1.0);是从堆中new一个对象。 8.成员变量和局部变量在内存中的分配对于成员变量和局部变量:成员变量就是方法外部,类的内部定义的变量;局部变量就是方法或语句块内部定义的变量。局部变量必须初始化。形式参数是局部变量。局部变量的数据存储在栈内存中。栈内存中的局部变量随着方法的消失而消失。成员变量存储在堆中的对象里面,由垃圾回收器负责回收。 9.==与equals() ==是一个运算符 Equals则是String对象的方法 比较无法就是两种情况: 基本数据类型的比较 引用对象的比较 1.基本数据类型比较 ==比较两个值是否相等,相等则为true,否则为false;而equals()是String类的额方法,不用来比较基本数据类型 2.引用对象比较 ==比较两个对象是否指向内存中的同一个对象,即指向同一个地址;而equals()则比较String对象的内容是否相同 注意 String是一个特殊的引用类型,对于两个字符串的比较,不管是==还是Equals,两者比较的都是字符串是否相同,例如下面的两个例子的输出结果都是true: System.out.println(“Hello”==”Hello”);System.out.println(“Hello”.equals(“Hello”)); 当你创建两个String对象时,内存中的地址是不相同的,你可以赋相同的值。对弈字符串的内容相同,引用地址不一定相同(相同的内容的对象地址不一定相同),反过来,引用地址相同则内容一定相同。]]></content>
<categories>
<category>java基础知识</category>
</categories>
<tags>
<tag>java</tag>
</tags>
</entry>
<entry>
<title><![CDATA[java中关于try、catch、finally的分析]]></title>
<url>%2Fblog%2F2018%2F04%2F03%2Fjava1%2F</url>
<content type="text"><![CDATA[java中关于try、catch、finally的分析 1.try、catch、finally语句中,在如果try语句有return语句,则返回的之后当前try中变量此时对应的值,此后对变量做的任何修改,都不影响try中return的返回值 2.如果finally块中有return语句,则返回try或catch中的返回语句忽略 3.如果finally块中抛出异常,则整个try、catch、finally块中抛出异常 在使用try、catch、finally语句块中需要注意的 1.尽量在try或者catch中使用return语句。通过finally块中达到对try或者catch返回值修改是不可行的。 2.finally块中避免使用return语句,因为finally块中如果使用return语句,会显示的消化掉try、catch块中的异常信息,屏蔽了错误的发生 3.finally块中避免再次抛出异常,否则整个包含try语句块的方法会抛出异常,并且会消化掉try、catch块中的异常 java异常在java中异常的继承主要有两个:Error和Exception; 1.其中Error为jvm出现错误,以及系统奔溃等现象,这些错误没有办法通过程序来处理。多以在程序中不能使用catch来捕获这类异常。 2.对于Exception又可以分为checkedException和RuntimeException这两类异常, 2.1checkedException异常在进行编译运行之前就可以知道会不会发生异常,如果不对这类异常进行抛出、捕获的话就不能通过编译。 2.2RuntimeException就是在运行时候出现的异常,在运行之前无法确定 一般对异常的处理把会出现异常的代码块放在try语句块中,当抛出异常的时候就会生成一个异常对象。然后进行查找捕获这个异常,然后再进行对这个异常的处理 为什么要使用finally把资源释放或状态还原的代码放到finally块中,可以保证在try和catch语句块中执行完后,一定会执行finally语句块,而不用考虑各种复杂的跳转情况。 需要注意的是,当异常没有被catch,则直接抛出,也不会执行到try-catch下面的语句,因为这个异常被系统处理就是打印了异常栈的信息之后就结束了这个程序,也就是结束了这个进程 finally一般在什么时候执行finally在return语句之后,跳转到上一级程序之前执行 public static void main(String[] args) { System.out.println(test()); } public static String test() { try { System.out.println("try block"); return test1(); } finally { System.out.println("finally block"); // return "finally"; } } public static String test1() { System.out.println("return statement"); return "after return"; } 执行结果: try blockreturn statementfinally blockafter return 分析:1.try语句块,return test1(),则调用test1方法2.test1()执行完后返回“after return”,返回值“after return”保存在一个临时区域里3.执行finally语句块,若finally语句有返回值,则此返回值将替换掉临时区域的返回值4.在降临时区域的返回值送到上一级方法中 try、catch、finally都有return 1.在没有异常的情况下,try中的返回值先保存到临时区域里,再去执行finally,这个finally有返回值,这个返回值将之前try保存到临时区域的值替换掉,再将这个临时区域中的值返回到上一级方法。 2.如果有一场,则执行catch中的代码,这里的return将返回一个返回值放到临时区域,再去执行finally。这个finally有返回值,这样就将catch中存在临时区域中的值这个finally返回的值替换掉,再将这个临时区域的值返回给上一级。 总结:如果finally语句有返回值,则此返回值将会替换掉临时区域原来的值 验证: public int amethod() { try { // 1,抛出异常 FileInputStream dis = new FileInputStream("test1.txt"); } catch (Exception ex) { // 2.catch捕获异常,并执行 System.out.println("No such file found"); // 4,return 返回 return -1; } finally { // 3.finally一定会在return之前执行。(准确说,应该是return;语句) System.out.println("Done finally"); } return 0; } 输出结果: No such file foundDone finally-1 总结finally其实仅在return语句执行之前执行,如果return一个函数,则先执行这个函数,如果函数内有return语句,那么finally就会在这个return语句执行之前执行。如果catch中有返回值,而finally中也存在return返回值的话,则finally的返回值一定会替换掉catch中的返回值,因为catch中的返回值是存放在一个临时区域,try相同。 除非调用system.exit()让程序退出,也就是将调用的这个程序的进程断开,退出这个程序(断电等因素致使程序停止,进程终止),否则无论如何finally块都一定会执行]]></content>
<categories>
<category>java基础知识</category>
</categories>
<tags>
<tag>java</tag>
</tags>
</entry>
</search>
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。