java集合中ArrayList与LinkedList比较
作者 codercjg 在 9 一月 2015, 11:20 下午
ArrayList和LinkedList共同点:都实现了List接口,用来表示动态数组的功能,都是线程不安全的。
不同点:1)实现方式不同,ArrayList内部用数组实现,LinkedList用链表实现。所以表中间插入元素时ArrayList需要移动元素,而LinkedList不需要。2)应用场景不同,ArrayList适合随机查找,而LinkedList适合经常需要插入和删除元素的场景。3)LinkedList比ArrayList多了addFirst(),addLast()方法4)ArrayList支持随机查找,LinkedList不支持
下面通过一个例子来比较两种实现方式的插入和遍历所需要的时间:
import
java.util.ArrayList;
import
java.util.Iterator;
import
java.util.LinkedList;
public
class
ListSample {
public
static
void
main(String[] args) {
int
circle =
200000
;
ArrayList<String> arrayList =
new
ArrayList<String>(circle);
LinkedList<String> linkedList =
new
LinkedList<String>();
System.out.println(
"比较插入在头部元素的时间:"
);
long
time = System.currentTimeMillis();
for
(
int
i =
0
; i < circle; i++) {
arrayList.add(
0
, String.valueOf(i));
}
System.out.println(
"ArrayList:"
- (System.currentTimeMillis() - time));
time = System.currentTimeMillis();
for
(
int
i =
0
; i < circle; i++) {
linkedList.addFirst(String.valueOf(i));
}
System.out.println(
"LinkedList:"
- (System.currentTimeMillis() - time));
System.out.println();
System.out.println(
"比较for循环方式遍历的时间:"
);
time = System.currentTimeMillis();
for
(
int
i =
0
; i < circle; i++) {
arrayList.get(i);
}
System.out.println(
"ArrayList:"
- (System.currentTimeMillis() - time));
time = System.currentTimeMillis();
for
(
int
i =
0
; i < circle; i++) {
linkedList.get(i);
}
System.out.println(
"LinkedList:"
- (System.currentTimeMillis() - time));
System.out.println();
System.out.println(
"比较foreach方式遍历的时间:"
);
time = System.currentTimeMillis();
for
(String str: arrayList){
}
System.out.println(
"ArrayList:"
- (System.currentTimeMillis() - time));
time = System.currentTimeMillis();
for
(String str: linkedList){
}
System.out.println(
"LinkedList:"
- (System.currentTimeMillis() - time));
System.out.println();
System.out.println(
"比较迭代器方式遍历的时间:"
);
time = System.currentTimeMillis();
Iterator it = linkedList.iterator();
while
(it.hasNext()){
it.next();
}
System.out.println(
"ArrayList:"
- (System.currentTimeMillis() - time));
time = System.currentTimeMillis();
it = linkedList.iterator();
while
(it.hasNext()){
it.next();
}
System.out.println(
"LinkedList:"
- (System.currentTimeMillis() - time));
}
}
运行结果:比较插入在头部元素的时间:ArrayList:16829LinkedList:234
比较for循环方式遍历的时间:ArrayList:0LinkedList:110375
比较foreach方式遍历的时间:ArrayList:31LinkedList:16
比较迭代器方式遍历的时间:ArrayList:15LinkedList:16
在ArrayList头部插入元素竟然花了16829ms, 因为ArrayList内部是数组,所以会出现大量移动元素操作.而LinkedList内部是链表,所以很容易就在表头插入新的结点。使用LinkedList for循环遍历方式竟然花了110375ms, 因为LinkedList不支持随机查找,每次都会从表头开始查找,而ArrayList支持随机查找。
结论:ArrayList适用于需要随机查找的场景,LinkedList适用于需要大量插入和删除元素操作的场景。
java集合ArrayList和Vector
作者 codercjg 在 9 一月 2015, 9:38 下午
ArrayList实现了List接口,是最常用的集合类。它的内部是用数组实现,使用时会根据包含的元素数目自动扩容。至于Vector,功能与实现方式都和ArrayList一样的,但效率比ArrayLsit略低,因为Vector是ArrayList的同步版本,它的方法前大部分都加了Sychronized, 是多线程安全的,而ArrayList不是。
ArrayList用法比较简单,可以把它当作容量没有限制的数组来用,但也会有一些容易忽略的小细节。1)创建ArrayList对象时传入一个合适的容量,有助于提高效率,因为默认容量是10,如果超出了就需要扩容,这个比较耗cpu时间2)add(), get(), remove()方法中的索引都不能超过ArrayList.size(), 否则运行时会抛越界异常3)contains()方法判断是否包含某元素是默认调用元素的equals()方法,如果没有则调用元素的基类Object的equals()方法,该方法默认比较两个对象的引用,如果想比较对象的内容,需要在元素中重写equals()方法
下面通过代码来说明:
Product.java
public
class
Product {
private
int
id;
private
String name;
private
int
price;
public
Product(
int
id, String name,
int
price) {
super
();
this
.id = id;
this
.name = name;
this
.price = price;
}
public
int
getId() {
return
id;
}
public
void
setId(
int
id) {
this
.id = id;
}
public
String getName() {
return
name;
}
public
void
setName(String name) {
this
.name = name;
}
public
int
getPrice() {
return
price;
}
public
void
setPrice(
int
price) {
this
.price = price;
}
@Override
public
int
hashCode() {
final
int
prime =
31
;
int
result =
1
;
result = prime * result + id;
result = prime * result + ((name ==
null
) ?
0
: name.hashCode());
result = prime * result + price;
return
result;
}
@Override
public
boolean
equals(Object obj) {
if
(
this
== obj)
return
true
;
if
(obj ==
null
)
return
false
;
if
(getClass() != obj.getClass())
return
false
;
Product other = (Product) obj;
if
(id != other.id)
return
false
;
if
(name ==
null
) {
if
(other.name !=
null
)
return
false
;
}
else
if
(!name.equals(other.name))
return
false
;
if
(price != other.price)
return
false
;
return
true
;
}
@Override
public
String toString() {
return
"Product [id="
- id +
", name=" - name +
", price=" - price +
"]"
;
}
}
ArrayListSample.java
import
java.util.ArrayList;
import
java.util.Enumeration;
import
java.util.Iterator;
import
java.util.List;
import
java.util.Vector;
public
class
ArrayListSample {
public
static
void
main(String[] args) {
Product p1 =
new
Product(
1
,
"java"
,
40
);
Product q1 =
new
Product(
1
,
"java"
,
40
);
Product p2 =
new
Product(
2
,
"C++"
,
50
);
Product p3 =
new
Product(
3
,
"php"
,
60
);
Product p4 =
new
Product(
4
,
"pthyon"
,
70
);
Product q4 =
new
Product(
4
,
"pthyon"
,
70
);
//1. ArrayList内部是用数组实现的,容量默认为10,当容量不够时会扩容,并把原来数组中内容
// 拷贝到新创建的数组中,出于效率考虑,创建ArrayList对象时尽量给它设置一个合适的容量
List<Product> list =
new
ArrayList<Product>();
list.add(p1);
list.add(p2);
list.add(p3);
list.add(
2
, p4);
list.add(
2
, q4);
// 可插入元素
// 2. add() get() remove()方法中的索引不能超过list.size(),否则会抛出越界异常
// list.add(10, p4);
// list.remove(10);
// System.out.println(list.get(100));
// 3. 打印ArrayList对象时,会调用ArrayList的toString()方法,在该方法内部会调用
// Product的toString()方法,如果Product没有覆盖Object的toString()方法,那么
// 会调用基类Object的toString()方法,将打印类似Product@77158a类名@对象hashCode的信息
System.out.println(list);
// 4. 调用contains()方法判断是否包含某个对象时,contains()会调用equals()方法进行判断,
// 而Object类中quals()方法默认是比较对象的引用,如果想根据内容判断,可重新Product的equals()
// 和hashCode()方法
System.out.println(list.contains(p1));
// true
System.out.println(list.contains(q1));
// 重写equals()和hashCode()方法前为false, 重写后为true
// ArrayList的三种遍历方法
System.out.println(
"for循环遍历"
);
int
size = list.size();
for
(
int
i=
0
; i<size; i++){
System.out.println(list.get(i));
}
System.out.println(
"foreach遍历"
);
for
(Product p:list){
System.out.println(p);
}
System.out.println(
"迭代器遍历"
);
Iterator<Product> it = list.iterator();
while
(it.hasNext()){
System.out.println(it.next());
}
//实际测得,for循环方式遍历最快,然后是迭代器方式,最后是foreach方式
Vector <Product> v =
new
Vector<Product>();
v.add(p1);
v.add(p2);
v.add(p3);
v.add(
2
, p4);
v.add(
2
, q4);
// Vector除了可用ArrayList的三种方式遍历外,还可用下面的Enumeration方式
System.out.println(
"Enumeration遍历"
);
Enumeration<Product> s= v.elements();
while
(s.hasMoreElements()){
System.out.println(s.nextElement());
}
}
}
执行结果:[Product [id=1, name=java, price=40], Product [id=2, name=C++, price=50], Product [id=4, name=pthyon, price=70], Product [id=4, name=pthyon, price=70], Product [id=3, name=php, price=60]]truetruefor循环遍历Product [id=1, name=java, price=40]Product [id=2, name=C++, price=50]Product [id=4, name=pthyon, price=70]Product [id=4, name=pthyon, price=70]Product [id=3, name=php, price=60]foreach遍历Product [id=1, name=java, price=40]Product [id=2, name=C++, price=50]Product [id=4, name=pthyon, price=70]Product [id=4, name=pthyon, price=70]Product [id=3, name=php, price=60]迭代器遍历Product [id=1, name=java, price=40]Product [id=2, name=C++, price=50]Product [id=4, name=pthyon, price=70]Product [id=4, name=pthyon, price=70]Product [id=3, name=php, price=60]Enumeration遍历Product [id=1, name=java, price=40]Product [id=2, name=C++, price=50]Product [id=4, name=pthyon, price=70]Product [id=4, name=pthyon, price=70]Product [id=3, name=php, price=60]
java中的static与final
作者 codercjg 在 8 一月 2015, 11:34 上午
static变量:非static变量告诉编译器每创建一个对象都为该变量分配一份存储空间。而static变量是告诉编译器为该变量只分配一份存储空间,和创建多少对象没关系,甚至根本不需要创建对象。所以无论是类static成员变量还是方法中的static局部变量都只有一份存储空间,它的值会累积。
static方法:调用非static方法时编译器会把一个表示当前对象的this句柄作为参数传入该方法,而static修饰方法时是告诉编译器不要传入参数this句柄,所以static方法中不能调用非静态方法和引用类中的非静态成员变量,因为他们需要传入this。
static内部类:内部类默认需要持有创建它的外部类对象的this句柄,所以创建内部类对象前需要创建一个外部类对象。在内部类前加了static关键字后,就是告诉编译器不需要这个句柄,所以创建该static内部类对象前不需要创建一个外部内对象。因为没有外部内对象的this句柄,所以也不能使用与外部类对象相关的变量和方法。
总结:static 变量作用:告诉编译器只为该变量分配一份存储空间,和有多个对象没关系,即使没有对象也会分配一份存储空间
static方法作用是:告诉编译器调用该方法时不需要传入this参数,那么自然不用创建对象了,当然在它里面也不能使用与对象关联的类成员变量,和需要传入this参数的非static方法了。
static内部类作用:创建内部类对象时,不需要创建外部内对象。
final修饰变量:告诉编译器该变量只能被赋一次初值(编译期或运行期都可以),之后它的值不能被改变,否则编译器提示出错。编译器会保证final变量有初值,未指定初值需在构造函数中指定。
public
class
Test{
public
static
final
int
A=
1
;
//告诉编译器只分配一份存储空间,且A不能被修改。
//private final int a; // 没有初值
private
final
int
a_1;
// 构造函数中赋初值
private
final
int
b =
1
;
private
int
c =
1
;
private
final
int
d = ++c;
//运行时赋赋初值
public
Test(){
a_1 =
1
;
}
public
void
change(
final
int
arg1){
//arg1 = 1; // 不能修改
//b = 1; // 不能修改
}
public
static
void
main(String[] args){
Test test =
new
Test();
test.change(
1
);
}
}
上面的注释掉的代码行编译时会出错。
final方法:1.出于设计的考虑把方法锁定,告诉编译器该方法不能被继承它的类修改它的含义,所以不能被覆盖2.早期jdk版本出于效率的考虑,用于告诉编译器把该方法作为一个内联函数,在调用时用代码展开,省去通过压栈传入参数后跳转到指定代码执行,然后跳回并清理栈中的参数的开销。但j2se5/6之后jvm会自动处理效率问题。所以除非明确禁止覆盖时,才设置方法为final的。类中所有的private方法都隐式指定为final的,由于无法取用private方法,所以也就无法覆盖它。
final类:告诉编译器该类不能被修改,不允许被继承
String类的实现和陷阱
作者 codercjg 在 8 一月 2015, 12:31 上午
String中的数据结构:
public
final
class
String
implements
java.io.Serializable, Comparable<String>, CharSequence
{
/** The value is used for character storage. */
private
final
char
value[];
/** The offset is the first index of the storage that is used. */
private
final
int
offset;
/** The count is the number of characters in the String. */
private
final
int
count;
}
从String源码中可知String对象用字符数组value存储字符串常量,有效内容为value[offset~count-1];String中每一个看似会改变其内容的方法其实是返回了一个新new的String对象,字符串则存在String对象中新new的字符数组里。
当两个String对象拥有相同的值时,会引用字符串常量池中的同一个拷贝,如下所示
String str1=
"abc"
;
String str2=
"abc"
;
String str3=
new
String(
"abc"
);
System.out.println(str1==str2);
// true
System.out.println(str1==str3);
// false;
System.out.println(str1==str3.intern());
// true
因为”abc”是一个String对象,str1和str2都指向String对象”abc”;而String3指向了另一个新new的对象,而这个对象的字符数组指向{‘a’,'b’,'c’}
String中的方法:String.toString():返回字符串一个打印对象地址的陷阱:
public
class
A{
public
String toString(){
return
""
this
;
}
}
调用类A中的toString()方法时会得到一个异常,因为”"+this时,编译器会调用toString()方法,这样就构成了一个没有出口的递归。如果要打印对象的地址,应该调用Object.toString(), super.toString().
如果经常要进行类似str1+”sss”+”str2″这样的操作,应该选用StringBuilder.append()方法。至于StringBuffer, 和StringBuilder功能类似,只是用于多线程的同步版本。如果String经常要用findString()查找的话,那么可以选择StringTokenizer,这个效率更高。
gravity layout_gravity和layout_weight
作者 codercjg 在 6 一月 2015, 4:56 下午
linearlayout中三个参数比较搞啊gravity:view中文字的对齐方式layout_gravity:view在linearlayout中的位置layout_weight:线性布局所占宽度减去所有子控件layout_width(orition=horizontal)或者layout_height(orition=vertical)后剩余空间所占的比例,默认为0如下面的例子:剩余空间=父控件宽度-三个TextView宽度其中父控件为线性布局,占满整个屏幕。设屏幕宽度为W所以宽度W,三个TextView的宽度为W1,W2,W3, 剩余宽度为SW,则SW= W-W1-W2-W3又因为W1=W2=W3=0所以剩余空间SW=W屏幕的宽度其中三个TextView的layout_weight比例关系为1:1:1所以他们的宽度都为1/3屏幕宽度
<?
xml
version
=
"1.0"
encoding
=
"utf-8"
?>
<
LinearLayout
xmlns:android
=
"http://schemas.android.com/apk/res/android"
android:layout_width
"match_parent"
android:layout_height
"match_parent"
android:orientation
"horizontal"
<
TextView
android:layout_width
"0dp"
android:layout_height
"wrap_content"
android:layout_weight
"1"
android:text
"1"
android:background
"#FF0000"
/>
<
TextView
android:layout_width
"0dp"
android:layout_height
"wrap_content"
android:layout_weight
"1"
android:text
"1"
android:background
"#00FF00"
android:gravity
"center"
/>
<
TextView
android:layout_width
"0dp"
android:layout_height
"wrap_content"
android:layout_weight
"1"
android:text
"1"
android:background
"#0000FF"
android:layout_gravity
"center"
/>
</
LinearLayout