澳门新葡萄京娱乐场 1

澳门新葡萄京娱乐场Java基础

1、 为什么两个(1927年)时间相减得到一个奇怪的结果?

(3623个赞)

如果执行下面的程序,程序解析两个间隔1秒的日期字符串并比较:

public static void main(String[] args) throws ParseException {
    SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
    String str3 = "1927-12-31 23:54:07";  
    String str4 = "1927-12-31 23:54:08";  
    Date sDt3 = sf.parse(str3);  
    Date sDt4 = sf.parse(str4);  
    long ld3 = sDt3.getTime() /1000;  
    long ld4 = sDt4.getTime() /1000;
    System.out.println(ld4-ld3);
}

输出是:

353

为什么 ld4-lD3
不是1(因为我希望这两个时间差是一秒),而是353?

如果将日期字符串各加一秒:

String str3 = "1927-12-31 23:54:08";  
String str4 = "1927-12-31 23:54:09";

ld4-ld3 的结果是1.

sun.util.calendar.ZoneInfo[id="Asia/Shanghai",
offset=28800000,dstSavings=0,
useDaylight=false,
transitions=19,
lastRule=null]

Locale(Locale.getDefault()): zh_CN

解决方案

这是上海时区,在12月31日有一个变化。

查阅这个网址来了解上海在1927年时区变化的细节。基本上在1927年年底的午夜,始终会回拨5分52秒。所以“1927-12-31
23:54:08”实际上发生了两次,看起来Java解析了后一次的时间作为当地的日期和时间导致了差异。

1. ArrayList、LinkedList、Vector的区别

答:

澳门新葡萄京娱乐场 1

集合关系

2、Java是“引用传递”还是“值传递”?

(2480个赞)

我一直认为Java是引用传递;然而,我看了一堆博客(例如这篇)声称不是这样的。我认为我没有理解它们之间的区别。

给个解释?

解决方案

Java一直是值传递。不幸的是,他们决定把指针叫做引用,因此新人总是被搞晕。因为这些引用也是通过值传递的。

相同点:

ArraList、LinkedList、Vector 都实现了 List 接口。

3、一个关于Java += 操作符的问题

(2223赞)

直到今天我认为这个例子:

i += j;

只是一个简写的:

i = i + j;

但如果这样做:

int i = 5;
long j = 8;

然而 i = i + j; 没法编译,而 i += j; 就可以编译。

这意味着i += j; 实际上是i = (type of i) (i + j)的简写么?

解决方案

总有人问这类问题,JLS里有答案。参见 §15.26.2复合赋值运算符。摘录:

E1 op= E2 型的复合赋值表达式等价于 E1 = (T)((E1) op (E2)),这里 T 是
E1 的类型,不同的是 E1 只计算一次。

一个例子,引自 §15.26.2

[…] 下面的代码是正确的:

short x = 3;
x += 4.6;

x的结果等于7,因为它等价于:

short x = 3;
x = (short)(x + 4.6);

换句话说,你的假设是正确的。

不同点:

(1) ArrayList 底层是用数组实现的,可以认为 ArrayList
是一个可改变大小的数组。
当越来越多的元素添加到 ArrayList 的时候,它的大小会动态增长。
由于 ArrayList 本质上是一个数组,所以它的元素可以直接通过 get 和 set
方法来访问。
(2) LinkedList
底层是用双向链表实现的。所以,在添加和删除操作上,性能比
ArrayList 好;在查询和修改操作上,性能比 ArrayList 差。
(3) Vector 和 ArrayList 几乎是一样的,区别在于 Vector
线程安全的。因此,性能上较 ArrayList
要差。另一个区别是扩容策略不一样:当越来越多的元素添加进来需要更大空间的时候,Vector
的大小增长为原来的两倍,而 ArrayList 的大小增长原来的50%

4、HashMap 和 Hashtable 之间的不同?

(1769个赞)

