面试题
1.&
(1)按位运算符; (2)逻辑运算符
作为逻辑运算符时,&左右两端条件式有一个为假就会不成立,但是两端都会运行,比如(1+2)=4 &(1+2)=3;1+2=4即使为假也会去判断1+2=3是否成立。
2.&&——逻辑运算符
&&也叫做短路运算符,因为只要左端条件式为假直接不成立,不会去判断右端条件式。
对于& :无论任何情况,&两边的操作数都会参与计算
对于&& :当&&左边的操作数为false是,&&右边的操作将不参与计算,此时最终结果都为false
无论使用哪个运算符,对最终的运算结果时候没有影响的。
推荐使用&& ,效率更高
&(与运算)与异或与的区别,&可以作为位运算符
3.按位与运算符(&)#
定义:参加运算的两个数据,按二进制位进行"与"运算。
运算规则:
0&0=0 0&1=0 1&0=0 1&1=1
总结:两位同时为1,结果才为1,否则结果为0。
例如:3&5 即 0000 0011& 0000 0101 = 0000 0001,因此 3&5 的值得1。
注意:负数按补码形式参加按位与运算。
与运算的用途:
1)清零
如果想将一个单元清零,即使其全部二进制位为0,只要与一个各位都为零的数值相与,结果为零。
2)取一个数的指定位
比如取数 X=1010 1110 的低4位,只需要另找一个数Y,令Y的低4位为1,其余位为0,即Y=0000 1111,然后将X与Y进行按位与运算(X&Y=0000 1110)即可得到X的指定位。
3)判断奇偶
只要根据最未位是0还是1来决定,为0就是偶数,为1就是奇数。因此可以用if ((a & 1) == 0)代替if (a % 2 == 0)来判断a是不是偶数。
2.HashSet与TreeSet的区别
HashSet是由一个hash表来实现的,因此,它的元素是无序的。add(),remove(),contains()方法的时间复杂度是O(1)。
TreeSet是由一个树形的结构来实现的,它里面的元素是有序的。因此,add(),remove(),contains()方法的时间复杂度是O(logn)。
HashSet
不能保证元素的排列顺序,顺序有可能发生变化
当向HashSet集合中存入一个元素时,HashSet会调用该对象的
如果hashcode相同通过equlse方法判断是否相同,相同就覆盖,不同就加载链表后面
TreeSet
TreeSet是SortedSet接口的唯一实现类
TreeSet可以确保集合元素处于排序状态。TreeSet支持两种排序方式,自然排序 和定制排序,其中自然排序为默认的排序方式
存取有序,按照对象中的Compareto排序或者按照创建TreeSet的时候实例化的compare方法排序
不允许存放null值,底层是用红黑树排列的
自然排序
-
1.要排序的类中实现 Comparable<T>接口
-
2.重写Comparable接口中的Compareto方法
比较器排序
-
创建接口的时候new一个Comparator接口,
-
实现里面的在里面compare方法中定义比较规则
两个排序器都写的话按照接口中的排序
HashSet和TreeSet的区别
Set中元素不可以重复,是无序的(这里的无序是指存入元素的先后顺序与输出元素的先后顺序不一致)
HashSet:
①内部的数据结构是哈希表,是线程不安全的。
②HashSet中保证集合中元素是唯一的方法:通过对象的hashCode和equals方法来完成对象唯一性的判断。如果对象的hashCode值不同,则不用判断equals方法,就直接存到HashSet中。
注意:如果元素要存到HashSet中,必须覆盖hashCode方法和equals方法。
③HashSet是哈希表实现的,HashSet中的元素是无序的。集合元素可以是null,但只能放入一个null。
HashSet要求放入的对象必须实现HashCode()方法,放入的对象是以hashcode码作为标识的,而具有相同内容的String对象,HashCode是一样的,所以放入的内容不能重复。但是同一个类的对象可以放入不同的实例。
TreeSet:
TreeSet是SortedSet接口的唯一实现类,TreeSet可以保证集合元素处于排序状态。TreeSet支持两种排序方式,自然排序和定制排序,其中自然排序是默认的排序方式,想TreeSet中加入的对象应该是同一个类的对象。
①可以对Set集合中的元素进行排序,是线程不安全的。
②TreeSet判断元素唯一性的方法是:根据比较方法的返回结果是否为0,如果是0,则是相同元素,如果不是0,是不同元素,存储。
④TreeSet是二叉树实现的,TreeSet中的数据是自动排好顺序的,不允许放入null值
总结:HashSet存放对象的时候判断对象是否相等,是根据对象的hashCode和equals方法。TreeSet存放对象时,是根据对象的compareTo方法比较两个对象是否相等,并进行比较。
Hashset和Treeset的使用场景
HashSet:哈希表是通过使用称为散列法的机制来存储信息的,元素并没有以某种特定的顺序来存放。
TreeSet:提供一个使用树结构存储set接口的实现(红黑树算法)。对象以升序顺序存储,访问和遍历的时间很快。
使用场景:HashSet是基于Hash算法实现的,其性能通常都优于TreeSet。我们通常都应该使用HashSet,在我们需要排序的功能时,我们才使用TreeSet。
HashSet和TreeSet的区别
案例:
登录成功需要用户名和密码都正确,如果用户名错误了就没必要去判断密码了。
相同点:只要有一端为假,则语句不成立
|——SortedSet接口——TreeSet实现类
2Set接口——|——HashSet实现类 3
|——LinkedHashSet实现类 4
HashSet 5
此类实现 Set 接口,由哈希表(实际上是一个 HashMap 实例)支持。它不保证集合的迭代顺序;特别是它不保证该顺序恒久不变。此类允许使用 null 元素。 6
此类为基本操作提供了稳定性能,这些基本操作包括 add、remove、contains 和 size,假定哈希函数将这些元素正确地分布在桶中。对此集合进行迭代所需的时间与 HashSet 实例的大小(元素的数量)和底层 HashMap 实例(桶的数量)的“容量”的和成比例。因此,如果迭代性能很重要,则不要将初始容量设置得太高(或将加载因子设置得太低)。
55TreeSet 56
此类实现 Set 接口,该接口由 TreeMap 实例支持。此类保证排序后的 set 按照升序排列元素,根据使用的构造方法不同,可能会按照元素的自然顺序 进行排序,或按照在创建 set 时所提供的比较器进行排序。 57
是一个有序集合,元素中安升序排序,缺省是按照自然顺序进行排序,意味着TreeSet中元素要实现Comparable接口; 58
我们可以构造TreeSet对象时,传递实现了Comparator接口的比较器对象.
总结 116HashSet是基于Hash算法实现的,其性能通常优于TreeSet,我们通常都应该使用HashSet,在我们需要排序的功能时,我门才使用TreeSet
0
首先要明白一点:加入Set里面的元素必须定义equals()方法以确保对象的唯一性。
第一个问题:TreeSet是怎么实现有序的,它是按什么规则排序的? TreeSet的底层实现是采用红-黑树的数据结构,采用这种结构可以从Set中获取有序的序列,但是前提条件是:元素必须实现Comparable接口,该接口中只用一个方法,就是compareTo()方法。当往Set中插入一个新的元素的时候,首先会遍历Set中已经存在的元素(当然不是采用顺序遍历,具体采用什么方法,建议自己去看看源码),并调用compareTo()方法,根据返回的结果,决定插入位置。进而也就保证了元素的顺序。
第二个问题:它们怎么保证元素的不重复,是根据什么判断两个元素相同而不再添加的呢? 上面已经说过,加入Set里面的元素必须定义自己的equals()方法,但是对于良好的设计风格,最好在覆盖equals()方法的同时,也覆盖hashCode()方法,当然,对于TreeSet而言不用覆盖hashCode()方法也可。请记住:覆盖hashCode()方法的目的,只有一个原因就是提高效率。
在往Set中插入新的对象时,首先会用该对象的hashCode()与已经存在对象的hashCode()做比较,如果相等,那就不能插入,如果不等,才会调用equals()方法,如果equals结果为true,说明已经存在,就不能再插入,如果为false,可以插入。
注:如果没有覆盖hashCode()方法,那就是只比较equals().对两个对象equals运算,是判断两个对象是否相等的关键。
1、HashSet
HashSet有以下特点:
不能保证元素的排列顺序,顺序有可能发生变化
不是同步的
集合元素可以是null,但只能放入一个null
当向HashSet集合中存入一个元素时,HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据 hashCode值来决定该对象在HashSet中存储位置。
简单的说,HashSet集合判断两个元素相等的标准是两个对象通过equals方法比较相等,并且两个对象的hashCode()方法返回值相等
注意,如果要把一个对象放入HashSet中,重写该对象对应类的equals方法,也应该重写其hashCode()方法。其规则是如果两个对 象通过equals方法比较返回true时,其hashCode也应该相同。另外,对象中用作equals比较标准的属性,都应该用来计算hashCode的值。
2、TreeSet类
TreeSet是SortedSet接口的唯一实现类,TreeSet可以确保集合元素处于排序状态。TreeSet支持两种排序方式,自然排序 和定制排序,其中自然排序为默认的排序方式。向TreeSet中加入的应该是同一个类的对象。
TreeSet判断两个对象不相等的方式是两个对象通过equals方法返回false,或者通过CompareTo方法比较没有返回0
自然排序
自然排序使用要排序元素的CompareTo(Object obj)方法来比较元素之间大小关系,然后将元素按照升序排列。
Java提供了一个Comparable接口,该接口里定义了一个compareTo(Object obj)方法,该方法返回一个整数值,实现了该接口的对象就可以比较大小。
obj1.compareTo(obj2)方法如果返回0,则说明被比较的两个对象相等,如果返回一个正数,则表明obj1大于obj2,如果是 负数,则表明obj1小于obj2。
如果我们将两个对象的equals方法总是返回true,则这两个对象的compareTo方法返回应该返回0
定制排序
自然排序是根据集合元素的大小,以升序排列,如果要定制排序,应该使用Comparator接口,实现 int compare(T o1,T o2)方法。
3、最重要
1.TreeSet 是二差树实现的,Treeset中的数据是自动排好序的,不允许放入null值。
2.HashSet 是哈希表实现的,HashSet中的数据是无序的,可以放入null,但只能放入一个null,两者中的值都不能重复,就如数据库中唯一约束。
3.HashSet要求放入的对象必须实现HashCode()方法,放入的对象,是以hashcode码作为标识的,而具有相同内容的 String对象,hashcode是一样,所以放入的内容不能重复。但是同一个类的对象可以放入不同的实例 。
3.字符流与字节流的区别
1.字节流操作的基本单元为字节;字符流操作的基本单元为Unicode码元。
2.字节流默认不使用缓冲区;字符流使用缓冲区
3.字符流通常用于处理二进制数据,实际它可以处理任意类型的数据,但它不支持直接写入或读取Unicode码元,字符流通常用于处理文本数据,它支持写入及读取Unicoke码元。
4.进程与线程的区别
进程是资源分配的最小单位,线程是CPU调度的最小单位“这样的回答感觉太抽象,都不太容易让人理解。
做个简单的比喻:进程=火车,线程=车厢
-
线程在进程下行进(单纯的车厢无法运行)
-
一个进程可以包含多个线程(一辆火车可以有多个车厢)
-
不同进程间数据很难共享(一辆火车上的乘客很难换到另外一辆火车,比如站点换乘)
-
同一进程下不同线程间数据很易共享(A车厢换到B车厢很容易)
-
进程要比线程消耗更多的计算机资源(采用多列火车相比多个车厢更耗资源)
-
进程间不会相互影响,一个线程挂掉将导致整个进程挂掉(一列火车不会影响到另外一列火车,但是如果一列火车上中间的一节车厢着火了,将影响到所有车厢)
-
进程可以拓展到多机,进程最多适合多核(不同火车可以开在多个轨道上,同一火车的车厢不能在行进的不同的轨道上)
-
进程使用的内存地址可以上锁,即一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。(比如火车上的洗手间)-"互斥锁"
-
进程使用的内存地址可以限定使用量(比如火车上的餐厅,最多只允许多少人进入,如果满了需要在门口等,等有人出来了才能进去)-“信号量”
5.Java中实现多态的机制是什么
子类继承父类或者实现父类接口
子类调用父类方法
父类指向子类应用
靠的是父类或者接口定义的引用变量可以指向子类或具体实现类的实例对象,而程序调用的方法在运行期才
动态绑定,就是引用变量所指向的具体实例对象的方法,也就是内存里正在运行的那个对象的方法,而不是引用变
量的类型中定义的方法。
6.java异常分类
可以thows,就是抛出自定义异常或者,抛出指定异常,可以定义一个全局拦截器拦截
throws 就是给异常抛出这个方法,由上一个方法调用者处理
try{}catch{} 捕获异常,在try快中出现错误就会执行catch语句,捕获异常,可以对异常进行处理。
里面还有个fannly块,不管是否抛出异常都会执行这个里面的东西,一般用于资源的释放
java异常分为编译时异常(也叫强制性异常)也叫checkedException和运行时异常(非强制性异常)也叫RuntimeException。只有java语言中提供了
checked异常。java任务编译时异常都是可以处理的。所以我们必须显式的处理,如果没有处理,该程序在编译时就会发生错误无法编译。这体现了java的设计哲
学:没有完善错误处理的代码根本没有机会被执行。对Checked异常处理方法有两种:
1.当方法知道如何处理该异常,则用try。。catch块来处理该异常。
2.当前方法不知道如何处理,则在定义该方法时声明抛出该异常 throws或者throw来单独抛出
运行时异常只有当代码在运行时才发生的异常,编译时不需要try catch。Runtime如除数是0 和数组下标越界
等。其产生频繁,处理麻烦,若显示声明或者捕获将会对程序的可读性和运行效率影响很大。所以由系统自动检测
并将它们交给缺省的异常处理程序。有处理要求也可以自行捕获。
对于运行时异常常见的有:
java.lang.NullPointerException
空指针:一般对一些参数需要进行非空判断
java.lang.ArrayIndexOutOfBoundsException
下标越界:尽量认真检查
java.lang.NoSuchMethodError
方法不存在错误
java.lang.NumberFormatException
数据格式转化异常
java.sql.SQLException
sql异常
java.lang.IllegalAccessException
无访问权限异常
7、重载和重写的区别
重载是方法名相同,参数类型不同的方法可以有多个。
重写就是子类重写父类方法,方法名,参数类型必须一样。使用的时候优先调用子类重写方法,如果子类没有重写父类方法就调用父类方法,如果重写了就优先调用子类方法。
抽象方法必须被重写。
定义不同---重载是定义相同的方法名,参数不同;重写是子类重写父类的方法
范围不同---重载是在一个类中,重写是子类与父类之间的
多态不同---重载是编译时的多态性,重写是运行时的多态性
返回不同---重载对返回类型没有要求,而重写要求返回类型,有兼容的返回类型
参数不同---重载的参数个数、参数类型、参数顺序可以不同,而重写父子方法参数必须相同
修饰不同---重载对访问修饰没有特殊要求,重写访问修饰符的限制一定要大于被重写方法的访问修饰符
8、反射获取class对象的方式有哪些,反射创建对象的方式有哪些。
1.获取Class对象的方式:
-
Class.forName(`全类名路径`);
-
对象.getClass();
-
类名.class;
2.反射获取对象的方式:
-
class对象.getConstructor().newInstance();
-
class对象.getDeclaredConstructor().newInstance();
-
class对象.newInstance();
第一种可以获取公有的有参无参的构造方法。
第二种可以获取公有与私有的有参无参的构造方法。
第三种只能获取无参的构造方法。
4.调用对象的clone方法。必须先实现java.lang.Cloneable接口。
5.使用序列化和反序列化。必须先实现Serializable接口。
6.使用unsafe.allocateInstance(class)创建对象。
9.NIO中的阻塞,非阻塞,多路复用,异步,同步是什么
阻塞:在发起read的时候会一直等待,等待传递过来数据才会返回。
-
BIO是阻塞的
非阻塞:发起read的时候查看有没有数据,没有就直接返回-1,如果有就返回正式的数据。有个问题就是会一直反复访问,造成浪费
-
NIO有非阻塞与阻塞
多路复用:解决了非阻塞模式的资源访问浪费。相当于一个监视器,select一直阻塞,如果有请求进来了就判断请求类型,做出响应操作
如果没有请求就一致阻塞。不会提高数据本身传输的效率,知识监控了io情况,节省了电脑的资源
-
不只NIO可以用多路复用,BIO也可以用多路复用。
同步:线程自己去获取结果,只要是线程自己获取结果的就都是同步。
异步:线程自己不去获取结果,而由其他线程送结果(至少两个线程)。比如我现在在敲代码,让别人给我带包烟。然后我继续敲代码,这就是异步
阻塞、非阻塞、多路复用都是同步的,不存在异步阻塞清空,只要是异步就不可能是阻塞情况。
常见的异步非阻塞:回调函数,我发起一个请求,交给其他线程处理,处理完毕之后调用回调函数给结果回调回来
比如JavaScript中的回调函数
10. String s = "Hello";s = s + " world!";这两行代码执行后,原始的 String 对象 中的内容到底变了没有?
没有变,因为String被设计成不可变类,所以它的所有对象都是不可变的。s=s+"world" 这是s=“hello word" ,是一个新的变量,
原来的Hello还在常量池里面。s只是不指向原来的变量了,指向了”helloWorld“ ,原来的那个变量还在内存中,只不是没有使用了
而已。综上而述,如果经常对字符串进行各式各样的修改,或者说,不可预见的修改,那么使用String来代表字符串的话会引起很
大的内存开销。因为String对象建立之后不能在改变,所以对每一个不同的字符串都需要一个String对象来表示。 这时,应该考虑
使用 StringBuffer 类,它允许修改,而不是每个 不同的字符串都要生成一个新的对象。并且,这两种类的对象转换十分容易。同
时,我们还可以知道,如果要使用内容 相同的字符串,不必每次都 new 一个 String。例如我们要在构造器中对一个名叫 s String
引用变量进行初始化, 把它设置为初始值,应当这样做:
public class Demo {
private String s;
...
s = "Initial Value";
...
}
而非
s = new String("Initial Value");
后者每次都会调用构造器,生成新对象,性能低下且内存开销大,并且没有意义,因为 String 对象不可改变,所 以对于内容相同的字符串,只要一个 String 对象来表示就可以了。也就说,多次调用上面的构造器创建多个对象,他 们的 String 类型属性 s 都指向同一个对象。 上面的结论还基于这样一个事实:对于字符串常量,如果内容相同,Java 认为它们代表同一个 String 对象。而 用关键字 new 调用构造器,总是会创建一个新的对象,无论内容是否相同。 至于为什么要把 String 类设计成不可变 类,是它的用途决定的。其实不只 String,很多 Java 标准类库中的类都是不可变的。在开发一个系统的时候,我们 有时候也需要设计不可变类,来传递一组相关的值,这也是面向对象思想的体现。不可变类有一些优点,比如因为它 的对象是只读的,所以多线程并发访问也不会有任何问题。当然也有一些缺点,比如每个不同的状态都要一个对象来 代表,可能会造成性能上的问题。所以 Java 标准类库还提供了一个可变版本,即 StringBuffer。
11.volatile与synchronized的区别
volatile修饰的变量可以实现数据共享.即在不同线程中可以使用同一个变量.但是不确保原子性.不安全.
synchronized是同步代码块,被它包裹的内容是线程安全的,也可以实现变量共享,他可以放在方法上,可以放在类上,也可以单独成为一个代码块.被它包裹的内容,当里面有编程进来的时候,别的线程进不来,必须等待里面的线程出去的时候才能进来,确保了原子性.
12.java如何对异常进行处理
throws 抛出交给调用者处理.
throw new XXXException("XXX"); 抛出自定义异常,可以由全局异常处理器捕获.
try{}catch{}finally 可以自行捕获,try中出错才执行catch.finally中方法不管有误异常的都执行.
13.访问权限修饰符 public、private、protected,默认修饰符的区别
修饰符 | 当前类 | 同包 | 子类 | 其他包 |
---|---|---|---|---|
public | √ | √ | √ | √ |
protected | √ | √ | √ | × |
default | √ | √ | × | × |
private | √ | × | × | × |
14.==与equals的区别
最大的区别就是,一个是运算符,一个是方法.
==如果比较的是基本数据类型,则比较的是值是否相等.如果是引用数据类型,则比较的是地址值是否相等.
equals()用来比较两个对象的内容是否相等.
注意:equals方法不能用于基本数据类型的比较,如果没有equals方法进行重写,则比较的是引用类型的变量所指向的对象的地址.
15.请求转发与重定向的区别
本质区别:转发是服务器行为,重定向是客户端行为.
重定向特点:两次请求,浏览器地址栏改变,可以访问web之外的资源,传输的数据会丢失.
请求转发特点:一次请求,浏览器地址不改变,可以将数据存储在request域中.传输的数据不会丢失.
16.jsp与Servlet的区别
jsp经过编译后就变成了servlet,jsp本质就是servlet,jvm只能识别java的类,不能识别jsp代码.web容器将jsp的代码编译成jvm能够识别的java类.其实就是当你通过http请求一个JSP页面时,首先tomcat会调用service()方法将JSP编译成为Servlet,然后执行Servlet
JSP侧重视图,Servlet主要用于控制逻辑
-
Servlet没有内置对象
-
JSP中的内置对象都是必须通过HTTPServletRequest对象,HttpServletReponse对象以及HttpServet对象得到
-
Servlet生命周期:
-
init:servlet对象创建时,调用此方法
-
service:只要访问Servlet就会进入这个方法
-
destroy:servlet对象销毁时,调用此方法
17.说说你对mvc开发模式的理解,有何优缺点?
MVC是一种框架模式,说到底是一种框架,而不是一种设计模式,框架通常是代码重用,而设计模式是设计重用,而架构则介于两者之间,部分代码重用,部分设计重用,有时分析也可重用
用户通过试图发送请求到控制器,然后访问模型,拿出用户想要的属性,在通过控制器返回给试图.
优点:实现了功能模块和显示模块的分离,同时它还提高了应用系统的可维护性,可扩展性,可移植性和组件的可复用性.
缺点:没有明确的定义,不适合小型规模的应用程序,增加系统结构和实现的复杂性,
视图与控制器间的连接过于紧密.
优点:分层,结构清晰,耦合性低,大型项目代码的复用性得到极大的提高,开发人员分工明确,提高了开发的效率,维护方便,降低了维护成本。
缺点:简单的小型项目,使用MVC设计反而会降低开发效率,层和层虽然相互分离,但是之间关联性太强,没有做到独立的重用。
18.MVC的各个部分都有哪些技术来实现?如何实现?
MVC是Model-View-Controller的简写.
Model代表的是应用的业务逻辑(通过JavaBean,EJB组件实现(EJB是Enterprise Java Beans技术的简称, 又被称为企业Java Beans))
View是应用的表示层(由jsp页面产生)
Controller是提供应用的处理过程控制(一般是一个servlet)
通过这种设计模型把应用逻辑,处理过程和显示逻辑分成不同的组件实现.这些组件可以进行交互和重用.
19.hashmap的数据结构是什么
jdk1.8之前是数组+单向链表
jdk1.8之后是数组+单向链表+树
20.hashmap是如何扩容
它有个加载因子,当长度的阈值到达加载因子的阈值就会自动扩容两倍.
1.8之前因为要是链表过长,就会影响查询效率,所以在1.8之后链表长度超过8就自动转换为红黑树.提高查询效率.链表长度减少到6以内之后才会转回链表.
21.拦截器与过滤器的区别
22.抽象类和接口的区别
抽象类只能单继承,接口可以多实现.
抽象类和接口都不能被实例化,如果要实例化就需要使用多态的方式.
抽象类中可以写构造方法,接口中不能.
接口里面定义的变量只能是公有的静态的变量,抽象类中的变量是普通变量.
23.静态变量与实例变量的区别
静态变量不属于某个实例对象,而是属于类,也叫类变量,只要程序加载了类的字节码,不用创建任何实例对象就会被分配空间,就可以被使用,也就是说,你创建了多个对象,他们共用了一个静态变量,而实例对象是属于自己的独有的,不会被共享!
静态变量是公共的,而实例对象,是自己用自己的!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· winform 绘制太阳,地球,月球 运作规律
· 上周热点回顾(3.3-3.9)