Iterator可用来遍历Set和List集合,但是ListIterator只能用来遍历List。
Iterator对集合只能向前遍历,ListIterator既可以向前也可以向后。
ListIterator实现了Iterator接口,并包含其他的功能,比如:增加元素,替换元素,获取前一个和后一个元素的索引等等。
快速失败:在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的结构进行了增加、删除或修改,则会抛出Concurretn Modification Exception。
原理:迭代器在遍历时直接访问集合中的内容,并在遍历过程中使用一个modCount变量,集合在遍历期间如果结构发生变化,就会改变modCount的值,每当迭代器使用hasNext()/next()遍历下一个元素时,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。
java.util包下的集合类都是快速失败的,不能在多线程下发生并发修改,即迭代过程中修改该。
安全失败:采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。
尽管基于拷贝内容避免了异常,但是迭代器并不能访问到修改后的元素。
java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改。
Java中的HashMap是以键值对的形式存储元素的,HashMap需要一个hash函数,它使用hashCode()和equals()方法来向集合添加元素和检索元素。当调用put()方法的时候,HashMap会计算key的hash值,然后把键值对存储在集合中合适的索引上,如果key已经存在了,value会被更新成新值。
Java中的HashMap使用hashCode()来确定键值对的索引,当根据键获取值的时候也会用到这两个方法,如果没有正确的实现这两个方法,两个不同的键可能会有相同的hash值,因此可能会被集合认为是相等的。而且,这两个方法也用来发现重复元素,所以这两个方法的实现对HashMap的精确性和正确性是至关重要的。
HashMap和HashTable都实现了Map接口,因此很多特性非常相似,但是存在以下不同:
HashMap允许键和值是null,而HashTable不允许键或者值是null。
HashTable是同步的(线程安全),而HashMap(线程非安全)不是,因此HashMap更适合于单线程环境,而HashTable更适合于多线程环境。
HashMap提供了可供应用迭代的键的集合,因此HashMap是快速失败的,另一方面,HashTable提供了对键的列举,一般认为HashTable是一个遗留的类。
区别:
Array可以包含基本类型和对象类型,ArrayList只能包含对象类型。
Array大小是固定的,ArrayList大小是动态变化的。
ArrayList提供了更多的方法和特性,比如addAll(),removeAll(),iterator()等等。
当处理固定大小的基本数据类型的时候,应该使用Array。
ArrayList和LinkedList都实现了List接口,区别如下:
ArrayList是基于索引的数据接口,它的底层是数组,可以以O(1)时间复杂度对元素进行随机访问,LinkedList的底层是双向链表,查找某个元素的时间复杂度是O(n)。
相对于ArrayList,LinkedList的插入,添加,删除操作更快,因为当元素被添加到任意位置的时候,不需要向数组那样重新计算大小或者更新索引。
LinkedList比ArrayList更占内存,因为LinkedList每一个结点存储直接前驱、直接后继以及数据。
Java提供了只包含一个compareTo()方法的Comparable接口,这个方法可以给两个对象排序,方法的返回值包括负数,0,整数来表明已经存在的对象小于,等于,大于输入对象。
Java提供了包含compare()和equals()两个方法的Comparetor接口,compare()方法用来给两个输入参数排序,返回负数,0,正数表明第一个参数是小于,等于,大于第二个参数。equals()方法需要一个对象作为参数,它用来决定输入参数是否和comparator相等,只有当输入参数也是一个comparator并且输入参数和当前comparator的排序结果是相同的时候,这个方法才返回true。
优先级队列是一个基于优先级堆的无界队列,它的元素是按照自然顺序排序的。在创建的时候,我们可以给它提供一个负责给元素排序的比较器,优先级队列不允许null值,因为没有自然顺序或者说他们没有相关联的比较器,最后优先级队列不是线程安全的,出队和入队的时间复杂度是O(log(n))。
大O符号描述了数据结构里面数据元素增加的时候,算法的规模或者是一个渐进上界。
大O符号也可以用来描述其他行为,比如:内存消耗。
因为集合类实际上是数据结构,我们一般使用大O符号基于时间,内存和性能来选择最好的实现。大O符号可以对大量数据的性能给出一个很好的说明。
有序数组最大的好处在于查找的时间复杂度是O(logn),而无序数组是O(n)。有序数组的缺点是插入操作的时间复杂度是O(n),因为需要移动插入位置后的元素,相反,无序数组的插入时间复杂度是常量O(1)。
根据应用的需要正确选择要使用的集合的类型对性能非常重要,比如:假如元素的数量是固定的,而且能事先知道,我们就应该用Array而不是ArrayList。
有些集合类允许指定初始容量,因此,如果我们能估计出存储的元素的数目,我们可以设置初始容量来避免重新计算hash值或者是扩容。
为了类型安全,可读性和健壮性的原因总是要使用泛型,同时使用泛型还可以避免运行时的ClassCastException。
使用JDK提供的不变类作为Map的键可以避免为我们自己的类实现hashCode()和equals()方法。
编程的时候接口优于实现。
底层的集合实际上是空的情况下,返回长度是0的集合或者是数组,不要返回null。
Enumeration速度是Iterator的2倍,同时占用更少的内存,但是Iterator远远比Enumeration安全,因为其他线程不能修改正在被Iterator遍历的集合里的对象,同时Iterator允许调用者删除底层集合里面的元素,这对于Enumeration来说是不可能的。
HashSet是由一个hash表来实现的,因此,它的元素是无序的,add(),remove(),contains()方法的时间复杂度是O(1)。
TreeSet是由一个树形的结构来实现的,它里面的元素是有序的,因此,add(),remove(),contains()方法的时间复杂度是O(logn)。
垃圾回收的目的是识别并且丢弃不再使用的对象来释放和重用资源。
垃圾回收是在内存中存在没有引用的对象或者超过作用域的对象时进行。
这两个方法用来提示JVM要进行垃圾回收,但是,立即开始还是延迟执行垃圾回收时取决于JVM的。
垃圾回收器决定回收某个对象时,就会运行该对象的finalize()方法,可以覆盖此方法来实现对其他资源的回收。
finalization方法主要是在调用了一些native的方法时候,在里面去调用释放函数。
不会,在下一个垃圾回收周期中,这个对象是可被回收的。
JVM的堆是运行时数据区,所有类的实例和数组都是在堆上分配内存。它在JVM启动的时候被创建,对象所占的堆内存时由自动内存管理系统也就是垃圾回收器回收。
堆内存是由存活的对象和死亡的对象组成的,存活的对象是应用可以访问的,不会被垃圾回收。死亡的对象是应用不可访问尚且还没有被垃圾收集器回收掉的对象。一直到垃圾收集器把这些对象回收之前,他们会一直占据堆内存空间。
永久代是用于存放持续使用的一些资源信息,比如Java类、方法等。永久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些类,例如Hibernate等,在这种时候需要设置一个比较大的永久待空间来存放这些运行过程中新增的类。
吞吐量收集器使用并行版本的新生代垃圾回收器,用于中等规模和大规模数据的应用程序,而串行收集器对大多数的小应用就足够了。
当对象不再存活的时候,垃圾回收可以将其回收。
判断对象是否存活,可以用计数器或者可达性分析两种方法。当计数器为0时,表明没有引用再指向该对象;可达性分析,当不能从GC Root寻找一条路径到达该对象时,表明该对象可以被回收。
Java中有两种异常:检查型异常和非检查型异常。检查型异常必须经过捕捉检查处理,否则不能通过编译,比如IOException、SQLException等。非检查型异常是在程序运行时可能会发生的,比如NullPointerException、IndexOutOfBoundsException等,所以程序可以捕捉,也可不捕捉,这些错误一般是由程序的逻辑错误引起的,程序应该从逻辑角度去尽量避免。
Exception和Error都是Throwable的子类,Exception用于用户程序可以捕获的异常,Error定义了不期望被用户程序捕获的异常,一般是指与虚拟机相关的问题,如系统崩溃、虚拟机错误,内存空间不足、方法调用栈溢出等。
Throw用于方法内部,Throws用于方法声明上;
Throw后跟异常对象,Throws后跟异常类型;
Throw后只能跟一个异常对象,Throws后可以一次声明多种异常类型。
异常处理完成后,Exception对象会在下一个垃圾回收过程中被回收掉。
JDBC是允许用户在不同数据库之间做选择的一个抽象层。JDBC允许开发者用Java写数据库应用程序,而不需要关系底层特定数据库的细节。
JDBC提供了特定厂商对JDBC API接口类的实现,驱动必须要提供java.sql包下面这些类的实现:Connection,Statement,PreparedStatement,CallableStatement,ResultSet和Driver。
PreparedStatement是预编译的,因此性能会更好,同时,不同的查询参数值,PreparedStatement可以重用。
像打开、连接和关闭数据库这种和数据库的交互可能非常费时,尤其当客户端数量增加的时候,会消耗大量资源,成本较高,可以在应用服务器启动的时候建立多个数据库连接并维护在一个池中。连接请求由池中的连接提供,在连接使用完毕以后,把连接归还到池中,以用于满足将来更多的请求。
Servlet是用来处理客户端请求并产生动态网页内容的Java类,Servelt主要是用来处理或者是存储HTML表单提交的数据,产生动态内容,在无状态的HTTP协议下管理状态信息。
对每一个客户端的请求,Servlet引擎载入Servlet,调用它的init()方法,完成Servlet的初始化,然后Servlet对象通过为每一个请求单独调用service()方法来处理所有随后来自客户端的请求,最后,调用Servlet的destroy()方法把Servlet删除掉。
HTTP响应由三个部分组成:
状态码(Status Code):描述了响应的状态,可以用来检查是否成功的完成了请求,请求失败的情况下,状态码可以用来找出失败的原因。如果Servlet没有返回状态码,默认会返回成功的状态码。
HTTP头部(HTTP Header):它们包含了更多关于响应的信息,比如响应过期的日期,或者指定用来给用户安全的传输实体。
响应主体(Body):包含了响应的内容,比如HTML代码,图片等,主体是由传输在HTTP消息中紧跟在头部后面的数据字节组成。
cookie是Web服务器发送给浏览器的一块信息,浏览器会在本地文件中给每一个Web服务器存储cookie,以后浏览器在给特定服务器发送请求的时候,同时会发送所有为该服务器存储的cookie,服务器检查该cookie,从而判断用户的状态。
session是另一种记录客户状态的机制,不同的是cookie保存在客户端浏览器中,而session保存在服务器上,客户端浏览服务器的时候,服务器把客户端信息以某种形式存储在服务器上。当客户端浏览器再次访问服务器的时候,服务器只需要从该session中查找该客户的状态就可以了。
如果说cookie机制是检查客户身上的“通信证”,那么session机制就是通过检查服务器上的“客户明细表”来确认客户身份。
封装就是将属性私有化,提供公有的方法访问私有属性,封装给对象提供了隐藏内部特性和行为的能力。对象提供了一些能被其他对象访问的方法来改变内部的数据。
封装的好处:
通过隐藏对象的属性来保护对象内部的状态,提高了代码的安全性、可用性和可维护性。
多态是同一行为具有多个不同表现形式或形态的能力,多态不仅仅发生在父类和与子类之间,还发生在接口和实现类之间。
多态需要满足三个前提条件:继承、重写、父类引用指向子类对象,例如Parent p = new Child()
。