Java中 HashMap 和 Hashtable的不同是什么?

非多线程应用中使用哪个更有效率?

解决方案

Java 中 HashMap 和 HashTable 有几个不同点:

  1. Hashtable 是同步的,然而 HashMap不是。
    这使得HashMap更适合非多线程应用,因为非同步对象通常执行效率优于同步对象。
  2. Hashtable 不允许 null 值和键。HashMap允许有一个 null 键和人一个 NULL
    值。
  3. HashMap的一个子类是LinkedHashMap。所以,如果想预知迭代顺序(默认的插入顺序),只需将HashMap转换成一个LinkedHashMap。用Hashtable就不会这么简单。

因为同步对你来说不是个问题,我推荐使用HashMap。如果同步成为问题,你可能还要看看ConcurrentHashMap。

总结:
  • ArrayList —— 随机访问(get/set)
  • LinkedList —— 插入和删除(add/remove)
  • Vector —— 线程安全(synchronized)

5、(如何) 读取或者把一个 InputStream 转成一个 String

(1724个赞)

如果你有一个 java.io.InputStream 对象,如处理这个对象并生成一个字符串?

假定我有一个 InputStream 对象,它包含文本数据,我希望将它转化成一个字符串(例如,这样我可以将流的内容写到一个log文件中)。

InputStream 转化成 String 最简单方法是什么?

解决方案

使用 Apache
commons IOUtils库来拷贝InputStream到StringWriter是一种不错的方式,类似这样:

StringWriter writer = new StringWriter();
IOUtils.copy(inputStream, writer, encoding);
String theString = writer.toString();

甚至

// NB: does not close inputStream, you can use IOUtils.closeQuietly for that
// 注意:不关闭inputStream,你可以使用 IOUtils.closeQuietly
String theString = IOUtils.toString(inputStream, encoding);

或者,如果不想混合Stream和Writer,可以使用 ByteArrayOutputStream。

2. Sring、StringBuffer、StringBuilder的区别

答:

String 字符串常量
StringBuffer 字符串变量(线程安全)
StringBuilder 字符串变量(非线程安全)

String 类型和 StringBuffer 类型的主要性能区别在于:

  • String 是不可变的对象,因此在每次对 String
    类型进行改变的时候其实都等同于生成一个新的 String
    对象,然后将指针指向新的 String 对象

    所以经常改变内容的字符串最好不要用 String
    ,因为每次生成对象都会对系统性能产生影响,特别当内存中无引用对象多了以后,
    JVM 的 GC 就会开始工作,那速度是一定会变慢的。
  • 而如果是使用 StringBuffer 类则结果就不一样了,每次结果都会
    StringBuffer
    对象本身进行操作
    ,而不是生成新的对象,再改变对象引用。
    所以在一般情况下我们推荐使用 StringBuffer
    ,特别是字符串对象经常改变的情况下。而在某些特别情况下, String
    对象的字符串拼接其实是被 JVM 解释成了 StringBuffer
    对象的拼接,所以这些时候 String 对象的速度并不会比 StringBuffer
    对象慢。

6、为什么Java中的密码优先使用 char[] 而不是String?

(1574个赞)

在Swing中,密码字段有一个getPassword()(返回
char数组)方法而不是通常的getText()(返回String)方法。同样的,我遇到过一个建议,不要使用
String 来处理密码。

为什么String涉及到密码时,它就成了一个安全威胁?感觉使用char数组不太方便。

解决方案

String是不可变的。这意味着一旦创建了字符串,如果另一个进程可以进行内存转储,在GC发生前,(除了反射)没有方法可以清除字符串数据。

使用数组操作完之后,可以显式地清除数据:可以给数组赋任何值,密码也不会存在系统中,甚至垃圾回收之前也是如此。

所以,是的,这是一个安全问题 –
但是即使使用了char数组,仅仅缩小了了攻击者有机会获得密码的窗口,它值针对制定的攻击类型。

在大部分情况下 StringBuffer > String

7、遍历HashMap的最佳方法

(1504个赞)

遍历HashMap中元素的最佳方法是什么?

解决方案

这样遍历entrySet:

public static void printMap(Map mp) {
    Iterator it = mp.entrySet().iterator();
    while (it.hasNext()) {
        Map.Entry pair = (Map.Entry)it.next();
        System.out.println(pair.getKey() + " = " + pair.getValue());
        it.remove(); // avoids a ConcurrentModificationException
    }
}

更多请查阅Map。

StringBuffer

java.lang.StringBuffer 是线程安全的可变字符序列。一个类似于 String
的字符串缓冲区,但不能修改。虽然在任意时间点上它都包含某种特定的字符序列,但通过调用某些方法可以改变该序列的长度和内容。
可将字符串缓冲区安全地用于多个线程。可以在必要时对这些方法进行同步,因此任意特定实例上的所有操作就好像是以串行顺序发生的,该顺序与所涉及的每个线程进行的方法调用顺序一致。
StringBuffer 的主要操作是 append 和 insert
方法,可重载这些方法,以接受任意类型的数据。
每个方法都能有效地将给定的数据转换成字符串,然后将该字符串的字符追加或插入到字符串缓冲区中。
append 方法始终将这些字符添加到缓冲区的末端;而 insert
方法则在指定的点添加字符。

8、(如何)从数组创建ArrayList

(1468个赞)

我有一个数组,初始化如下:

Element[] array = {new Element(1), new Element(2), new Element(3)};

我希望将这个数组转化成一个ArrayList类的对象。

解决方案

new ArrayList<Element>(Arrays.asList(array))
在大部分情况下 StringBuilder > StringBuffer

9、产生一个Java的内存泄露

(1478个赞)

我有过一个面试,被问到如何产生一个Java内存泄露。不用说,我感到相当傻,甚至如何产生一个的线索都没有。

那么怎么才能产生一个内存泄露呢?

解决方案

在纯Java中,有一个很好的方式可以产生真正的内存泄露(通过执行代码使对象不可访问但仍存在于内存中):

  1. 应用产生一个长时间运行的线程(或者使用一个线程池加速泄露)。
  2. 线程通过一个(可选的自定义)类加载器加载一个类。
  3. 该类分配大内存(例如,new
    byte[1000000]),赋值给一个强引用存储在静态字段中,再将它自身的引用存储到ThreadLocal中。分配额外的内存是可选的(泄露类实例就够了),但是这样将加速泄露工作。
  4. 线程清除所有自定义类的或者类加载器载入的引用。
  5. 重复上面步骤。

这样是有效的,因为ThreadLocal持有对象的引用,对象持有类的引用,接着类持有类加载器的引用。反过来,类加载器持有所有已加载类的引用。这会使泄露变得更加严重,因为很多JVM实现的类和类加载都直接从持久带(permgen)分配内存,因而不会被GC回收。

StringBuilder

java.lang.StringBuilder 一个可变的字符序列是5.0新增的。此类提供一个与
StringBuffer 兼容的 API,但不保证同步。该类被设计用作 StringBuffer
的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。如果可能,建议优先采用该类,因为在大多数实现中,它比
StringBuffer 要快。两者的方法基本相同。

10、使用Java在一个区间内产生随机整数数

(1422个赞)
我试着使用Java生成一个随机整数,但是随机被指定在一个范围里。例如,整数范围是5~10,就是说5是最小的随机值,10是最大的。5到10之间的书也可以是生成的随机数。

解决方案

标准的解决方式(Java1.7 之前)如下:

import java.util.Random;

public static int randInt(int min, int max) {

    Random rand;
    int randomNum = rand.nextInt((max - min) + 1) + min;

    return randomNum;
}

请查看相关的JavaDoc。在实践中,java.util.Random 类总是优于 java.lang.Math.random()。

特别是当标准库里有一个直接的API来完成这个工作,就没有必要重复制造轮子了。

总结:
  • 如果要操作少量的数据 —— String
  • 单线程操作大量数据 —— StringBuilder
  • 多线程操作大量数据 —— StringBuffer

3. HashMap与Hashtable

相同点
HashMap
  • HashMap 是基于哈希表实现的,每一个元素是一个 key-value
    对,其内部通过单链表解决冲突问题,容量不足(超过了阀值)时,同样会自动增长。
  • HashMap
    非线程安全的,只是用于单线程环境下,多线程环境下可以采用
    concurrent 并发包下的 ConcurrentHashMap
  • HashMap 实现了 Serializable 接口,因此它支持序列化,实现了Cloneable
    接口,能被克隆。
HashMap 存数据的过程是:

HashMap 内部维护了一个存储数据的 Entry 数组,HashMap
采用链表解决冲突,每一个 Entry 本质上是一个单向链表。当准备添加一个
key-value
对时,首先通过 hash(key) 方法计算 hash 值,然后通过
indexFor(hash,length) 求该 key-value 对的存储位置,计算方法是先用
hash&0x7FFFFFFF 后,再对 length 取模,这就保证每一个 key-value
对都能存入 HashMap
中,当计算出的位置相同时,由于存入位置是一个链表,则把这个 key-value
对插入链表头。

Hashtable
  • Hashtable 同样是基于哈希表实现的,同样每个元素是一个 key-value
    对,其内部也是通过单链表解决冲突问题,容量不足(超过了阀值)时,同样会自动增长。
  • Hashtable 是 JDK1.0 引入的类,是线程安全的,能用于多线程环境中。
  • Hashtable同样实现了Serializable接口,它支持序列化,实现了Cloneable接口,能被克隆。
HashMap与Hashtable的区别
(1) 继承的父类不同

Hashtable 继承自 Dictionary 类,而 HashMap 继承自 AbstractMap
类。但二者都实现了 Map 接口。

(2) 线程安全性不同

Hashtable 中的方法是 synchronized 的,而 HashMap
中的方法在默认情况下是非 synchronized
的。在多线程并发的环境下,可以直接使用Hashtable,不需要自己为它的方法实现同步,但使用
HashMap 时就必须要自己增加同步处理。

(3) 是否提供contains方法
  • HashMap 把 Hashtable 的 contains 方法去掉了,改成 containsValue 和
    containsKey,因为 contains 方法容易让人引起误解。
  • Hashtable 则保留了 contains,containsValue 和 containsKey
    三个方法,其中 contains 和 containsValue 功能相同。
(4) key和value是否允许null值
  • Hashtable 中,key 和 value 都不允许出现 null 值。但是如果在
    Hashtable 中有类似 put(null,null) 的操作,编译同样可以通过,因为 key
    和 value 都是 Object 类型,但运行时会抛出 NullPointerException
    异常,这是 JDK 的规范规定的。
  • HashMap 中,null
    可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为
    null
    。当 get() 方法返回 null 值时,可能是 HashMap
    中没有该键,也可能使该键所对应的值为 null。因此,在 HashMap 中不能由
    get() 方法来判断 HashMap 中是否存在某个键, 而应该用 containsKey()
    方法来判断。
(5) 两个遍历方式的内部实现上不同

Hashtable、HashMap都使用了 Iterator。而由于历史原因,Hashtable 还使用了
Enumeration 的方式 。

(6) hash值不同

哈希值的使用不同,Hashtable 直接使用对象的 hashCode。而 HashMap
重新计算 hash 值。
hashCode 是 jdk 根据对象的地址或者字符串或者数字算出来的 int
类型的数值。
Hashtable 计算 hash 值,直接用 key 的 hashCode(),而 HashMap 重新计算了
key 的 hash 值,Hashtable 在求 hash 值对应的位置索引时,用取模运算,而
HashMap 在求位置索引时,则用与运算,且这里一般先用hash&0x7FFFFFFF
后,再对 length 取模,&0x7FFFFFFF 的目的是为了将负的 hash
值转化为正值,因为 hash 值有可能为负数,而
&0x7FFFFFFF 后,只有符号外改变,而后面的位都不变。

(7) 内部实现使用的数组初始化和扩容方式不同
  • HashTable在不指定容量的情况下的默认容量为11,而HashMap为16,Hashtable
    不要求底层数组的容量一定要为2的整数次幂,而 HashMap
    则要求一定为2的整数次幂。
  • Hashtable 扩容时,将容量变为原来的2倍加1,而 HashMap
    扩容时,将容量变为原来的2倍。
  • Hashtable 和 HashMap
    它们两个内部实现方式的数组的初始大小和扩容的方式。HashTable 中 hash
    数组默认大小是11,增加的方式是 old*2+1。

4. Java的四种引用

(1) 强引用(StrongReference)

强引用在程序代码之中普遍存在的,比如下面这段代码中的object和str都是强引用:

Object object = new Object();
String str = "hello";

只要某个对象有强引用与之关联,JVM
必定不会回收这个对象
,即使在内存不足的情况下,JVM 宁愿抛出 OutOfMemory
错误也不会回收这种对象。
如果想中断强引用和某个对象之间的关联,可以显示地将引用赋值为null,这样一来的话,JVM在合适的时间就会回收该对象。

(2) 软引用(SoftReference)

软引用是用来描述一些有用但并不是必需的对象,在 Java 中用
java.lang.ref.SoftReference
类来表示。对于软引用关联着的对象,只有在内存不足的时候 JVM
才会回收该对象
。因此,这一点可以很好地用来解决 OOM
的问题,并且这个特性很适合用来实现缓存:比如网页缓存、图片缓存等。
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被
JVM 回收,这个软引用就会被加入到与之关联的引用队列中。

(3) 弱引用(WeakReference)

弱引用也是用来描述非必需对象的,当 JVM
进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象
。在 java
中,用
java.lang.ref.WeakReference 类来表示。

(4) 虚引用(PhantomReference)

虚引用和前面的软引用、弱引用不同,它并不影响对象的生命周期。在
java 中用 java.lang.ref.PhantomReference
类表示。如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收

进一步理解软引用和弱引用

对于强引用,我们平时在编写代码时经常会用到。而对于其他三种类型的引用,使用得最多的就是软引用和弱引用,这2种既有相似之处又有区别。
它们都是用来描述非必需对象的,但是被软引用关联的对象只有在内存不足时才会被回收,而被弱引用关联的对象在JVM进行垃圾回收时总会被回收
针对上面的特性,软引用适合用来进行缓存,当内存不够时能让JVM回收内存,弱引用能用来在回调函数中防止内存泄露。因为回调函数往往是匿名内部类,隐式保存有对外部类的引用,所以如果回调函数是在另一个线程里面被回调,而这时如果需要回收外部类,那么就会内存泄露,因为匿名内部类保存有对外部类的强引用。

5. 关键字synchronized、volatile、transient

(1) synchronized关键字

synchronized
关键字用于多线程访问程序中的共享资源时实现顺序同步访问资源
。可以修饰方法或者代码块。而且关键字
synchronized 取得的锁都是对象锁

注意:
什么叫对象锁呢,就是一个对象产生一把锁,如果多个线程调用一个对象的多个方法,这些方法都被
synchronized
修饰,那么这些线程共同竞争一把锁,最后表现的就是同步顺序执行各个被
synchronized 修饰的方法。

(1.1) 同步方法

使用 synchronized
修饰的方法是同步方法,多个线程调用同一个对象的同步方法时会顺序同步执行。

(1.1.1) synchronized锁重入

当一个线程执行 synchronized
关键字修饰的方法的时候,其他线程是不可以访问该对象中 synchronized
关键字修饰的方法的,因为唯一的一把锁已经被当前线程使用了。但是如果当前线程在
synchronized 方法/块的内部调用本类或者其父类的其他 synchronized
方法/块
,是永远可以得到锁的,这就叫做锁重入

(1.1.2) 出现异常,同步锁自动释放

当线程执行 synchronized
修饰的代码出现异常时,其所持有的锁会自动释放,从而其他线程可以再次争夺锁的使用权而非一直等待造成死锁。

(1.1.3) 同步不具有继承性

如果父类被 synchronized
关键字修饰,那么线程执行父类代码会同步,但是同步并不会继承给其子类,调用子类的方法仍然是异步的。

(1.2) 同步代码块

一般来说,一个方法处理的内容很多,如果 synchronized
修饰以后,其他同步方法就必须等待其执行完毕才可以继续执行
,如果该方法需要较长时间处理,这就明显会降低效率,失去了多线程的意义,所以我们可以考虑将同步的范围缩小,即从同步一个方法缩小为同步一段代码块,这就是同步代码块产生的原因。
同步代码块的语法是:

synchronized (this) {} 
synchronized (object) {}

synchronized(this )我称之为 this
同步代码块,针对的是当前对象;synchronized(object) 我称之为非 this
同步代码块,针对的是 object 对象。

注意:
不论是同步方法还是同步代码块,实质上都是争夺锁的问题,而锁一定是对象级的,即一个对象只会产生一个锁,所以只要有一个线程在执行
synchronized
修饰的东西(不论是方法还是代码块),那么其他线程都无法访问被
synchronized 修饰的方法或代码块。
但是注意使用非this同步代码块的时候,里面的 object 不要用 String
类型的,因为大家都知道 JVM 具有 String 常量池缓存的功能,所以使用 String
类型可能产生问题。

(1.3) 静态同步synchronized方法和synchronized(class)代码块

关键字 synchronized 还可以应用在 static 静态方法上,或者
synchronized(class) 代码块。如果这样写,产生的是类级别的锁,也就是给
*.java
这个类加锁,而非给某个对象加锁。这就意味着线程执行同一个类的不同对象的静态同步
synchronized 方法和synchronized(class) 代码块时,都会同步执行。

(2) volatile关键字

Java语言提供了一种稍弱的同步机制,即 volatile
变量,用来确保将变量的更新操作通知到其他线程
。当把变量声明为 volatile
类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。volatile
变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取 volatile
类型的变量时总会返回最新写入的值

在访问 volatile 变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此
volatile 变量是一种比 sychronized 关键字更轻量级的同步机制

当对非 volatile 变量进行读写的时候,每个线程先从内存拷贝变量到 CPU
缓存中。如果计算机有多个 CPU,每个线程可能在不同的 CPU
上被处理,这意味着每个线程可以拷贝到不同的 CPU cache 中。而声明变量是
volatile 的,JVM 保证了每次读变量都从内存中读,跳过 CPU cache 这一步。

当一个变量定义为 volatile 之后,将具备两种特性:
  • 保证此变量对所有的线程的可见性(可见性,是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的。也就是一个线程修改的结果,另一个线程马上就能看到),当一个线程修改了这个变量的值,volatile
    保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。但普通变量做不到这点,普通变量的值在线程间传递均需要通过主内存来完成。
  • 禁止指令重排序优化。有 volatile 修饰的变量,赋值后多执行了一个“load
    addl $0x0,
    (%esp)”操作,这个操作相当于一个内存屏障(指令重排序时不能把后面的指令重排序到内存屏障之前的位置),只有一个
    CPU 访问内存时,并不需要内存屏障;(什么是指令重排序:是指 CPU
    采用了允许将多条指令不按程序规定的顺序分开发送给各相应电路单元处理)。
volatile 性能:

volatile
的读性能消耗与普通变量几乎相同,但是写操作稍慢,因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行。

总结:

总体来说,volatile 是并发编程中的一种优化,在某些场景下可以代替
synchronized。但是,volatile 的不能完全取代 synchronized
的位置,只有在一些特殊的场景下,才能适用
volatile。总的来说,必须同时满足下面两个条件才能保证在并发环境的线程安全:

  • 对变量的写操作不依赖于当前值
  • 该变量没有包含在具有其他变量的不变式中
(3) transient关键字

transient 关键字为我们提供了便利,你只需要实现 Serializable
接口,将不需要序列化的属性前添加关键字
transient
,序列化对象的时候,这个属性就不会序列化到指定的目的地中。

小结:
  • 一旦变量被 transient
    修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问。
  • transient
    关键字只能修饰变量,而不能修饰方法和类。注意,本地变量是不能被
    transient 关键字修饰的
  • 被 transient 关键字修饰的变量不再能被序列化,一个静态变量不管是否被
    transient 修饰,均不能被序列化。

6. foreach与for循环效率对比

(1) 对于数组而言,for 和 foreach 遍历效率相差不大。
(2) 对于集合而言
  • 对大多数的集合,foreach 比起传统的 for
    循环稍有性能优势但差别不大,因为它对索引的边界值只计算一次。而在对多个集合进行嵌套式迭代时优势会更明显。
  • 循环ArrayList时,普通for循环比foreach循环花费的时间要少一点;循环LinkList时,普通for循环比foreach循环花费的时间要多很多。当我将循环次数提升到一百万次的时候,循环ArrayList,普通for循环还是比foreach要快一点;但是普通for循环在循环LinkList时,程序直接卡死。
(3) Effective Java 中建议,一般情况下使用 foreach 进行循环,因为其在简洁性和预防 Bug 方面有着传统 for 循环无法比拟的优势,并且没有性能损失。但是除了以下三种情况:
  • 过滤:如果需要遍历集合,并删除选定的元素,就需要使用显式的迭代器,以便可以调用它的remove方法。
  • 转换:如果需要遍历列表或数组,并取代它部分或者全部的元素值,就需要列表迭代器ListIterator或者数组索引,以便设定元素的值。(如果直接更改它引用对象的值的话,也可以使用Iterator,前提是符合按引用传递的原则,Iterator的元素为基本数据类型就不会按引用传递,或者它们的包装类,因为是不可变类,也不符合要求。)
  • 平行迭代:如果需要并行地遍历多个集合,就需要显式的控制迭代器或者索引变量,以便所有迭代器或者索引变量都可以得到同步前移。
建议:
  • 需要循环数组结构的数据时,建议使用普通 for 循环,因为 for
    循环采用下标访问,对于数组结构的数据来说,采用下标访问比较好。
  • 需要循环链表结构的数据时,一定不要使用普通for循环,这种做法很糟糕,数据量大的时候有可能会导致系统崩溃。
    原因:foreach 使用的是迭代器
    可以下标访问时,使用 for,不能下标访问,需要指针访问时,使用
    foreach。

7. Java是按值传递还是按引用传递?

(1) 什么是按值传递

指的是在方法调用时,传递给参数的是值的拷贝。
按值传递的重要特点:传递的是值的拷贝,也就是说传递后就互不相关了。

(2) 什么是按引用传递

指的是在方法调用时,传递给参数的是引用的地址,也就是变量所对应的内存空间的地址。
按引用传递的重要特点:传递的是引用的地址,也就是说传递前和传递后都指向同一个引用(也就是同一个内存空间)。

总结:

一般情况下,在数据做为参数传递的时候,基本数据类型是值传递,引用数据类型是引用传递(地址传递)。

特例:
String对象做为参数传递时,走的依然是引用传递,只不过String这个类比较特殊。
String对象一旦创建,内容不可更改。每一次内容的更改都是重现创建出来的新对象

参考JAVA中值传递和引用传递的三种情况

发表评论

电子邮件地址不会被公开。 必填项已用*标注