数组
数组与其他种类的容器之间的区别有三方面:效率、类型和保存基本类型的能力。在java中,数组是一种效率最高的存储和随机访问对象引用序列的方式。数组就是一个简单的线性序列,这使得元素访问非常快速,但也损失了其他一些特性。当创建了一个数组对象(将数组本身作为对象看待),数组的大小就被固定了,并且这个数组的生命周期也是不可改变的。通常用移到新数组中。这正式ArrayList类的行为方式。然而这种弹性带来的开销,使得ArrayList比数组效率低得多。
在C++中,容器类vector的确知道自己保存的对象是何类型。不过与java中的数组相比,它有一个缺点:C++中vector的操作符[]不做边界检查,所以可能会越界操作(然而,要问vector有多大是可能的,at()方法的确会执行边界检查)。而在java中,无论使用数组或容器,都有边界检查。如果越界操作就会得到一个RuntimeException异常。这类异常通常说明是程序员的错误,因此你不必自己做越界检查。多说一句,为了速度,C++的vector不会对每次存取做边界检查;而java的数组与容器会因为时刻存在的边界检查带来固定的性能开销。
其他通用的容器类:List、Set和Map,它们不以具体的类型来处理对象。也就是说
,它们将所有对象都按Object类型处理,即java中所有类的基类。从某个角度来说,这种方式很好:你只需要构建一个容器,任意的java对象都可以放入其中。(除开基本类型之外,数组可以放入容器作为使用Java基本包装器类的常量,或者作为包装在你自己的类中的可变值。)这正是数组比通用容器优越的第二点:当你创建一个数组时,它只能保存特定类型(这与第三点相关——数组可以保存基本类型,容器则不能)。这意味着会在编译时做类型检查。以防错误的类型插入数组,或取出数据时弄错类型。当然无论在编译时还是运行时,Java都会阻止你向对象发送不恰当的消息。所以,并不是说哪种方法更不安全,只是如果编译时就能够指出错误,那么程序可以运行得更快,也减少了程序的使用者被异常吓着的可能性。
数组,书中没有再去细细的分析,而且建议优先使用容器,因为从效率、类型、保存基本类型上看,可能只有在效率上还有些优势,但是在稍微灵活些的实现上,似乎都会有很多限制;个人觉得从其方便性和理解上(从c过来的人)可能更简单直观;数组的赋值,特别是赋以根据自己特定需要的有意义的值,不是那么直接,(也包括容器),需要用Generator类(根据自己的需要而修改产生的)解决(书上给了很好的示例)。最后就是介绍了System.arraycopy()、binarySearch()、Array.asList、CompareTo等实用方法。
深入容器:可以理解为更好的使用容器,推荐一般默认常用的容器。ArrayList、HashSet、HashMap
首先比较完整的容器图很好,可以看到我们最常用的List、Set、Queue是继承自Collection,而Map是单独出来的接口,带有Abstract开头的抽象类,方便我们自定义需要的容器,其实最常用的还是原来那张简化图足够了。先说说共性的,常考虑的
(1)容器填充:fill() addALL() 或者自定义的Generator
(2)Collection常用方法:书中有张表很详细。注意的是不包括随机访问元素的get()方法(Set是自己维护内部顺序);如clear() contains() remove() iterator()
(3)UnsupportedOperationException异常:“执行各种不同的添加和移除的方法在Collection接口中都是可选操作”,是特殊的接口定义方法,不保证调用某些方法是有意义的行为,当出现这种没有实际定义的方法是产生异常;这样的好处是防止出现接口爆炸的情况。
(4)散列(HashCode方法):可以根据需要自己覆盖原有的该方法,也可以利用Object自带的hashcode方法;应该都学过散列的冲突问题,因此可以想到散列值对应的位置存储的该是一个存放引用的列表;相应的就会理解在HashMap中为什么要弄个容量和负载因子的设置了,如果能够估计HashMap的使用量,合理的预先设置会减少再散列以及查找元素带来的性能损耗问题。
(5)同步控制:容器在①防止多个进程同时修改同一个容器②获取迭代之后(iterator),遍历完之前,的插入、删除等操作都有报错。
(6)持有引用:主要说了为垃圾回收提供了不同级别的间接提示的Java.lang.ref类库:
SoftReference(对内存敏感的高速缓存)、WeakReference(“规范映射”,同一个值只有一个存储空间)、PhantomReference(最弱,依赖于ReferenceQueue);
具体的List、Set、Map的使用,下面的表更有用。
Stack
栈(Stack)是限制仅在表的一端进行插入和删除运算的线性表。
java 没有栈这样的数据结构,如果想利用先进后出(FILO)这样的数据结构,就必须自己实现。
要实现Stack,至少应该包括:
1. pop() 出栈操作,弹出栈顶元素。
2. push(E e) 入栈操作
3. peek() 查看栈顶元素
4. isEmpty() 栈为空
另外,实现一个栈,还应该考虑到几个问题:
1. 栈的初始大小以及栈满以后如何新增栈空间
2. 对栈进行更新时需要进行同步
有三种实现的方式,数组,容器,以及链表的方法。
数据:
package gsm;
import java.util.*;
public class StackArray{
private int[] array;//用数组实现
private int top; //栈顶指针
private final static int size = 100;
public StackArray(){
array = new int[size];
top = -1; //栈空的时候
}
//压栈
public void push(int element){
if(top == size-1){
throw new StackOverflowError();
}
else
array[++top] = element;
}
//弹栈
public int pop(){
if(top == -1){
throw new EmptyStackException();
}
return array[top--];
}
//判断是否为空
public boolean isEmpty(){
return top == -1;
}
//返回栈顶元素
public Integer peek(){
if(top == -1){
throw new EmptyStackException();
}
return array[top];
}
}
容器
package gsm;
public interface Stack<T> {
public T pop();
public void push(T element);
public boolean isEmpty();
public T peek();
}
package gsm;
import java.util.*;
public class StackList<T> implements Stack<T> {
private List<T> list ; //用容器实现
StackList(){
list = new ArrayList<T>();
}
//弹栈
public T pop(){
if(this.isEmpty() == true){
throw new EmptyStackException();
}
return list.remove(list.size()-1);
}
//压栈
public void push(T element){
list.add(element);
}
//判断是否为空
public boolean isEmpty(){
return list.size() == 0;
}
//返回栈顶元素
public T peek(){
if(this.isEmpty() == true){
throw new EmptyStackException();
}
return list.get(list.size()-1);
}
}
链表
package gsm;
import java.util.EmptyStackException;
public class LinkedStack<T> implements Stack<T>{
//不用容器或者数组等数据结构存储节点
//Node定义一个节点类
private static class Node<U>{
private U item; //存储的data
private Node<U> next; //类似指针
Node(){
this.item = null;
this.next = null;
}
Node(U item, Node<U> next){
this.item = item;
this.next = next;
}
boolean end(){
return item == null && next == null;
}
}
private Node<T> top ; //栈顶指针
LinkedStack(){
top = new Node<T>();
、Arrays类:提供了操作数组的一些static方法,如
fill() 用于以某个值填充整个数组。
sort() 用于对数组的排序,需要参数的类型实现Comparable接口
binarySearch() 用于在已经排序的数组中查找元素。
asList() 接受任意的数组为参数,并将其转变为List容器。
……
2、如果一个字符串需要多次改变其值,那么应该使用更高效的StringBuffer取代String。
3、复制数组System.arraycopy(),用它复制数组比用for循环复制要快得多。
4、基本类型数组和对象数组都可以复制,然而,如果复制对象数组,那么只是复制了对象的引用——而不是对象本身的拷贝。这被称作“浅复制”。
5、数组的比较:
Arrays .equal()
import java.util.Arrays;
public class Test{
public static void main(String[] args){
String[] s1 = new String[5];
Arrays.fill(s1,"Hi");
String[] s2 = {"Hi", "Hi", "Hi", "Hi", "Hi"};
System.out.println(Arrays.equals(s1, s2));
System.out.println(s1.equals(s2));
System.out.println(s1==s2);
}
}
1).对于Arrays.equals ,S1的所以元素指向同一个对象,而数组s2包含五个相互独立的对象,然而,数组相等是基于内容的(通过Object.equal()比较),所以结果为true。
2).但是后两个语句输入都是false。原因呢?
6、数组元素的比较:
1).Java有两种方式来提供比较功能。第一种是实现java.lang.Comparable接口,此接口中只有compareTo(Object o)一个方法。
2).实现Comparator接口,区中有俩个方法compare(Object o1, Object o2)和equals(Object o)两个方法。
7、Java标准库:针对基本类型设计的“快速排序(Quicksort)”,针对对象设计的“稳定归并排序”。
8、若找到目标,Arrays.binarySearch()产生的返回值等于或大于。否则,它将产生负返回值,表示若要保持数组的排序状态,此目标元素所应该插入的位置,这个负值得计算方式是:
-(插入点)- 1;
插入点是指,第一个大于查找对象的元素在数组的位置。如果数组中所有元素都小于要查找的对象吗,“插入点”就等于被查找数组的大小size()。
如果数组中包含重复的元素,则无法保证找到的是哪一个。此算法确实不是专为包含重复元素的数组而设计的,不过任然可用。
如果需要对没有重复元素的数组排序,可以使用TreeSet(保持数组顺序),或者LinkedHashSet(保持插入顺序.。
9、基本数据数组无法使用Comparator进行排序。
10、在使用binarySearch()时必须提供同样地Comparator(使用方法的重载版本)。
11、数组是效率最高的保持一组对象的方式,它是你的第一选择。而且,如果要保存基本类型,则只能用数组。
12、 Java容器类类库的用途是"保存对象",并将其划分为两个不同的概念:
1).Collection。一个独立的元素,这些元素都服从一条或多条规则。List必须保存元素特定的顺序,而Set不能有重复元素。
2).Map。一组成对的”键值对“对象。初看起来这似乎应该是一个Collection,其元素是成对的对象,但是这样的设计实现起来太笨拙了,于是我们将Map明确提取出来形成一个独立的概念。另一方面,如果使用Collection表示Map的部分内容,会便于查看此部分内容。因此Map可以返回所有键组成的Set,所有值组成的Collection,或其键值对组成的Set;并且象数组一样容易扩展成多维Map,无需增加新的概念,只要让Map中键值对的每个“值”也是一个Map即可(此Map中的“值”还可以是Map,依此类推)。
13、
1).List按对象进入的顺序保存对象,不做排序或编辑操作。
2).Set对每个对象只接受一次,并使用自己内部的排序方法(通常,你只关心某个元素是否属于Set,而不关心它的顺序--否则应该使用List)。
3).Map同样对每个元素保存一份,但这是基于"键"的,Map也有内置的排序,因而不关心元素添加的顺序。
4).如果添加元素的顺序对你很重要,应该使用 LinkedHashSet或者LinkedHashMap.、
14、Collections中的fill()方法也是只复制同一个对象的引用来填充整个容器,并且只对List对象有用。
15、容器的缺点:将对象加入容器的时候就丢失了类型信息。容器只保存对Object的引用,Object是所有类的基类,因此容器可以保存任何类型的对象。(当然不包括基本类型,因为它们不是真正的对象,没有继承任何东西。)
16、如果原本是使用ArrayList,但是后来考虑到容器的特点,该用Set,那该怎么做?或者你打算写通用的代码,它们只是使用容器,不知道或者说不关心容器的类型,那么如何才能不重写代码就可以应用与不同类型的容器?
迭代器(也是一种设计模式)的概念可以用于达成次目的。迭代器是一个对象,它的工作是遍历并选择序列中的对象,而客户端程序员不必知道或关心该序列底层的结构(也就是不同容器的类型)。此外,迭代器通常被称为"轻量级"对象:创建它的代价小。
17、Collection中并不包括随机访问所选择元素的get()方法。因为Collection包括Set,而Set是自己维护内部顺序的(这使得随机访问变得没意义)。因此,如果你想要检查Collection中的元素,必须使用迭代器。
18、Java中,Vector、Stack和Hashtable已经过时。
19、List的功能方法
如果再开发环境中,性能开销主要由其他因素产生,那么ArrayList与LinkedList之间的开销差异就不重要了,无论使用哪一种都可以。
20、Set的功能方法:
Set具有与Collection完全一样得接口,因此没有任何额外的功能。实际上Set就是Collection,只是行为不同。(这是继承与多态思想的典型应用:表现不同的行为。)Set不保存重复的元素。
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.TreeSet;
public class TestSet{
static void fill(Set<Object> s){
//split根据匹配给定的正则表达式来拆分此字符串。
s.addAll(Arrays.asList("one two three four five six seven".split(" ")));
}
public static void text(Set<Object> s){
//replaceAll(String regex,String replacement)
//使用给定的 replacement替换此字符串所有匹配给定的正则表达式的子字符串。
System.out.println(s.getClass().getName().replaceAll("\\w+\\.", ""));
//调用多次:为了验证Set不保存重复的元素。
fill(s);
fill(s);
fill(s);
System.out.println(s);
s.addAll(s);
s.add("one");
s.add("one");
s.add("one");
System.out.println(s);
System.out.println("s.contains(\"one\"):" + s.contains("one"));
}
public static void main(String[] args){
text(new HashSet<Object>());
text(new TreeSet<Object>());
text(new LinkedHashSet<Object>());
}
}
运行结果:
从运行结果可以注意到,HashSet所维护的元素的次序不同于TreeSet和LinkedHashSet,因为它们保存元素以便以后还能找到该元素的方式个不相同。
1).TreeSet采用红黑树的数据结构排序元素。
2).HashSet则采用散列函数,这是专门为快速查询二设计的。
3).LinedHashSet内部使用散列加快查询速度,同时使用链表维护元素的次序。
20、使用HashSet和TreeSet这两种情况下,都必须为类定义equals();而hashCode(),只有在类被HashSet用到的时候才是必要的(这种可能性很大,因为HashSet通常是使用Set的第一选择)。无论如何,作为一种编程风格,当覆盖equals()的时候,就应该同时覆盖hashCode()。
21、Map的功能方法:
“键(key)“必须是唯一的,而”值(value)“可以有重复。
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
class Counter{
int i = 1;
public String toString(){
return Integer.toString(i);
}
}
public class Statistics{
private static Random rand = new Random();
public static void main(String[] args){
Map<Object, Object> m = new HashMap<Object, Object>();
for(int i = 0; i < 10000; i++){
Integer r = new Integer(rand.nextInt(20));
if(m.containsKey(r)){
((Counter)m.get(r)).i++;
}
else{
m.put(r, new Counter());
}
}
System.out.println(m);
}
}
这里为什么不之间用Integer而是另外写一个Counter类呢?
原因:Java包装器类(如这里的Integer)的对象一旦创建,就无法改变它的值。
22、使用自己创建的类作为HashMap的“键”,需要同时覆盖hashCode()和equals()。原因如下:
1).默认的hashCode()继承自Object,它默认是使用对象的地址计算散列码。
因此即使俩个对象的实例的内容相同,对象的地址是不同的,所以默认的hashCode()得到的散列码是不同的。如下例子:
import java.util.HashMap;
import java.util.Map;
public class TestHashMap{
private int id;
public TestHashMap(int id){
this.id = id;
}
public void func(){
System.out.println(id);
}
public String toString(){
return "TestHashcode#" + id;
}
public static void main(String[] args){
TestHashMap testHashcode = new TestHashMap(2012);
Map<Object, Object> hashMap = new HashMap<Object, Object>();
hashMap.put(testHashcode, 2012);
if(hashMap.containsKey(testHashcode)){
System.out.println("找到id为:" + testHashcode.id+"的TestHashcode");
}
else{
System.out.println("找不到id为:" + testHashcode.id+"的TestHashcode");
}
TestHashMap testHashcode1 = new TestHashMap(2012);
if(hashMap.containsKey(testHashcode1)){
System.out.println("找到id为:" + testHashcode1.id+"的TestHashcode");
}
else{
System.out.println("找不到id为:" + testHashcode1.id+"的TestHashcode");
}
System.out.println("--------------------------------");
System.out.println("testHashcode散列码为:"+testHashcode.hashCode());
System.out.println("testHashcode1散列码为:"+testHashcode1.hashCode());
}
}
运行结果:
出现了“找到”与“找不到”这种尴尬的处境。
2).这时,自己覆盖Oject的hashCode(),在TestHashMap中添加:
public int hashCode(){
return id;
}
再运行,结果如下:
3).这是因为HashMap使用equals()判断当前的“键”是否与表中存在的“键”相同,而默认equals()是继承自Object, Object中的Object.equals()只是比较对象的地址。于是再向TestHashMap添加:
public boolean equals(Object o){
// o instanceofTestHashMap检查o是否是TestHashMap的实例
return (o instanceof TestHashMap)
&& (id == ((TestHashMap)o).id);
}
运行结果:
综上:自定义的类,如果不为“键”覆盖hashCode()和equals(),那么在使用散列的数据结构是(HashSet、HashMap、LinkedHashSe,或LinkedHashMap),就无法正确处理你的“键“。
23、散列的价值在于速度:散列使得查询得以快速进行。由于瓶颈位于“键“的查询速度,散列使用数组(存储一组元素最快的数据结构)来保存散列”键“信息。数组并不保持”键“本身,而是通过”键“对象生成一个数字,将其作为数组的小标。这个数字就是散列码,由定义在Object中的。且可能由你的类覆盖的hashCode()生成。为解决数组容量被固定的问题,不同的”键“可以产生相同的下标,出现冲突。
24、查询一个“值“:计算散列码,使用散列码查询数组。出现冲突,由“外部链接“处理,这时数组并不是直接保存”值“,而是保存”值“的list。然后对list中的”值“使用equals()方法进行线性的查询。
25、ArrayList底层由数组支持,而LinkedList是由双向链表实现的。因此,如果经常要在表中插入和删除元素,LinkedList就比较适合(LinkedList还有建立在AbstractSequentialList基础上的其他功能);否则,应该使用速度更快的ArrayList。
26、树的行为方式是:它总是处于排序状态。TreeSet、HashMap都是排序好的。
27、只有在执行binarySearch()之前,才确实需要对List或数组进行排序。
28、总结:
1).
数组将数字与对象联系起来。它保存类型明确的对象,查询对象时,不需要对结果做类型转换。它可以是多维的,可以保存基本类型的数据。但是,数组一旦生成,其容量就不能改变。
2).Collection保存单个的元素,而Map保存相关联的键值对。
3).像数组一样,List也建立数字与对象的关联,可以认为数组和List都是排好序的容器。List能够自动扩充容量。但是List不能保存基本类型,只能保存Object的引用,因此必须对从容器中取出的Object结果做类型转换(RTTI)。
4).如果要进行大量的随机访问,就使用ArrayList;如果要经常从List中间插入或删除元素,则应该使用LinkedList。
5).
队列、双向队列以及栈的行为,由LinkedList提供支持。
6).Map是一种将对象与对象相关联的设计。HashMap着重于快速访问;TreeMap保持“键”始终处于排序状态,所以没有HashMap快。LinkedHashMap保持元素插入的顺序,也可以使用LRU(“最近最少使用“)算法对其重排序。
7). Set不接受重复元素。HashSet提供最快的查询速度,TreeSet保持元素处于排序状态。LinkedHashSet以插入顺序保存元素。
8).新程序中不应该使用过时的Vector、Hashtable和Stack。
Java 容器类库:抽象类和遗留构建.
可以看看看源码。比如HashMap这种容器就是看数组的组织形式和查询访问处理过程. 就是看
几个常用接口,比如put,get这些以及构造函数,就是数据初始化的时候都做了什么。
只有四种容器:List, Set, Queue, Map
Collection: List, Set, Queue
Collection接口的定义
interface Collection<T> {
boolean add(T e);
boolean addAll(Collection<? extends T> c);
void clear();
boolean contains(Object o);
boolean containsAll(Collection<?> c);
boolean isEmpty();
Iterator<T> iterator();
boolean remove(Object o);
boolean removeAll(Collection<?> c);
boolean retainAll(Collection<?> c);
int size();
Object[] toArray();
<T> T[] toArray(T[] a);
}
Collection接口不包括随机访问元素get()方法,因为Collection要包括Set. Set是自己维护内部顺序的(这样随机访问就没有意义了),因此如果想检查Collection中的元素,就要使用迭代器Iteration。
ArrayList来保存数据集,然后向上转型为Collection:Collection c = new ArrayList(); 如果想用List额外的功能,如返回子集,需要转型:Collection c3 = ((List) c).subList(3,5);
通过Collection接口定义的源码可以看出,
3.1 是用模板实现的,适合各种类型,代码复用性更好了。
3.2 所有...All方法接收的参数都是Collection对象。也就是List, Set,Queue,通过向上转型提高了代码的复用性。
3.3 Collection定义了Iterator函数。用于访问容器元素。
List 接口的定义
interface List<T> extends Collection<T> {
void add(int index, T element);
void add(int index, T element);
boolean addAll(int index, Collection<? extends T> c);
T get(int index);
int indexOf(Object o);
int lastIndexOf(Object o);
ListIterator<T> listIterator();
ListIterator<T> listIterator(int index);
T remove(int index);
T set(int index, T element);
List<T> subList(int fromIndex, int toIndex);}
Iterator 接口的定义
interface Iterator<T> {
boolean hasNext();
T next();
void remove();
}
ListIterator 接口的定义(双向,支持向前和向后遍历,同时支持add和修改)
interface ListIterator<T> {
void add(T e);
boolean hasPrevious();
int nextIndex();
T previous();
int previousIndex();
void set(T e);
void remove();
}
ArrayList是支持泛型的,它继承自AbstractList,实现了List、RandomAccess、Cloneable、java.io.Serializable接口。
他仅有的两个私有属性:
private transient Object[] elementData ;
private int size;
具体说来:ArrayList是用数组实现的。ArrayList最重要的特征就是动态数组,即可以动态扩容。这是怎么实现的呢?
主要技术是使用了 elementData = Arrays.copyOf(elementData, newCapacity); 数组的复制。扩容时把原来数组里的所有元素拷贝到新创建的数组中,其中对数组的拷贝与移动大量使用了 System.arraycopy
http://blog.csdn.net/jzhf2012/article/details/8540410
http://blog.csdn.net/crave_shy/article/details/17436773
关于迭代器再说两句:
private class Itr implements Iterator {
int cursor = 0;
int lastRet = -1;
int expectedModCount = modCount;
}
Itr 依靠三个int变量实现遍历。cursor是当前位置,第一次调用 next() 将返回索引为 0 的元素。lastRet 记录上一次游标所在位置,因此它总是比 cursor 少 1。
方法 next(): 返回的是索引为 cursor 的元素 ,然后修改 cursor 和 lastRet 的值:
public Object next() {
checkForComodification();
try {
Object next = get( cursor);
lastRet = cursor ++;
return next;
} catch(IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
Set
Set对存储的元素要求是唯一性: 所以要通过equals来确保对象的唯一性。
Set接口的定义
public interface Set<E> extends Collection<E> {
int size();
boolean isEmpty();
boolean contains(Object o);
Iterator<E> iterator();
Object[] toArray();
<T> T[] toArray(T[] a);
boolean add(E e);
boolean remove(Object o);
boolean containsAll(Collection<?> c);
boolean addAll(Collection<? extends E> c);
boolean retainAll(Collection<?> c);
boolean removeAll(Collection<?> c);
void clear();
boolean equals(Object o); //Set 自己独有的
int hashCode(); //Set 自己独有的
}
class SetType {
int i ;
public SetType( int n){
i = n;
}
//判断是否相等1:类型相等 2:值相等
public boolean equals(Object o){
return o instanceof SetType && ( i==((SetType)o). i);
}
public String toString(){
return Integer.toString( i);
}
}
HashSet
(适合快速查找)
HashSet 底层是使用 HashmMap 实现的。 当使用 add 方法将对象添加到Set当中时,实际上是将该对象作为底层所维护的 Map 对象的 key,而 value 则都是同一个 Object 对象.
class HashType extends SetType{
HashType( int n){
super(n);
}
public int hashcode(){
return this .i ;
}
}
TreeSet
//默认构造函数。使用该构造函数,TreeSet中的元素按照自然排序进行排列。
TreeSet()
// 创建的TreeSet包含collection
TreeSet(Collection<? extends E> collection)
// 指定TreeSet的比较器
TreeSet(Comparator<? super E> comparator)
// 创建的TreeSet包含set
TreeSet(SortedSet<E> set)
继承结构:TreeSet->SortedSet->Set(保持了次序:按照元素的插入顺序保存元素,底层是树,元素必须实现Comparable接口,可排序)
SortedSet(接口:按对象的比较函数对元素排序)
TreeSet 中的元素支持 2 种排序方式:自然排序 或者 根据创建 TreeSet 时提供的 Comparator 进行排序。这取决于使用的构造方法。TreeSet实际是TreeMap实现的,
因为TreeSet是Set所以支持add,remove,因为有序,就支持get.
TreeSet 支持的基本操作(add、remove 和contain) 提供受保证的 log(n) 时间开销。
因为执行了NavigableSet,TreeSet拥有了导航方法:
4.1 提供元素项的导航方法,返回某个元素。
4.2 另一类是提供集合的导航方法,返回某个集合。
subSet(fromElement, toElement )
headSet(toElement)
tailSet(fromElement)
http://www.cnblogs.com/skywang12345/p/3311268.html
http://www.fengfly.com/plus/view-213903-1.html
class TreeType extends SetType implements Comparable<TreeType>{
TreeType( int n){
super (n);
}
public int compareTo(TreeType arg){
return (arg.i < this. i ? -1 :(arg. i== this .i ?0:1));
}
}
LinkedHashSet
(具有HashSet的查询速度,内部使用链表维护(次序),因此保持了次序:按照元素的插入顺序保存元素)
SortedSet
(接口:按对象的比较函数对元素排序)
Queue:
队列是典型FIFO先进先出容器。队列通常被当做一种可靠的将对象从程序的某个区域传输到另一个区域的途径。队列在并发编程特别重要,因为他们可以安全地将对象从一个任务传输给另一个任务。他的两个实现LinkedList和PriorityQueue,差异是排序,而非性能
Queue 接口的定义:
interface Queue<E>
//throws excexption
boolean add(E e);//将元素插入队尾
E remove();//移出返回队头
E element(); //返回队头
//return special value
boolean offer(E e); //将元素插入队尾
E poll();//移出返回队头
E peek();//返回队头
PriorityQueue根据优先级确定下一个移出对列的元素。放入PriorityQueue的元素需要实现Comparable接口,最小的元素会先在队首。
当你在PriorityQueue上调用offer()方法来插入一个对象时,这个对象会在队列中被排序(这依赖具体实现,PriorityQueue通常会在插入时排序(维护一个堆),但是也可能在移除时选择最重要的元素)。默认的排序是自然排序,但是可以通过自己的Comparator来修改这个顺序(优先级)。
PriorityQueue 接口的定义
boolean add(E e)
void clear()
Comparator<? super E> comparator()
boolean contains(Object o)
Iterator<E> iterator()
boolean offer(E e)
E peek()
E poll()
boolean remove(Object o)
int size()
Object[] toArray()
<T> T[] toArray(T[] a)
优先级自定义举例:
public class ToDoList extends PriorityQueue<ToDoList.ToDoItem>{
static class ToDoItem implements Comparable<ToDoItem>{
private char primary;
private int secondary;
private String item;
public ToDoItem(String td, char pri, int sec){
this.primary = pri;
this.secondary = sec;
this.item = td;
}
// 因为类型 E 是自定义的 ToDoItem,为了能够定义优先级 - 排序
// 要 implements Comparable, 定义 compareTo
public int compareTo(ToDoItem arg){
if(this.primary > arg.primary){
return +1;
}
if(this.primary == arg.primary){
if(this.secondary < arg.secondary)
return -1;
else if(this.secondary > arg.secondary)
return 1;
else if(this.secondary == arg.secondary )
return 0;
}
return -1;
}
public String toString(){
return Character.toString(primary)+" "+this.secondary+" "+item;
}
}
public void add(String td, char pri, int sec){
super.add(new ToDoItem(td, pri, sec));
}
public static void main(String[] args) {
// TODO Auto-generated method stub
}
}
Map
Map接口的定义
interface Map<K, V> {
void clear();
boolean containsKey(Object key);
boolean containsValue(Object value);
V get(Object key);
V put(K key, V value);
boolean isEmpty();
Set<java.util.Map.Entry<K, V>> entrySet();
Set<K> keySet();
void putAll(Map<? extends K, ? extends V> m);
V remove(Object key);
int size();
Collection<V> values();
}
interface Map.Entry<K, V> {
K getKey();
V getValue();
V setValue(V value);
boolean equals(Object o); //定义equals方法:判断两个Entry是否相同
int hashCode(); //定义获取hashCode的方法
}
Map 接口有6种实现:
HashMap 下面详细介绍,缺点线程不安全
LinkedHashMap 类似HashMap,迭代遍历时候,取得key-value pair的顺序是它的插入或者是LRU(最近最少使用)次序。
TreeMap 实现了SortedMap, 类似TreeSet的一些方法。红黑树的实现,所得到的结果是经过排序(次序由comparable和comparator决定)的。
WeakHashMap
ConcurrentHashMap 线程安全的Map.
IdentityHashMap
如果不为自己创建的类覆盖hashCode()和equals(),那么使用散列数据结构(HashSet,HashMap, LinkedHashSet,或者LinkedHashMap)就无法正确处理你的键。
HashMap类
HashMap继承自AbstractMap,实现了Map接口(这些内容可以参考《Java集合类》)。来看类的定义。
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable
为什么要使用HashMap?
可以使用List作为基本结构,再覆盖hashCode()和equals()就可以实现基本的Map功能。
但是问题是对键的查询,键没有任何特定的顺序保存,所以只能使用线性查询,而线性查询是最慢的查询方式。
所以这里要使用一种叫哈希表(散列)的数据结构。这个DS综合(折中)了数组和链表的优点。
hash对每个要存储的数据进行计算:hash(key.hashCode),得到的哈希值是数组的index.
通过看HashMap源码,可以发现:
1. HashMap底层是用Entry数组实现的,我们放进HashMap的元素,实际上是放进数组中的。
数组名叫table。Entry<K,V>是Map.Entry<K,V>的实现。
table 当需要的时候会扩容resize。大小必须是2的幂。
2. [散列表实现机制] 当向 HashMap 中put 一对key-value pair时候,它会根据 key 的 hashCode 值计算出一个位置,该位置就是此对象准备往数组中存放的位置。
如果该位置没有对象存在,就将此对象直接放进数组中;
如果该位置有对象存在,则顺着此对象存在的链开始寻找(Entry 类有一个 Entry 类型的 next 成员变量,指向了该对象的下一个对象),如果此链上有对象的话,再去使用 equals 方法进行比较,如果此链上的某个对象equals方法比较为 false , 则将该对象放到数组中,然后将数组中该位置以前存在的那个对象链接到此对象后面。
遍历方法
Map<Integer, Integer> map = new Hashmap<Integer, Integer>();
Iterator it = map.entrySet().iterator();
while(it.hasNext()){
Map.Entry entry = (Map.Entry ) it.next();
Object key = entry.getKey();
Object value = entry.getValue();
}
HashMap使用了散列码hashcode来取代对key的缓慢搜索。hashcode相对唯一,代表对象。
可以实现什么算法?
1. 统计分布,比如统计Random随机数的分布。
for( i=0;i<N; i++){
int r = rand.nextInt(20); //key
Integer freq = map.get(r);//返回value
if(freq == null){
map.put(r,1);
} else{
map.put(r,freq+1);
}
}
http://www.cnblogs.com/hzmark/archive/2012/12/24/hashmap.html
备注:
1. hashCode()
http://www.oschina.net/question/82993_75533 如何正确重写hashcode和equal方法,参考这个文章。
设计hashCode()时最重要的因素就是:无论何时,对同一个对象调用hashCode()都应该生成同样的值。所以,如果你的hashCode()方法依赖于对象中易变的数据,用户就要当心了,因为此数据发生变化时,hashCode()就会产生一个不同的散列码,相当于产生一个不同的键。
此外,也不应该使hashCode()依赖于具有唯一性的对象信息,尤其是使用this的值。这很糟糕!因为这样做无法生成一个新的键,使之与put()中原始的键值对中的键相同。所以,应该使用对象内有意义的标识符。
尽量保证使用对象的同一个属性来生成hashCode()和equals()两个方法。在Employee的案例中,我们使用员工id。
eqauls方法必须保证一致(如果对象没有被修改,equals应该返回相同的值).任何时候只要a.equals(b),那么a.hashCode()必须和b.hashCode()相等。两者必须同时重写。
数组为什么特殊
数组和其它容器之间的区别有三方面:效率、编译期类型检查和保存基本类型的能力,但是由于有了泛型容器和自动包装机制,现在最主要的区别就是效率。ArrayList 效率低很多。
数组是一种效率最高的存储和访问对象引用序列的方式。
数组是第一级对象
初始化数组的2种方式:
1. 聚集初始化:
//aggregate initialization
BerylliumSphere[] d = { new BerylliumSphere(),
new BerylliumSphere(),
new BerylliumSphere(),
new BerylliumSphere()};
动态聚集初始化:
//dynamic initialization
a = new BerylliumSphere[]{ new BerylliumSphere(), new BerylliumSphere()};
可以在任意位置创建和初始化数组对象 e.g.
hide(new BerylliumSphere[]{ new BerylliumSphere())
返回一个数组
多维数组
new 来分配:int [][][] a = new int[2][2][4];
Arrays.deepToString输出多维数组
数组中构成矩阵的每个向量都可以具有任意的长度 -- 粗糙数组:
[[Sphere 1, Sphere 2], [Sphere 3, Sphere 4, Sphere 5, Sphere 6], [Sphere 7, Sphere 8, Sphere 9, Sphere 10, Sphere 11, Sphere 12, Sphere 13, Sphere 14]]
数组与泛型
创建测试数据
Arrays.fill() 只能用同一个值填充各个位置,或者是填充某个区域
数据生成器
从Generator中创建数组,为了更灵活的创建更有意义的数组。
Arrays实用功能
Arrays 类有一套用于数组的静态方法(所有这些方法对各种基本类型和 Object 类而重载过):
方法说明equals()比较两个数组是否相等deepEquals()用于多维数组比较fill()填充数组sort()数组排序binarySearch()在已经排序的数组中查找元素toString产生数组的 String 表示hashCode()产生数组的散列码asList()接受任意的序列或数组作为其参数,并转换为 List 容器复制对象数组:只是复制了对象的引用——而不是对象本身的拷贝。
数组元素的比较: comparable and comparator
数组排序:Arrays.sort(sa);
使用内置的排序方法,可以对任意的基本类型数组排序;也可以对对象数组排序,只要该对象实现了Comparable接口,或者具有相关联的Comparator:
Java标准库中的排序算法针对正排序的特殊类型进行了优化: 针对基本类型的快速排序;针对对象设计的“稳定归并排序”。所以无需担心排序的性能问,除非确实确定了排序不分是程序效率的瓶颈。
在已排序的数组中查找:Arrays.binarySearch()
int location = Arrays.binarySearch(a, r)
如果需要对没有重复元素的数组排序,可以使用TreeSet(保持排序顺序),或者LinkedHashSet(保持插入顺序)
一维数组的声明方式:
type var[]; 或type[] var;
声明数组时不能指定其长度(数组中元素的个数),
Java中使用关键字new创建数组对象,格式为:
数组名 = new 数组元素的类型 [数组元素的个数]
实例:
TestNew.java:
程序代码:
public class TestNew
{
public static void main(String args[]) {
int[] s ;
int i ;
s = new int[5] ;
for(i = 0 ; i < 5 ; i++) {
s[i] = i ;
}
for(i = 4 ; i >= 0 ; i--) {
System.out.println("" + s[i]) ;
}
}
}
初始化:
1.动态初始化:数组定义与为数组分配空间和赋值的操作分开进行;
2.静态初始化:在定义数字的同时就为数组元素分配空间并赋值;
3.默认初始化:数组是引用类型,它的元素相当于类的成员变量,因此数组分配空间后,每个元素也被按照成员变量的规则被隐士初始化。
实例:
TestD.java(动态):
程序代码:
public class TestD
{
public static void main(String args[]) {
int a[] ;
a = new int[3] ;
a[0] = 0 ;
a[1] = 1 ;
a[2] = 2 ;
Date days[] ;
days = new Date[3] ;
days[0] = new Date(2008,4,5) ;
days[1] = new Date(2008,2,31) ;
days[2] = new Date(2008,4,4) ;
}
}
class Date
{
int year,month,day ;
Date(int year ,int month ,int day) {
this.year = year ;
this.month = month ;
this.day = day ;
}
}
TestS.java(静态):
程序代码:
public class TestS
{
public static void main(String args[]) {
int a[] = {0,1,2} ;
Time times [] = {new Time(19,42,42),new Time(1,23,54),new Time(5,3,2)} ;
}
}
class Time
{
int hour,min,sec ;
Time(int hour ,int min ,int sec) {
this.hour = hour ;
this.min = min ;
this.sec = sec ;
}
}
TestDefault.java(默认):
程序代码:
public class TestDefault
{
public static void main(String args[]) {
int a [] = new int [5] ;
System.out.println("" + a[3]) ;
}
数组:是一组相关变量的集合
数组是一组相关数据的集合,一个数组实际上就是一连串的变量,数组按照使用可以分为一维数组、二维数组、多维数组
数据的有点
不使用数组定义100个整形变量:int i1;int i2;int i3
使用数组定义 int i[100];
数组定义:int i[100];只是一个伪代码,只是表示含义的
一维数组
一维数组可以存放上千万个数据,并且这些数据的类型是完全相同的,
使用java数组,必须经过两个步骤,声明数组和分配内存给该数组,
声明形式一
声明一维数组:数据类型 数组名[]=null;
非配内存给数组:数组名=new 数据类型[长度];
声明形式二
声明一维数组:数据类型 [] 数组名=null;
java数据类型分为两大类
基本数据类型
int、long操作的时候本身就是具体的内容
引用数据类型:数组、类、接口
引用传递的就是一个内存的使用权,一块内存空间,可能有多个人同时使用
事例声明数组
package com.qn.array;
public class Test {
public static void main(String[] args) {
int score[]=null;//声明数组
score=new int[3];//开辟空间,大小为3
}
}
数组的声明格式里,数据类型是数组元素的数据类型,常见的有整形、浮点型、与字符型等
数组名是用来统一这组相同数据类型元素的名称,其命名规则和变量的相同
数组声明后实际上是在栈内存中保存了此数组的名称,结下了是要在堆内存中配置数组所需要的内存,齐产固定是告诉编译器,所声明的数组要存放多少个元素,而new 则是命令编译器根据括号里的长度
基本数据类型偶读有其默认值:int 0;只要是引用数据类型默认值就是null
事例
package com.qn.array;
public class Test {
public static void main(String[] args) {
int score[]=null;//声明数组
score=new int[3];//开辟空间,大小为3
System.out.print('score[0]='+score[0]);
System.out.print('\tscore[1]='+score[1]);
System.out.print('\tscore[2]='+score[2]);
}
}
堆栈内存的解释
数组操作中,在栈内存中保存的永远是数组的名称,只开辟了栈内的空间,数组是永远无法使用的,必须有指向的对内存才可以使用,要想开辟新对内存空间必须使用new关键字,之后就是将对内存的使用权交给对应的栈内存,而且一个堆内存空间可以同时被多个栈内存空间指向,比如一个人可以有多个名字,人就相当于对内存,名字就相当于栈内存
声明数组的同时分配内存空间
声明数组的同时非配内存
数据类型 数组名[]=new 数据类型[个数]
int score[]=new int[10];
声明一个元素个数为10的整形数组score,同时开辟依靠内存空间工期使用
java中,由于整形数据类型占用的空间为4个byte,而整个数组score可保存的元素有10个。所以上例中占用的内存共有4*10=40个字节
数组的访问
数组中元素的表示方法
想要访问数组里的元素可以利用索引来完成,java的数组索引标号由10开始,以一个score[10]的整形数组为例,score[0]代表第一个元素
一直向下,最后一个为score[9]
取得数组的长度
在java中取得数组的长度(也就是数组元素的长度)可以利用数组名称.length完成,
数组名称.length--返回一个int类型的数据
package com.qn.array;
public class Test {
public static void main(String[] args) {
int score[]=null;//声明数组
score=new int[3];//开辟空间,大小为3
System.out.println(score.length);
}
}
结果
数组的静态初始化
之前的数组,所采用的都是动态初始化,所有的内容在数组声明的时候并不具体的指定,而是以默认值的形式出现
静态初始化是指在数组声明后直接为数组指定具体的内容
如果想要直接在声明的时候给数组赋初始值,可以采用大括号完成,只要在数组的生命格式后面加上初值的赋值即可,
数据类型 数组名 []={初始值0,初始值1,初始值3,....初始值n};
package com.qn.array;
public class Test {
public static void main(String[] args) {
int score[]={1,2,3,4,5,6};//使用静态初始化声明数组
System.out.println(score.length);
}
}
结果
6
范例求出数组中的最大值和最小值
package com.qn.array;
public class Test {
public static void main(String[] args) {
int score[]={45,12,8,23,89,56};//使用静态初始化声明数组
int max=0;
int min=0;
max=min=score[0];
for (int i = 0; i < score.length; i++) {
if(score[i]>max){
max=score[i];
}
if(score[i]
min=score[i];
}
}
System.out.println('最大值:'+max);
System.out.println('最小值:'+min);
}
}
结果
范例排序,在操作中排序是比较常用的
从大到小
package com.qn.array;
public class Test {
public static void main(String[] args) {
int score[]={45,12,8,23,89,56};//使用静态初始化声明数组
int temp=0;
for (int i = 0; i < score.length; i++) {
for (int j = 0; j < score.length-1; j++) {
if(score[i]>score[j]){
temp=score[i];
score[i]=score[j];
score[j]=temp;
}
}
}
for (int i = 0; i < score.length; i++) {
System.out.print(score[i]+'\t');
}
}
}
结果
这个时候不要被i值所迷惑 if(score[i]>score[j]){
这一步主要知识为了比较,实际上完成之后输出的时候是根据j的值排序的
二维数组
如果可以把一维数组当做几何中的一条线图形,那么二维数组就相当于一个表格
A B
1 姓名 年龄
2 齐宁 21
3 齐燕 23
4 齐威 26
二维数组声明的方式和以为数组的类似,内存分配也一样是用new这个关键字
其实声明与分配内存的格式如下
动态初始化
数据类型 数组名[][];
数组名=new 数据类型[行的个数][列的个数];
声明并初始化数组
数据类型 数组名[][]=new 数据类型[行的个数][列的个数];
静态初始化
二维数组的存储
声明二维数组score 同时开辟一段内存空间
int score[][]=new int[4][3];
整体数据score可保存的元素是4*3=12个,在java中,int数据类型所占用的空间为4个字节,因此该整形数组占用的内存共为4*12=48个字节
事例
package com.qn.array;
public class test1 {
public static void main(String[] args) {
int score[][] = new int[4][3];
score[0][1] = 30;
score[1][0] = 31;
score[2][1] = 32;
score[2][2] = 33;
score[3][1] = 34;
score[1][1] = 35;
for (int i = 0; i < score.length; i++) {
for (int j = 0; j < score[i].length; j++) {
System.out.print(score[i][j]+'\t');
}
System.out.println('');
}
}
}
结果
二维数组静态初始化
用到的时候才会开辟空间,不用的(红色部分)则不开辟空间
多维数组
一般只是用到二维数组
三维数组简单了解
package com.qn.array;
public class test1 {
public static void main(String[] args) {
int score[][][]={
{
{5,1},{6,7}
},
{
{9,4},{8,3}
}
};
System.out.print(score[0][0][0]+'\t');
System.out.print(score[0][0][1]+'\t');
System.out.print(score[0][0][0]+'\t');
System.out.print(score[0][0][1]+'\t');
System.out.print(score[1][0][0]+'\t');
System.out.print(score[1][0][1]+'\t');
System.out.print(score[1][1][0]+'\t');
System.out.print(score[1][1][1]+'\t');
}
}
Java 数组
数组对于每一门编程语言来说都是重要的数据结构之一,当然不同语言对数组的实现及处理也不尽相同。
Java语言中提供的数组是用来存储固定大小的同类型元素。
你可以声明一个数组变量,如numbers[100]来代替直接声明100个独立变量number0,number1,....,number99。
本教程将为大家介绍Java数组的声明、创建和初始化,并给出其对应的代码。
声明数组变量
首先必须声明数组变量,才能在程序中使用数组。下面是声明数组变量的语法:
dataType[] arrayRefVar; // 首选的方法
或
dataType arrayRefVar[]; // 效果相同,但不是首选方法
注意: 建议使用dataType[] arrayRefVar 的声明风格声明数组变量。 dataType arrayRefVar[] 风格是来自 C/C++ 语言 ,在Java中采用是为了让 C/C++ 程序员能够快速理解java语言。
实例
下面是这两种语法的代码示例:
double[] myList; // 首选的方法
或
double myList[]; // 效果相同,但不是首选方法
创建数组
Java语言使用new操作符来创建数组,语法如下:
arrayRefVar = new dataType[arraySize];
上面的语法语句做了两件事:
一、使用dataType[arraySize]创建了一个数组。
二、把新创建的数组的引用赋值给变量 arrayRefVar。
数组变量的声明,和创建数组可以用一条语句完成,如下所示:
dataType[] arrayRefVar = new dataType[arraySize];
另外,你还可以使用如下的方式创建数组。
dataType[] arrayRefVar = {value0, value1, ..., valuek};
数组的元素是通过索引访问的。数组索引从0开始,所以索引值从0到arrayRefVar.length-1。
实例
下面的语句首先声明了一个数组变量myList,接着创建了一个包含10个double类型元素的数组,并且把它的引用赋值给myList变量。
public class TestArray {
public static void main(String[] args) {
// 数组大小
int size = 10;
// 定义数组
double[] myList = new double[size];
myList[0] = 5.6;
myList[1] = 4.5;
myList[2] = 3.3;
myList[4] = 4.0;
myList[5] = 34.33;
myList[6] = 34.0;
myList[7] = 45.45;
myList[8] = 99.993;
myList[9] = 11123;
// 计算所有元素的总和
double total = 0;
for (int i = 0; i < size; i++) {
total += myList[i];
}
System.out.println("总和为: " + total);
}
}
以上实例输出结果为:
总和为: 11354.173
下面的图片描绘了数组myList。这里myList数组里有10个double元素,它的下标从0到9。
处理数组
数组的元素类型和数组的大小都是确定的,所以当处理数组元素时候,我们通常使用基本循环或者foreach循环。
示例
该实例完整地展示了如何创建、初始化和操纵数组:
public class TestArray {
public static void main(String[] args) {
double[] myList = {1.9, 2.9, 3.4, 3.5};
// 打印所有数组元素
for (int i = 0; i < myList.length; i++) {
System.out.println(myList[i] + " ");
}
// 计算所有元素的总和
double total = 0;
for (int i = 0; i < myList.length; i++) {
total += myList[i];
}
System.out.println("Total is " + total);
// 查找最大元素
double max = myList[0];
for (int i = 1; i < myList.length; i++) {
if (myList[i] > max) max = myList[i];
}
System.out.println("Max is " + max);
}
}
以上实例编译运行结果如下:
1.9
2.9
3.4
3.5
Total is 11.7
Max is 3.5
foreach循环
JDK 1.5 引进了一种新的循环类型,被称为foreach循环或者加强型循环,它能在不使用下标的情况下遍历数组。
示例
该实例用来显示数组myList中的所有元素:
public class TestArray {
public static void main(String[] args) {
double[] myList = {1.9, 2.9, 3.4, 3.5};
// 打印所有数组元素
for (double element: myList) {
System.out.println(element);
}
}
}
以上实例编译运行结果如下:
1.9
2.9
3.4
3.5
数组作为函数的参数
数组可以作为参数传递给方法。例如,下面的例子就是一个打印int数组中元素的方法。
public static void printArray(int[] array) {
for (int i = 0; i < array.length; i++) {
System.out.print(array[i] + " ");
}
}
下面例子调用printArray方法打印出 3,1,2,6,4和2:
printArray(new int[]{3, 1, 2, 6, 4, 2});
数组作为函数的返回值
public static int[] reverse(int[] list) {
int[] result = new int[list.length];
for (int i = 0, j = result.length - 1; i < list.length; i++, j--) {
result[j] = list[i];
}
return result;
}
以上实例中result数组作为函数的返回值。
Arrays 类
java.util.Arrays类能方便地操作数组,它提供的所有方法都是静态的。具有以下功能:
给数组赋值:通过fill方法。
对数组排序:通过sort方法,按升序。
比较数组:通过equals方法比较数组中元素值是否相等。
查找数组元素:通过binarySearch方法能对排序好的数组进行二分查找法操作。
具体说明请查看下表:
序号 方法和说明
1 public static int binarySearch(Object[] a, Object key)
用二分查找算法在给定数组中搜索给定值的对象(Byte,Int,double等)。数组在调用前必须排序好的。如果查找值包含在数组中,则返回搜索键的索引;否则返回 (-(插入点) - 1)。
2 public static boolean equals(long[] a, long[] a2)
如果两个指定的 long 型数组彼此相等,则返回 true。如果两个数组包含相同数量的元素,并且两个数组中的所有相应元素对都是相等的,则认为这两个数组是相等的。换句话说,如果两个数组以相同顺序包含相同的元素,则两个数组是相等的。同样的方法适用于所有的其他基本数据类型(Byte,short,Int等)。
3 public static void fill(int[] a, int val)
将指定的 int 值分配给指定 int 型数组指定范围中的每个元素。同样的方法适用于所有的其他基本数据类型(Byte,short,Int等)。
4 public static void sort(Object[] a)
对指定对象数组根据其元素的自然顺序进行升序排列。同样的方法适用于所有的其他基本数据类型(Byte,short,Int等)。
从数组的一些简单介绍、到定义、声明、初始化、以及常用的方法、细微的区别、但是没有讲多维数组、多维数组本质是一维数组的延伸、以后有时间补充、有补充或者误导的地方望不吝指正、不胜感激!
一:数组的简介
Java中的数组、是一种简单的线性数据存储结构、他用牺牲自动扩展大小来换取与集合相比的唯一优势——查询效率的提升。他本身是一种引用类型的数据、所以我们要使用数组就要首先声明、初始化他、而他的初始化的完成也就意味着此数组的大小将不再改变、而且具有初始值。当存储的是java原始基础类型时、默认值是不同基础类型值的默认值、当是引用类型时、如果我们没有指定具体的对象、则他的默认值是null。从数组的内部组成来看、觉得java中没有多维数组!所谓的多维数组其实就是一维数组的延伸、只是数组的值存放的是另一个数组的引用、依次深入、我们可以扩展到任意维度的数组、注意:数组的扩展的过程中只能存放相同类型或者子类型的数组!
Java中的数组长度不变!存储的数据类型唯一!有时候我们会有种错觉觉得这两者都是不成立的、本质是因为java中继承的存在、我们定义的父类型的数组、他的数组元素也同样可以存放子类对象、这样就造成了数组存放对象可变的假象。当我们将数组的引用从一个对象改变到子类的对象的时候、感觉数组的长度变化、其实变的是引用指向的对象的长度、原来引用指向的对象长度没有变化、只是此对象被遗弃了而已、
对此只要记住一点:java中的数组时引用类型、当声明初始化一个对象时、数组的引用存放在栈内、实际对象存放在堆内存中、而我们平常所指的长度、类型都是具体对象的长度、类型!
此外、Arrays是一个操作java数组的类、提供了一些非常实用的用于操作数组的类、还有就是java数组与集合比较相似。后面会有两者间的相互转换的方法。
二:简单归纳:
java中有两种数据类型:
a)引用类型
b)基础类型
其中基础类型又有两种:
b1)数值类型
b2)及布尔类型。
数组——也为java的一个数据类型、归类为引用类型。本文意图说清楚两点:
1、数组的声明以及初始化。
2、常用的数组方法。
补充一点:对于我们常说的二维数组、多维数组其实是一维数组的延伸、这里暂时只围绕一维数组。
三:数组的声明及初始化
1、数组的声明:
作为一种引用类型、就如我们平常使用引用类型的时候声明一样、一般有两种写法:
a) type[] arrayName; exp: String[] strArray; b) type arrayName[]; exp: String strArray[];
第二种源于C的写法、由于很容易造成混淆、所以现在基本不使用这种声明方式了。
2、数组的初始化:
数组的初始化有两种:
a) 静态初始化——数组大小由系统分配、我们只为数组每个位置上赋值
String[] strArray1 = {"a", "b", "c", "d", "e"}; String[] strArray2 = new String[]{"a", "b", "c", "d", "e"};//在 new String[]中不能指定String数组的大小!
b)动态初始化——只指定数值的大小、初始化工作由系统为我们完成(即为数组的每个位置赋初始值)
String[] strArray3 = new String[5];//此时String数组的每个位置上的值都由系统来初始化、使用默认的值"" //我们能做的是动态的为strArray3每个位置上的值进行修改 for (int i = 0; i < strArray1.length; i++) { //这里仅用原始的方法进行赋值。 strArray3[i] = strArray1[i]; }
四:数组的常用方法
package com.chy.array.usefulMethods; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Set; import com.chy.array.bean.Student; @SuppressWarnings("all") public class ArrayUseFulMethoed { private static int[] intArray = {1, 2, 3, 4, 5}; private static String[] strArray = {"a", "b", "c", "d", "e"}; /** * 填充元素、比较大小、复制元素 */ public static void testFillArray(){ //注意字符串和对象的不同 Student[] student1 = new Student[4]; Student[] student2 = new Student[4]; System.out.println(Arrays.equals(student1, student2));//true Arrays.fill(student1, 0, 4, new Student(1,"chy")); Arrays.fill(student2, new Student(1,"chy")); System.out.println(Arrays.equals(student1, student2));//false String[] str1 = new String[4]; String[] str2 = new String[]{"a", "a", "a", "a"}; String[] str3 = {new String("a"), new String("a"), new String("a"), new String("a")}; Arrays.fill(str1, "a"); System.out.println(Arrays.equals(str1, str2));//true System.out.println(Arrays.equals(str2, str3));//true String[] str4 = Arrays.copyOf(str1, 2);//是将传入的数组拷贝len个元素到新的数组、相当于复制本身的一部分或者全部形成一个全新的数组 System.out.println(str4.length + "=======" + Arrays.toString(str4));// 2=======[a, a] String[] str5 = new String[8]; System.arraycopy(str4, 0, str5, 6, 2);//是将str4从下标0开的2个元素拷贝到从下标6开始放置的数组str5中 System.out.println(str5.length + "=======" + Arrays.toString(str5));// 8=======[null, null, null, null, null, null, a, a] } /** * 以字符串的形式输出指定数组的“模样” */ public static void printOriginalArray(){ String intArrayToString = Arrays.toString(intArray); System.out.println(intArrayToString); //result: [1, 2, 3, 4, 5] } /** * 将数组转化成List集合 * 注意:不能直接将int[]转化为集合、因为asList()方法的参数必须是对象。应该先把int[]转化为Integer[]。 * 对于其他primitive类型的数组也是如此,必须先转换成相应的wrapper类型数组。 */ public static void convetArrayToList(){ Integer[] integerArray = new Integer[intArray.length]; for (int i = 0; i < integerArray.length; i++) { integerArray[i] = intArray[i]; } ArrayList integerList1 = new ArrayList(Arrays.asList(integerArray)); /* * 不能写成下面: * ArrayList integerList2 = (ArrayList)Arrays.asList(integerArray); * 返回的是List、强转可以通过编译、但是不能正常使用。 */ for(int i : integerList1){ System.out.print(i); } //result: 12345 System.out.println(); } /** * 将List集合转换成数组 */ public static void convetListToArray(){ ArrayList strList = new ArrayList(Arrays.asList(strArray)); String[] strArrayFromList = new String[strList.size()]; strList.toArray(strArrayFromList); System.out.println(Arrays.toString(strArrayFromList)); //result: [a, b, c, d, e] /* * 注意:不能写成这样:String[] strArrayFromList = (String[])strList.toArray(strArrayFromList);会抛出ClassCastException。 * List.toArray()与List.toArray(T[] t)的区别在于: * List.toArray()返回的是一个Object[]、不能强转成String[]、强转的话可以通过编译、但是不能进行String[]的操作 * 而List.toArray(T[] t)会将list的值转换成T类型的数组。 */ } /** * 将数组转换成Set集合 */ public static void convertArrayToSet(){ Set set = new HashSet(Arrays.asList(strArray)); //Set具有无序性、所以输出结构不一定是原来数组元素存放顺序 System.out.println(set); //result: [d, e, b, c, a] } /** * 判断某个数组中是否包含一个元素、思路:将数组转换成list使用list的contains方法 */ public static void isContainObject(){ ArrayList strList = new ArrayList(Arrays.asList(strArray)); System.out.println(strList.contains("a")); //result: true //另一种实现 Arrays.sort(strArray); if(Arrays.binarySearch(strArray, "c") >= 0){ System.out.println(true); }else{ System.out.println(false); } } /** * 将两个相同类型的数组连接起来 */ public static void connTwoSameArray(){ int[] intArray2 = new int[]{6, 7, 8, 9, 10}; } /** * 将数组中数据排序 */ public static void sortArray(){ String[] str = {"c", "a" ,"d" ,"z" }; Arrays.sort(str); System.out.println(Arrays.toString(str)); //反序、 Arrays.sort(str, Collections.reverseOrder()); System.out.println(Arrays.toString(str)); } public static void main(String[] args) { /*printOriginalArray(); convetArrayToList(); convetListToArray(); isContainObject(); convertArrayToSet(); sortArray();*/ testFillArray(); } }
数组与其他种类的容器之间的区别有三方面:效率、类型和保存基本类型的能力。在java中,数组是一种效率最高的存储和随机访问对象引用序列的方式。数组就是一个简单的线性序列,这使得元素访问非常快速,但也损失了其他一些特性。当创建了一个数组对象(将数组本身作为对象看待),数组的大小就被固定了,并且这个数组的生命周期也是不可改变的。通常用移到新数组中。这正式ArrayList类的行为方式。然而这种弹性带来的开销,使得ArrayList比数组效率低得多。
在C++中,容器类vector的确知道自己保存的对象是何类型。不过与java中的数组相比,它有一个缺点:C++中vector的操作符[]不做边界检查,所以可能会越界操作(然而,要问vector有多大是可能的,at()方法的确会执行边界检查)。而在java中,无论使用数组或容器,都有边界检查。如果越界操作就会得到一个RuntimeException异常。这类异常通常说明是程序员的错误,因此你不必自己做越界检查。多说一句,为了速度,C++的vector不会对每次存取做边界检查;而java的数组与容器会因为时刻存在的边界检查带来固定的性能开销。
其他通用的容器类:List、Set和Map,它们不以具体的类型来处理对象。也就是说
,它们将所有对象都按Object类型处理,即java中所有类的基类。从某个角度来说,这种方式很好:你只需要构建一个容器,任意的java对象都可以放入其中。(除开基本类型之外,数组可以放入容器作为使用Java基本包装器类的常量,或者作为包装在你自己的类中的可变值。)这正是数组比通用容器优越的第二点:当你创建一个数组时,它只能保存特定类型(这与第三点相关——数组可以保存基本类型,容器则不能)。这意味着会在编译时做类型检查。以防错误的类型插入数组,或取出数据时弄错类型。当然无论在编译时还是运行时,Java都会阻止你向对象发送不恰当的消息。所以,并不是说哪种方法更不安全,只是如果编译时就能够指出错误,那么程序可以运行得更快,也减少了程序的使用者被异常吓着的可能性。
数组,书中没有再去细细的分析,而且建议优先使用容器,因为从效率、类型、保存基本类型上看,可能只有在效率上还有些优势,但是在稍微灵活些的实现上,似乎都会有很多限制;个人觉得从其方便性和理解上(从c过来的人)可能更简单直观;数组的赋值,特别是赋以根据自己特定需要的有意义的值,不是那么直接,(也包括容器),需要用Generator类(根据自己的需要而修改产生的)解决(书上给了很好的示例)。最后就是介绍了System.arraycopy()、binarySearch()、Array.asList、CompareTo等实用方法。
深入容器:可以理解为更好的使用容器,推荐一般默认常用的容器。ArrayList、HashSet、HashMap
首先比较完整的容器图很好,可以看到我们最常用的List、Set、Queue是继承自Collection,而Map是单独出来的接口,带有Abstract开头的抽象类,方便我们自定义需要的容器,其实最常用的还是原来那张简化图足够了。先说说共性的,常考虑的
(1)容器填充:fill() addALL() 或者自定义的Generator
(2)Collection常用方法:书中有张表很详细。注意的是不包括随机访问元素的get()方法(Set是自己维护内部顺序);如clear() contains() remove() iterator()
(3)UnsupportedOperationException异常:“执行各种不同的添加和移除的方法在Collection接口中都是可选操作”,是特殊的接口定义方法,不保证调用某些方法是有意义的行为,当出现这种没有实际定义的方法是产生异常;这样的好处是防止出现接口爆炸的情况。
(4)散列(HashCode方法):可以根据需要自己覆盖原有的该方法,也可以利用Object自带的hashcode方法;应该都学过散列的冲突问题,因此可以想到散列值对应的位置存储的该是一个存放引用的列表;相应的就会理解在HashMap中为什么要弄个容量和负载因子的设置了,如果能够估计HashMap的使用量,合理的预先设置会减少再散列以及查找元素带来的性能损耗问题。
(5)同步控制:容器在①防止多个进程同时修改同一个容器②获取迭代之后(iterator),遍历完之前,的插入、删除等操作都有报错。
(6)持有引用:主要说了为垃圾回收提供了不同级别的间接提示的Java.lang.ref类库:
SoftReference(对内存敏感的高速缓存)、WeakReference(“规范映射”,同一个值只有一个存储空间)、PhantomReference(最弱,依赖于ReferenceQueue);
具体的List、Set、Map的使用,下面的表更有用。
Stack
栈(Stack)是限制仅在表的一端进行插入和删除运算的线性表。
java 没有栈这样的数据结构,如果想利用先进后出(FILO)这样的数据结构,就必须自己实现。
要实现Stack,至少应该包括:
1. pop() 出栈操作,弹出栈顶元素。
2. push(E e) 入栈操作
3. peek() 查看栈顶元素
4. isEmpty() 栈为空
另外,实现一个栈,还应该考虑到几个问题:
1. 栈的初始大小以及栈满以后如何新增栈空间
2. 对栈进行更新时需要进行同步
有三种实现的方式,数组,容器,以及链表的方法。
数据:
package gsm;
import java.util.*;
public class StackArray{
private int[] array;//用数组实现
private int top; //栈顶指针
private final static int size = 100;
public StackArray(){
array = new int[size];
top = -1; //栈空的时候
}
//压栈
public void push(int element){
if(top == size-1){
throw new StackOverflowError();
}
else
array[++top] = element;
}
//弹栈
public int pop(){
if(top == -1){
throw new EmptyStackException();
}
return array[top--];
}
//判断是否为空
public boolean isEmpty(){
return top == -1;
}
//返回栈顶元素
public Integer peek(){
if(top == -1){
throw new EmptyStackException();
}
return array[top];
}
}
容器
package gsm;
public interface Stack<T> {
public T pop();
public void push(T element);
public boolean isEmpty();
public T peek();
}
package gsm;
import java.util.*;
public class StackList<T> implements Stack<T> {
private List<T> list ; //用容器实现
StackList(){
list = new ArrayList<T>();
}
//弹栈
public T pop(){
if(this.isEmpty() == true){
throw new EmptyStackException();
}
return list.remove(list.size()-1);
}
//压栈
public void push(T element){
list.add(element);
}
//判断是否为空
public boolean isEmpty(){
return list.size() == 0;
}
//返回栈顶元素
public T peek(){
if(this.isEmpty() == true){
throw new EmptyStackException();
}
return list.get(list.size()-1);
}
}
链表
package gsm;
import java.util.EmptyStackException;
public class LinkedStack<T> implements Stack<T>{
//不用容器或者数组等数据结构存储节点
//Node定义一个节点类
private static class Node<U>{
private U item; //存储的data
private Node<U> next; //类似指针
Node(){
this.item = null;
this.next = null;
}
Node(U item, Node<U> next){
this.item = item;
this.next = next;
}
boolean end(){
return item == null && next == null;
}
}
private Node<T> top ; //栈顶指针
LinkedStack(){
top = new Node<T>();
、Arrays类:提供了操作数组的一些static方法,如
fill() 用于以某个值填充整个数组。
sort() 用于对数组的排序,需要参数的类型实现Comparable接口
binarySearch() 用于在已经排序的数组中查找元素。
asList() 接受任意的数组为参数,并将其转变为List容器。
……
2、如果一个字符串需要多次改变其值,那么应该使用更高效的StringBuffer取代String。
3、复制数组System.arraycopy(),用它复制数组比用for循环复制要快得多。
4、基本类型数组和对象数组都可以复制,然而,如果复制对象数组,那么只是复制了对象的引用——而不是对象本身的拷贝。这被称作“浅复制”。
5、数组的比较:
Arrays .equal()
import java.util.Arrays;
public class Test{
public static void main(String[] args){
String[] s1 = new String[5];
Arrays.fill(s1,"Hi");
String[] s2 = {"Hi", "Hi", "Hi", "Hi", "Hi"};
System.out.println(Arrays.equals(s1, s2));
System.out.println(s1.equals(s2));
System.out.println(s1==s2);
}
}
1).对于Arrays.equals ,S1的所以元素指向同一个对象,而数组s2包含五个相互独立的对象,然而,数组相等是基于内容的(通过Object.equal()比较),所以结果为true。
2).但是后两个语句输入都是false。原因呢?
6、数组元素的比较:
1).Java有两种方式来提供比较功能。第一种是实现java.lang.Comparable接口,此接口中只有compareTo(Object o)一个方法。
2).实现Comparator接口,区中有俩个方法compare(Object o1, Object o2)和equals(Object o)两个方法。
7、Java标准库:针对基本类型设计的“快速排序(Quicksort)”,针对对象设计的“稳定归并排序”。
8、若找到目标,Arrays.binarySearch()产生的返回值等于或大于。否则,它将产生负返回值,表示若要保持数组的排序状态,此目标元素所应该插入的位置,这个负值得计算方式是:
-(插入点)- 1;
插入点是指,第一个大于查找对象的元素在数组的位置。如果数组中所有元素都小于要查找的对象吗,“插入点”就等于被查找数组的大小size()。
如果数组中包含重复的元素,则无法保证找到的是哪一个。此算法确实不是专为包含重复元素的数组而设计的,不过任然可用。
如果需要对没有重复元素的数组排序,可以使用TreeSet(保持数组顺序),或者LinkedHashSet(保持插入顺序.。
9、基本数据数组无法使用Comparator进行排序。
10、在使用binarySearch()时必须提供同样地Comparator(使用方法的重载版本)。
11、数组是效率最高的保持一组对象的方式,它是你的第一选择。而且,如果要保存基本类型,则只能用数组。
12、 Java容器类类库的用途是"保存对象",并将其划分为两个不同的概念:
1).Collection。一个独立的元素,这些元素都服从一条或多条规则。List必须保存元素特定的顺序,而Set不能有重复元素。
2).Map。一组成对的”键值对“对象。初看起来这似乎应该是一个Collection,其元素是成对的对象,但是这样的设计实现起来太笨拙了,于是我们将Map明确提取出来形成一个独立的概念。另一方面,如果使用Collection表示Map的部分内容,会便于查看此部分内容。因此Map可以返回所有键组成的Set,所有值组成的Collection,或其键值对组成的Set;并且象数组一样容易扩展成多维Map,无需增加新的概念,只要让Map中键值对的每个“值”也是一个Map即可(此Map中的“值”还可以是Map,依此类推)。
13、
1).List按对象进入的顺序保存对象,不做排序或编辑操作。
2).Set对每个对象只接受一次,并使用自己内部的排序方法(通常,你只关心某个元素是否属于Set,而不关心它的顺序--否则应该使用List)。
3).Map同样对每个元素保存一份,但这是基于"键"的,Map也有内置的排序,因而不关心元素添加的顺序。
4).如果添加元素的顺序对你很重要,应该使用 LinkedHashSet或者LinkedHashMap.、
14、Collections中的fill()方法也是只复制同一个对象的引用来填充整个容器,并且只对List对象有用。
15、容器的缺点:将对象加入容器的时候就丢失了类型信息。容器只保存对Object的引用,Object是所有类的基类,因此容器可以保存任何类型的对象。(当然不包括基本类型,因为它们不是真正的对象,没有继承任何东西。)
16、如果原本是使用ArrayList,但是后来考虑到容器的特点,该用Set,那该怎么做?或者你打算写通用的代码,它们只是使用容器,不知道或者说不关心容器的类型,那么如何才能不重写代码就可以应用与不同类型的容器?
迭代器(也是一种设计模式)的概念可以用于达成次目的。迭代器是一个对象,它的工作是遍历并选择序列中的对象,而客户端程序员不必知道或关心该序列底层的结构(也就是不同容器的类型)。此外,迭代器通常被称为"轻量级"对象:创建它的代价小。
17、Collection中并不包括随机访问所选择元素的get()方法。因为Collection包括Set,而Set是自己维护内部顺序的(这使得随机访问变得没意义)。因此,如果你想要检查Collection中的元素,必须使用迭代器。
18、Java中,Vector、Stack和Hashtable已经过时。
19、List的功能方法
如果再开发环境中,性能开销主要由其他因素产生,那么ArrayList与LinkedList之间的开销差异就不重要了,无论使用哪一种都可以。
20、Set的功能方法:
Set具有与Collection完全一样得接口,因此没有任何额外的功能。实际上Set就是Collection,只是行为不同。(这是继承与多态思想的典型应用:表现不同的行为。)Set不保存重复的元素。
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.TreeSet;
public class TestSet{
static void fill(Set<Object> s){
//split根据匹配给定的正则表达式来拆分此字符串。
s.addAll(Arrays.asList("one two three four five six seven".split(" ")));
}
public static void text(Set<Object> s){
//replaceAll(String regex,String replacement)
//使用给定的 replacement替换此字符串所有匹配给定的正则表达式的子字符串。
System.out.println(s.getClass().getName().replaceAll("\\w+\\.", ""));
//调用多次:为了验证Set不保存重复的元素。
fill(s);
fill(s);
fill(s);
System.out.println(s);
s.addAll(s);
s.add("one");
s.add("one");
s.add("one");
System.out.println(s);
System.out.println("s.contains(\"one\"):" + s.contains("one"));
}
public static void main(String[] args){
text(new HashSet<Object>());
text(new TreeSet<Object>());
text(new LinkedHashSet<Object>());
}
}
运行结果:
从运行结果可以注意到,HashSet所维护的元素的次序不同于TreeSet和LinkedHashSet,因为它们保存元素以便以后还能找到该元素的方式个不相同。
1).TreeSet采用红黑树的数据结构排序元素。
2).HashSet则采用散列函数,这是专门为快速查询二设计的。
3).LinedHashSet内部使用散列加快查询速度,同时使用链表维护元素的次序。
20、使用HashSet和TreeSet这两种情况下,都必须为类定义equals();而hashCode(),只有在类被HashSet用到的时候才是必要的(这种可能性很大,因为HashSet通常是使用Set的第一选择)。无论如何,作为一种编程风格,当覆盖equals()的时候,就应该同时覆盖hashCode()。
21、Map的功能方法:
“键(key)“必须是唯一的,而”值(value)“可以有重复。
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
class Counter{
int i = 1;
public String toString(){
return Integer.toString(i);
}
}
public class Statistics{
private static Random rand = new Random();
public static void main(String[] args){
Map<Object, Object> m = new HashMap<Object, Object>();
for(int i = 0; i < 10000; i++){
Integer r = new Integer(rand.nextInt(20));
if(m.containsKey(r)){
((Counter)m.get(r)).i++;
}
else{
m.put(r, new Counter());
}
}
System.out.println(m);
}
}
这里为什么不之间用Integer而是另外写一个Counter类呢?
原因:Java包装器类(如这里的Integer)的对象一旦创建,就无法改变它的值。
22、使用自己创建的类作为HashMap的“键”,需要同时覆盖hashCode()和equals()。原因如下:
1).默认的hashCode()继承自Object,它默认是使用对象的地址计算散列码。
因此即使俩个对象的实例的内容相同,对象的地址是不同的,所以默认的hashCode()得到的散列码是不同的。如下例子:
import java.util.HashMap;
import java.util.Map;
public class TestHashMap{
private int id;
public TestHashMap(int id){
this.id = id;
}
public void func(){
System.out.println(id);
}
public String toString(){
return "TestHashcode#" + id;
}
public static void main(String[] args){
TestHashMap testHashcode = new TestHashMap(2012);
Map<Object, Object> hashMap = new HashMap<Object, Object>();
hashMap.put(testHashcode, 2012);
if(hashMap.containsKey(testHashcode)){
System.out.println("找到id为:" + testHashcode.id+"的TestHashcode");
}
else{
System.out.println("找不到id为:" + testHashcode.id+"的TestHashcode");
}
TestHashMap testHashcode1 = new TestHashMap(2012);
if(hashMap.containsKey(testHashcode1)){
System.out.println("找到id为:" + testHashcode1.id+"的TestHashcode");
}
else{
System.out.println("找不到id为:" + testHashcode1.id+"的TestHashcode");
}
System.out.println("--------------------------------");
System.out.println("testHashcode散列码为:"+testHashcode.hashCode());
System.out.println("testHashcode1散列码为:"+testHashcode1.hashCode());
}
}
运行结果:
出现了“找到”与“找不到”这种尴尬的处境。
2).这时,自己覆盖Oject的hashCode(),在TestHashMap中添加:
public int hashCode(){
return id;
}
再运行,结果如下:
3).这是因为HashMap使用equals()判断当前的“键”是否与表中存在的“键”相同,而默认equals()是继承自Object, Object中的Object.equals()只是比较对象的地址。于是再向TestHashMap添加:
public boolean equals(Object o){
// o instanceofTestHashMap检查o是否是TestHashMap的实例
return (o instanceof TestHashMap)
&& (id == ((TestHashMap)o).id);
}
运行结果:
综上:自定义的类,如果不为“键”覆盖hashCode()和equals(),那么在使用散列的数据结构是(HashSet、HashMap、LinkedHashSe,或LinkedHashMap),就无法正确处理你的“键“。
23、散列的价值在于速度:散列使得查询得以快速进行。由于瓶颈位于“键“的查询速度,散列使用数组(存储一组元素最快的数据结构)来保存散列”键“信息。数组并不保持”键“本身,而是通过”键“对象生成一个数字,将其作为数组的小标。这个数字就是散列码,由定义在Object中的。且可能由你的类覆盖的hashCode()生成。为解决数组容量被固定的问题,不同的”键“可以产生相同的下标,出现冲突。
24、查询一个“值“:计算散列码,使用散列码查询数组。出现冲突,由“外部链接“处理,这时数组并不是直接保存”值“,而是保存”值“的list。然后对list中的”值“使用equals()方法进行线性的查询。
25、ArrayList底层由数组支持,而LinkedList是由双向链表实现的。因此,如果经常要在表中插入和删除元素,LinkedList就比较适合(LinkedList还有建立在AbstractSequentialList基础上的其他功能);否则,应该使用速度更快的ArrayList。
26、树的行为方式是:它总是处于排序状态。TreeSet、HashMap都是排序好的。
27、只有在执行binarySearch()之前,才确实需要对List或数组进行排序。
28、总结:
1).
数组将数字与对象联系起来。它保存类型明确的对象,查询对象时,不需要对结果做类型转换。它可以是多维的,可以保存基本类型的数据。但是,数组一旦生成,其容量就不能改变。
2).Collection保存单个的元素,而Map保存相关联的键值对。
3).像数组一样,List也建立数字与对象的关联,可以认为数组和List都是排好序的容器。List能够自动扩充容量。但是List不能保存基本类型,只能保存Object的引用,因此必须对从容器中取出的Object结果做类型转换(RTTI)。
4).如果要进行大量的随机访问,就使用ArrayList;如果要经常从List中间插入或删除元素,则应该使用LinkedList。
5).
队列、双向队列以及栈的行为,由LinkedList提供支持。
6).Map是一种将对象与对象相关联的设计。HashMap着重于快速访问;TreeMap保持“键”始终处于排序状态,所以没有HashMap快。LinkedHashMap保持元素插入的顺序,也可以使用LRU(“最近最少使用“)算法对其重排序。
7). Set不接受重复元素。HashSet提供最快的查询速度,TreeSet保持元素处于排序状态。LinkedHashSet以插入顺序保存元素。
8).新程序中不应该使用过时的Vector、Hashtable和Stack。
Java 容器类库:抽象类和遗留构建.
可以看看看源码。比如HashMap这种容器就是看数组的组织形式和查询访问处理过程. 就是看
几个常用接口,比如put,get这些以及构造函数,就是数据初始化的时候都做了什么。
只有四种容器:List, Set, Queue, Map
Collection: List, Set, Queue
Collection接口的定义
interface Collection<T> {
boolean add(T e);
boolean addAll(Collection<? extends T> c);
void clear();
boolean contains(Object o);
boolean containsAll(Collection<?> c);
boolean isEmpty();
Iterator<T> iterator();
boolean remove(Object o);
boolean removeAll(Collection<?> c);
boolean retainAll(Collection<?> c);
int size();
Object[] toArray();
<T> T[] toArray(T[] a);
}
Collection接口不包括随机访问元素get()方法,因为Collection要包括Set. Set是自己维护内部顺序的(这样随机访问就没有意义了),因此如果想检查Collection中的元素,就要使用迭代器Iteration。
ArrayList来保存数据集,然后向上转型为Collection:Collection c = new ArrayList(); 如果想用List额外的功能,如返回子集,需要转型:Collection c3 = ((List) c).subList(3,5);
通过Collection接口定义的源码可以看出,
3.1 是用模板实现的,适合各种类型,代码复用性更好了。
3.2 所有...All方法接收的参数都是Collection对象。也就是List, Set,Queue,通过向上转型提高了代码的复用性。
3.3 Collection定义了Iterator函数。用于访问容器元素。
List 接口的定义
interface List<T> extends Collection<T> {
void add(int index, T element);
void add(int index, T element);
boolean addAll(int index, Collection<? extends T> c);
T get(int index);
int indexOf(Object o);
int lastIndexOf(Object o);
ListIterator<T> listIterator();
ListIterator<T> listIterator(int index);
T remove(int index);
T set(int index, T element);
List<T> subList(int fromIndex, int toIndex);}
Iterator 接口的定义
interface Iterator<T> {
boolean hasNext();
T next();
void remove();
}
ListIterator 接口的定义(双向,支持向前和向后遍历,同时支持add和修改)
interface ListIterator<T> {
void add(T e);
boolean hasPrevious();
int nextIndex();
T previous();
int previousIndex();
void set(T e);
void remove();
}
ArrayList是支持泛型的,它继承自AbstractList,实现了List、RandomAccess、Cloneable、java.io.Serializable接口。
他仅有的两个私有属性:
private transient Object[] elementData ;
private int size;
具体说来:ArrayList是用数组实现的。ArrayList最重要的特征就是动态数组,即可以动态扩容。这是怎么实现的呢?
主要技术是使用了 elementData = Arrays.copyOf(elementData, newCapacity); 数组的复制。扩容时把原来数组里的所有元素拷贝到新创建的数组中,其中对数组的拷贝与移动大量使用了 System.arraycopy
http://blog.csdn.net/jzhf2012/article/details/8540410
http://blog.csdn.net/crave_shy/article/details/17436773
关于迭代器再说两句:
private class Itr implements Iterator {
int cursor = 0;
int lastRet = -1;
int expectedModCount = modCount;
}
Itr 依靠三个int变量实现遍历。cursor是当前位置,第一次调用 next() 将返回索引为 0 的元素。lastRet 记录上一次游标所在位置,因此它总是比 cursor 少 1。
方法 next(): 返回的是索引为 cursor 的元素 ,然后修改 cursor 和 lastRet 的值:
public Object next() {
checkForComodification();
try {
Object next = get( cursor);
lastRet = cursor ++;
return next;
} catch(IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
Set
Set对存储的元素要求是唯一性: 所以要通过equals来确保对象的唯一性。
Set接口的定义
public interface Set<E> extends Collection<E> {
int size();
boolean isEmpty();
boolean contains(Object o);
Iterator<E> iterator();
Object[] toArray();
<T> T[] toArray(T[] a);
boolean add(E e);
boolean remove(Object o);
boolean containsAll(Collection<?> c);
boolean addAll(Collection<? extends E> c);
boolean retainAll(Collection<?> c);
boolean removeAll(Collection<?> c);
void clear();
boolean equals(Object o); //Set 自己独有的
int hashCode(); //Set 自己独有的
}
class SetType {
int i ;
public SetType( int n){
i = n;
}
//判断是否相等1:类型相等 2:值相等
public boolean equals(Object o){
return o instanceof SetType && ( i==((SetType)o). i);
}
public String toString(){
return Integer.toString( i);
}
}
HashSet
(适合快速查找)
HashSet 底层是使用 HashmMap 实现的。 当使用 add 方法将对象添加到Set当中时,实际上是将该对象作为底层所维护的 Map 对象的 key,而 value 则都是同一个 Object 对象.
class HashType extends SetType{
HashType( int n){
super(n);
}
public int hashcode(){
return this .i ;
}
}
TreeSet
//默认构造函数。使用该构造函数,TreeSet中的元素按照自然排序进行排列。
TreeSet()
// 创建的TreeSet包含collection
TreeSet(Collection<? extends E> collection)
// 指定TreeSet的比较器
TreeSet(Comparator<? super E> comparator)
// 创建的TreeSet包含set
TreeSet(SortedSet<E> set)
继承结构:TreeSet->SortedSet->Set(保持了次序:按照元素的插入顺序保存元素,底层是树,元素必须实现Comparable接口,可排序)
SortedSet(接口:按对象的比较函数对元素排序)
TreeSet 中的元素支持 2 种排序方式:自然排序 或者 根据创建 TreeSet 时提供的 Comparator 进行排序。这取决于使用的构造方法。TreeSet实际是TreeMap实现的,
因为TreeSet是Set所以支持add,remove,因为有序,就支持get.
TreeSet 支持的基本操作(add、remove 和contain) 提供受保证的 log(n) 时间开销。
因为执行了NavigableSet,TreeSet拥有了导航方法:
4.1 提供元素项的导航方法,返回某个元素。
4.2 另一类是提供集合的导航方法,返回某个集合。
subSet(fromElement, toElement )
headSet(toElement)
tailSet(fromElement)
http://www.cnblogs.com/skywang12345/p/3311268.html
http://www.fengfly.com/plus/view-213903-1.html
class TreeType extends SetType implements Comparable<TreeType>{
TreeType( int n){
super (n);
}
public int compareTo(TreeType arg){
return (arg.i < this. i ? -1 :(arg. i== this .i ?0:1));
}
}
LinkedHashSet
(具有HashSet的查询速度,内部使用链表维护(次序),因此保持了次序:按照元素的插入顺序保存元素)
SortedSet
(接口:按对象的比较函数对元素排序)
Queue:
队列是典型FIFO先进先出容器。队列通常被当做一种可靠的将对象从程序的某个区域传输到另一个区域的途径。队列在并发编程特别重要,因为他们可以安全地将对象从一个任务传输给另一个任务。他的两个实现LinkedList和PriorityQueue,差异是排序,而非性能
Queue 接口的定义:
interface Queue<E>
//throws excexption
boolean add(E e);//将元素插入队尾
E remove();//移出返回队头
E element(); //返回队头
//return special value
boolean offer(E e); //将元素插入队尾
E poll();//移出返回队头
E peek();//返回队头
PriorityQueue根据优先级确定下一个移出对列的元素。放入PriorityQueue的元素需要实现Comparable接口,最小的元素会先在队首。
当你在PriorityQueue上调用offer()方法来插入一个对象时,这个对象会在队列中被排序(这依赖具体实现,PriorityQueue通常会在插入时排序(维护一个堆),但是也可能在移除时选择最重要的元素)。默认的排序是自然排序,但是可以通过自己的Comparator来修改这个顺序(优先级)。
PriorityQueue 接口的定义
boolean add(E e)
void clear()
Comparator<? super E> comparator()
boolean contains(Object o)
Iterator<E> iterator()
boolean offer(E e)
E peek()
E poll()
boolean remove(Object o)
int size()
Object[] toArray()
<T> T[] toArray(T[] a)
优先级自定义举例:
public class ToDoList extends PriorityQueue<ToDoList.ToDoItem>{
static class ToDoItem implements Comparable<ToDoItem>{
private char primary;
private int secondary;
private String item;
public ToDoItem(String td, char pri, int sec){
this.primary = pri;
this.secondary = sec;
this.item = td;
}
// 因为类型 E 是自定义的 ToDoItem,为了能够定义优先级 - 排序
// 要 implements Comparable, 定义 compareTo
public int compareTo(ToDoItem arg){
if(this.primary > arg.primary){
return +1;
}
if(this.primary == arg.primary){
if(this.secondary < arg.secondary)
return -1;
else if(this.secondary > arg.secondary)
return 1;
else if(this.secondary == arg.secondary )
return 0;
}
return -1;
}
public String toString(){
return Character.toString(primary)+" "+this.secondary+" "+item;
}
}
public void add(String td, char pri, int sec){
super.add(new ToDoItem(td, pri, sec));
}
public static void main(String[] args) {
// TODO Auto-generated method stub
}
}
Map
Map接口的定义
interface Map<K, V> {
void clear();
boolean containsKey(Object key);
boolean containsValue(Object value);
V get(Object key);
V put(K key, V value);
boolean isEmpty();
Set<java.util.Map.Entry<K, V>> entrySet();
Set<K> keySet();
void putAll(Map<? extends K, ? extends V> m);
V remove(Object key);
int size();
Collection<V> values();
}
interface Map.Entry<K, V> {
K getKey();
V getValue();
V setValue(V value);
boolean equals(Object o); //定义equals方法:判断两个Entry是否相同
int hashCode(); //定义获取hashCode的方法
}
Map 接口有6种实现:
HashMap 下面详细介绍,缺点线程不安全
LinkedHashMap 类似HashMap,迭代遍历时候,取得key-value pair的顺序是它的插入或者是LRU(最近最少使用)次序。
TreeMap 实现了SortedMap, 类似TreeSet的一些方法。红黑树的实现,所得到的结果是经过排序(次序由comparable和comparator决定)的。
WeakHashMap
ConcurrentHashMap 线程安全的Map.
IdentityHashMap
如果不为自己创建的类覆盖hashCode()和equals(),那么使用散列数据结构(HashSet,HashMap, LinkedHashSet,或者LinkedHashMap)就无法正确处理你的键。
HashMap类
HashMap继承自AbstractMap,实现了Map接口(这些内容可以参考《Java集合类》)。来看类的定义。
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable
为什么要使用HashMap?
可以使用List作为基本结构,再覆盖hashCode()和equals()就可以实现基本的Map功能。
但是问题是对键的查询,键没有任何特定的顺序保存,所以只能使用线性查询,而线性查询是最慢的查询方式。
所以这里要使用一种叫哈希表(散列)的数据结构。这个DS综合(折中)了数组和链表的优点。
hash对每个要存储的数据进行计算:hash(key.hashCode),得到的哈希值是数组的index.
通过看HashMap源码,可以发现:
1. HashMap底层是用Entry数组实现的,我们放进HashMap的元素,实际上是放进数组中的。
数组名叫table。Entry<K,V>是Map.Entry<K,V>的实现。
table 当需要的时候会扩容resize。大小必须是2的幂。
2. [散列表实现机制] 当向 HashMap 中put 一对key-value pair时候,它会根据 key 的 hashCode 值计算出一个位置,该位置就是此对象准备往数组中存放的位置。
如果该位置没有对象存在,就将此对象直接放进数组中;
如果该位置有对象存在,则顺着此对象存在的链开始寻找(Entry 类有一个 Entry 类型的 next 成员变量,指向了该对象的下一个对象),如果此链上有对象的话,再去使用 equals 方法进行比较,如果此链上的某个对象equals方法比较为 false , 则将该对象放到数组中,然后将数组中该位置以前存在的那个对象链接到此对象后面。
遍历方法
Map<Integer, Integer> map = new Hashmap<Integer, Integer>();
Iterator it = map.entrySet().iterator();
while(it.hasNext()){
Map.Entry entry = (Map.Entry ) it.next();
Object key = entry.getKey();
Object value = entry.getValue();
}
HashMap使用了散列码hashcode来取代对key的缓慢搜索。hashcode相对唯一,代表对象。
可以实现什么算法?
1. 统计分布,比如统计Random随机数的分布。
for( i=0;i<N; i++){
int r = rand.nextInt(20); //key
Integer freq = map.get(r);//返回value
if(freq == null){
map.put(r,1);
} else{
map.put(r,freq+1);
}
}
http://www.cnblogs.com/hzmark/archive/2012/12/24/hashmap.html
备注:
1. hashCode()
http://www.oschina.net/question/82993_75533 如何正确重写hashcode和equal方法,参考这个文章。
设计hashCode()时最重要的因素就是:无论何时,对同一个对象调用hashCode()都应该生成同样的值。所以,如果你的hashCode()方法依赖于对象中易变的数据,用户就要当心了,因为此数据发生变化时,hashCode()就会产生一个不同的散列码,相当于产生一个不同的键。
此外,也不应该使hashCode()依赖于具有唯一性的对象信息,尤其是使用this的值。这很糟糕!因为这样做无法生成一个新的键,使之与put()中原始的键值对中的键相同。所以,应该使用对象内有意义的标识符。
尽量保证使用对象的同一个属性来生成hashCode()和equals()两个方法。在Employee的案例中,我们使用员工id。
eqauls方法必须保证一致(如果对象没有被修改,equals应该返回相同的值).任何时候只要a.equals(b),那么a.hashCode()必须和b.hashCode()相等。两者必须同时重写。
数组为什么特殊
数组和其它容器之间的区别有三方面:效率、编译期类型检查和保存基本类型的能力,但是由于有了泛型容器和自动包装机制,现在最主要的区别就是效率。ArrayList 效率低很多。
数组是一种效率最高的存储和访问对象引用序列的方式。
数组是第一级对象
初始化数组的2种方式:
1. 聚集初始化:
//aggregate initialization
BerylliumSphere[] d = { new BerylliumSphere(),
new BerylliumSphere(),
new BerylliumSphere(),
new BerylliumSphere()};
动态聚集初始化:
//dynamic initialization
a = new BerylliumSphere[]{ new BerylliumSphere(), new BerylliumSphere()};
可以在任意位置创建和初始化数组对象 e.g.
hide(new BerylliumSphere[]{ new BerylliumSphere())
返回一个数组
多维数组
new 来分配:int [][][] a = new int[2][2][4];
Arrays.deepToString输出多维数组
数组中构成矩阵的每个向量都可以具有任意的长度 -- 粗糙数组:
[[Sphere 1, Sphere 2], [Sphere 3, Sphere 4, Sphere 5, Sphere 6], [Sphere 7, Sphere 8, Sphere 9, Sphere 10, Sphere 11, Sphere 12, Sphere 13, Sphere 14]]
数组与泛型
创建测试数据
Arrays.fill() 只能用同一个值填充各个位置,或者是填充某个区域
数据生成器
从Generator中创建数组,为了更灵活的创建更有意义的数组。
Arrays实用功能
Arrays 类有一套用于数组的静态方法(所有这些方法对各种基本类型和 Object 类而重载过):
方法说明equals()比较两个数组是否相等deepEquals()用于多维数组比较fill()填充数组sort()数组排序binarySearch()在已经排序的数组中查找元素toString产生数组的 String 表示hashCode()产生数组的散列码asList()接受任意的序列或数组作为其参数,并转换为 List 容器复制对象数组:只是复制了对象的引用——而不是对象本身的拷贝。
数组元素的比较: comparable and comparator
数组排序:Arrays.sort(sa);
使用内置的排序方法,可以对任意的基本类型数组排序;也可以对对象数组排序,只要该对象实现了Comparable接口,或者具有相关联的Comparator:
Java标准库中的排序算法针对正排序的特殊类型进行了优化: 针对基本类型的快速排序;针对对象设计的“稳定归并排序”。所以无需担心排序的性能问,除非确实确定了排序不分是程序效率的瓶颈。
在已排序的数组中查找:Arrays.binarySearch()
int location = Arrays.binarySearch(a, r)
如果需要对没有重复元素的数组排序,可以使用TreeSet(保持排序顺序),或者LinkedHashSet(保持插入顺序)
一维数组的声明方式:
type var[]; 或type[] var;
声明数组时不能指定其长度(数组中元素的个数),
Java中使用关键字new创建数组对象,格式为:
数组名 = new 数组元素的类型 [数组元素的个数]
实例:
TestNew.java:
程序代码:
public class TestNew
{
public static void main(String args[]) {
int[] s ;
int i ;
s = new int[5] ;
for(i = 0 ; i < 5 ; i++) {
s[i] = i ;
}
for(i = 4 ; i >= 0 ; i--) {
System.out.println("" + s[i]) ;
}
}
}
初始化:
1.动态初始化:数组定义与为数组分配空间和赋值的操作分开进行;
2.静态初始化:在定义数字的同时就为数组元素分配空间并赋值;
3.默认初始化:数组是引用类型,它的元素相当于类的成员变量,因此数组分配空间后,每个元素也被按照成员变量的规则被隐士初始化。
实例:
TestD.java(动态):
程序代码:
public class TestD
{
public static void main(String args[]) {
int a[] ;
a = new int[3] ;
a[0] = 0 ;
a[1] = 1 ;
a[2] = 2 ;
Date days[] ;
days = new Date[3] ;
days[0] = new Date(2008,4,5) ;
days[1] = new Date(2008,2,31) ;
days[2] = new Date(2008,4,4) ;
}
}
class Date
{
int year,month,day ;
Date(int year ,int month ,int day) {
this.year = year ;
this.month = month ;
this.day = day ;
}
}
TestS.java(静态):
程序代码:
public class TestS
{
public static void main(String args[]) {
int a[] = {0,1,2} ;
Time times [] = {new Time(19,42,42),new Time(1,23,54),new Time(5,3,2)} ;
}
}
class Time
{
int hour,min,sec ;
Time(int hour ,int min ,int sec) {
this.hour = hour ;
this.min = min ;
this.sec = sec ;
}
}
TestDefault.java(默认):
程序代码:
public class TestDefault
{
public static void main(String args[]) {
int a [] = new int [5] ;
System.out.println("" + a[3]) ;
}
数组:是一组相关变量的集合
数组是一组相关数据的集合,一个数组实际上就是一连串的变量,数组按照使用可以分为一维数组、二维数组、多维数组
数据的有点
不使用数组定义100个整形变量:int i1;int i2;int i3
使用数组定义 int i[100];
数组定义:int i[100];只是一个伪代码,只是表示含义的
一维数组
一维数组可以存放上千万个数据,并且这些数据的类型是完全相同的,
使用java数组,必须经过两个步骤,声明数组和分配内存给该数组,
声明形式一
声明一维数组:数据类型 数组名[]=null;
非配内存给数组:数组名=new 数据类型[长度];
声明形式二
声明一维数组:数据类型 [] 数组名=null;
java数据类型分为两大类
基本数据类型
int、long操作的时候本身就是具体的内容
引用数据类型:数组、类、接口
引用传递的就是一个内存的使用权,一块内存空间,可能有多个人同时使用
事例声明数组
package com.qn.array;
public class Test {
public static void main(String[] args) {
int score[]=null;//声明数组
score=new int[3];//开辟空间,大小为3
}
}
数组的声明格式里,数据类型是数组元素的数据类型,常见的有整形、浮点型、与字符型等
数组名是用来统一这组相同数据类型元素的名称,其命名规则和变量的相同
数组声明后实际上是在栈内存中保存了此数组的名称,结下了是要在堆内存中配置数组所需要的内存,齐产固定是告诉编译器,所声明的数组要存放多少个元素,而new 则是命令编译器根据括号里的长度
基本数据类型偶读有其默认值:int 0;只要是引用数据类型默认值就是null
事例
package com.qn.array;
public class Test {
public static void main(String[] args) {
int score[]=null;//声明数组
score=new int[3];//开辟空间,大小为3
System.out.print('score[0]='+score[0]);
System.out.print('\tscore[1]='+score[1]);
System.out.print('\tscore[2]='+score[2]);
}
}
堆栈内存的解释
数组操作中,在栈内存中保存的永远是数组的名称,只开辟了栈内的空间,数组是永远无法使用的,必须有指向的对内存才可以使用,要想开辟新对内存空间必须使用new关键字,之后就是将对内存的使用权交给对应的栈内存,而且一个堆内存空间可以同时被多个栈内存空间指向,比如一个人可以有多个名字,人就相当于对内存,名字就相当于栈内存
声明数组的同时分配内存空间
声明数组的同时非配内存
数据类型 数组名[]=new 数据类型[个数]
int score[]=new int[10];
声明一个元素个数为10的整形数组score,同时开辟依靠内存空间工期使用
java中,由于整形数据类型占用的空间为4个byte,而整个数组score可保存的元素有10个。所以上例中占用的内存共有4*10=40个字节
数组的访问
数组中元素的表示方法
想要访问数组里的元素可以利用索引来完成,java的数组索引标号由10开始,以一个score[10]的整形数组为例,score[0]代表第一个元素
一直向下,最后一个为score[9]
取得数组的长度
在java中取得数组的长度(也就是数组元素的长度)可以利用数组名称.length完成,
数组名称.length--返回一个int类型的数据
package com.qn.array;
public class Test {
public static void main(String[] args) {
int score[]=null;//声明数组
score=new int[3];//开辟空间,大小为3
System.out.println(score.length);
}
}
结果
数组的静态初始化
之前的数组,所采用的都是动态初始化,所有的内容在数组声明的时候并不具体的指定,而是以默认值的形式出现
静态初始化是指在数组声明后直接为数组指定具体的内容
如果想要直接在声明的时候给数组赋初始值,可以采用大括号完成,只要在数组的生命格式后面加上初值的赋值即可,
数据类型 数组名 []={初始值0,初始值1,初始值3,....初始值n};
package com.qn.array;
public class Test {
public static void main(String[] args) {
int score[]={1,2,3,4,5,6};//使用静态初始化声明数组
System.out.println(score.length);
}
}
结果
6
范例求出数组中的最大值和最小值
package com.qn.array;
public class Test {
public static void main(String[] args) {
int score[]={45,12,8,23,89,56};//使用静态初始化声明数组
int max=0;
int min=0;
max=min=score[0];
for (int i = 0; i < score.length; i++) {
if(score[i]>max){
max=score[i];
}
if(score[i]
min=score[i];
}
}
System.out.println('最大值:'+max);
System.out.println('最小值:'+min);
}
}
结果
范例排序,在操作中排序是比较常用的
从大到小
package com.qn.array;
public class Test {
public static void main(String[] args) {
int score[]={45,12,8,23,89,56};//使用静态初始化声明数组
int temp=0;
for (int i = 0; i < score.length; i++) {
for (int j = 0; j < score.length-1; j++) {
if(score[i]>score[j]){
temp=score[i];
score[i]=score[j];
score[j]=temp;
}
}
}
for (int i = 0; i < score.length; i++) {
System.out.print(score[i]+'\t');
}
}
}
结果
这个时候不要被i值所迷惑 if(score[i]>score[j]){
这一步主要知识为了比较,实际上完成之后输出的时候是根据j的值排序的
二维数组
如果可以把一维数组当做几何中的一条线图形,那么二维数组就相当于一个表格
A B
1 姓名 年龄
2 齐宁 21
3 齐燕 23
4 齐威 26
二维数组声明的方式和以为数组的类似,内存分配也一样是用new这个关键字
其实声明与分配内存的格式如下
动态初始化
数据类型 数组名[][];
数组名=new 数据类型[行的个数][列的个数];
声明并初始化数组
数据类型 数组名[][]=new 数据类型[行的个数][列的个数];
静态初始化
二维数组的存储
声明二维数组score 同时开辟一段内存空间
int score[][]=new int[4][3];
整体数据score可保存的元素是4*3=12个,在java中,int数据类型所占用的空间为4个字节,因此该整形数组占用的内存共为4*12=48个字节
事例
package com.qn.array;
public class test1 {
public static void main(String[] args) {
int score[][] = new int[4][3];
score[0][1] = 30;
score[1][0] = 31;
score[2][1] = 32;
score[2][2] = 33;
score[3][1] = 34;
score[1][1] = 35;
for (int i = 0; i < score.length; i++) {
for (int j = 0; j < score[i].length; j++) {
System.out.print(score[i][j]+'\t');
}
System.out.println('');
}
}
}
结果
二维数组静态初始化
用到的时候才会开辟空间,不用的(红色部分)则不开辟空间
多维数组
一般只是用到二维数组
三维数组简单了解
package com.qn.array;
public class test1 {
public static void main(String[] args) {
int score[][][]={
{
{5,1},{6,7}
},
{
{9,4},{8,3}
}
};
System.out.print(score[0][0][0]+'\t');
System.out.print(score[0][0][1]+'\t');
System.out.print(score[0][0][0]+'\t');
System.out.print(score[0][0][1]+'\t');
System.out.print(score[1][0][0]+'\t');
System.out.print(score[1][0][1]+'\t');
System.out.print(score[1][1][0]+'\t');
System.out.print(score[1][1][1]+'\t');
}
}
Java 数组
数组对于每一门编程语言来说都是重要的数据结构之一,当然不同语言对数组的实现及处理也不尽相同。
Java语言中提供的数组是用来存储固定大小的同类型元素。
你可以声明一个数组变量,如numbers[100]来代替直接声明100个独立变量number0,number1,....,number99。
本教程将为大家介绍Java数组的声明、创建和初始化,并给出其对应的代码。
声明数组变量
首先必须声明数组变量,才能在程序中使用数组。下面是声明数组变量的语法:
dataType[] arrayRefVar; // 首选的方法
或
dataType arrayRefVar[]; // 效果相同,但不是首选方法
注意: 建议使用dataType[] arrayRefVar 的声明风格声明数组变量。 dataType arrayRefVar[] 风格是来自 C/C++ 语言 ,在Java中采用是为了让 C/C++ 程序员能够快速理解java语言。
实例
下面是这两种语法的代码示例:
double[] myList; // 首选的方法
或
double myList[]; // 效果相同,但不是首选方法
创建数组
Java语言使用new操作符来创建数组,语法如下:
arrayRefVar = new dataType[arraySize];
上面的语法语句做了两件事:
一、使用dataType[arraySize]创建了一个数组。
二、把新创建的数组的引用赋值给变量 arrayRefVar。
数组变量的声明,和创建数组可以用一条语句完成,如下所示:
dataType[] arrayRefVar = new dataType[arraySize];
另外,你还可以使用如下的方式创建数组。
dataType[] arrayRefVar = {value0, value1, ..., valuek};
数组的元素是通过索引访问的。数组索引从0开始,所以索引值从0到arrayRefVar.length-1。
实例
下面的语句首先声明了一个数组变量myList,接着创建了一个包含10个double类型元素的数组,并且把它的引用赋值给myList变量。
public class TestArray {
public static void main(String[] args) {
// 数组大小
int size = 10;
// 定义数组
double[] myList = new double[size];
myList[0] = 5.6;
myList[1] = 4.5;
myList[2] = 3.3;
myList[4] = 4.0;
myList[5] = 34.33;
myList[6] = 34.0;
myList[7] = 45.45;
myList[8] = 99.993;
myList[9] = 11123;
// 计算所有元素的总和
double total = 0;
for (int i = 0; i < size; i++) {
total += myList[i];
}
System.out.println("总和为: " + total);
}
}
以上实例输出结果为:
总和为: 11354.173
下面的图片描绘了数组myList。这里myList数组里有10个double元素,它的下标从0到9。
处理数组
数组的元素类型和数组的大小都是确定的,所以当处理数组元素时候,我们通常使用基本循环或者foreach循环。
示例
该实例完整地展示了如何创建、初始化和操纵数组:
public class TestArray {
public static void main(String[] args) {
double[] myList = {1.9, 2.9, 3.4, 3.5};
// 打印所有数组元素
for (int i = 0; i < myList.length; i++) {
System.out.println(myList[i] + " ");
}
// 计算所有元素的总和
double total = 0;
for (int i = 0; i < myList.length; i++) {
total += myList[i];
}
System.out.println("Total is " + total);
// 查找最大元素
double max = myList[0];
for (int i = 1; i < myList.length; i++) {
if (myList[i] > max) max = myList[i];
}
System.out.println("Max is " + max);
}
}
以上实例编译运行结果如下:
1.9
2.9
3.4
3.5
Total is 11.7
Max is 3.5
foreach循环
JDK 1.5 引进了一种新的循环类型,被称为foreach循环或者加强型循环,它能在不使用下标的情况下遍历数组。
示例
该实例用来显示数组myList中的所有元素:
public class TestArray {
public static void main(String[] args) {
double[] myList = {1.9, 2.9, 3.4, 3.5};
// 打印所有数组元素
for (double element: myList) {
System.out.println(element);
}
}
}
以上实例编译运行结果如下:
1.9
2.9
3.4
3.5
数组作为函数的参数
数组可以作为参数传递给方法。例如,下面的例子就是一个打印int数组中元素的方法。
public static void printArray(int[] array) {
for (int i = 0; i < array.length; i++) {
System.out.print(array[i] + " ");
}
}
下面例子调用printArray方法打印出 3,1,2,6,4和2:
printArray(new int[]{3, 1, 2, 6, 4, 2});
数组作为函数的返回值
public static int[] reverse(int[] list) {
int[] result = new int[list.length];
for (int i = 0, j = result.length - 1; i < list.length; i++, j--) {
result[j] = list[i];
}
return result;
}
以上实例中result数组作为函数的返回值。
Arrays 类
java.util.Arrays类能方便地操作数组,它提供的所有方法都是静态的。具有以下功能:
给数组赋值:通过fill方法。
对数组排序:通过sort方法,按升序。
比较数组:通过equals方法比较数组中元素值是否相等。
查找数组元素:通过binarySearch方法能对排序好的数组进行二分查找法操作。
具体说明请查看下表:
序号 方法和说明
1 public static int binarySearch(Object[] a, Object key)
用二分查找算法在给定数组中搜索给定值的对象(Byte,Int,double等)。数组在调用前必须排序好的。如果查找值包含在数组中,则返回搜索键的索引;否则返回 (-(插入点) - 1)。
2 public static boolean equals(long[] a, long[] a2)
如果两个指定的 long 型数组彼此相等,则返回 true。如果两个数组包含相同数量的元素,并且两个数组中的所有相应元素对都是相等的,则认为这两个数组是相等的。换句话说,如果两个数组以相同顺序包含相同的元素,则两个数组是相等的。同样的方法适用于所有的其他基本数据类型(Byte,short,Int等)。
3 public static void fill(int[] a, int val)
将指定的 int 值分配给指定 int 型数组指定范围中的每个元素。同样的方法适用于所有的其他基本数据类型(Byte,short,Int等)。
4 public static void sort(Object[] a)
对指定对象数组根据其元素的自然顺序进行升序排列。同样的方法适用于所有的其他基本数据类型(Byte,short,Int等)。
从数组的一些简单介绍、到定义、声明、初始化、以及常用的方法、细微的区别、但是没有讲多维数组、多维数组本质是一维数组的延伸、以后有时间补充、有补充或者误导的地方望不吝指正、不胜感激!
一:数组的简介
Java中的数组、是一种简单的线性数据存储结构、他用牺牲自动扩展大小来换取与集合相比的唯一优势——查询效率的提升。他本身是一种引用类型的数据、所以我们要使用数组就要首先声明、初始化他、而他的初始化的完成也就意味着此数组的大小将不再改变、而且具有初始值。当存储的是java原始基础类型时、默认值是不同基础类型值的默认值、当是引用类型时、如果我们没有指定具体的对象、则他的默认值是null。从数组的内部组成来看、觉得java中没有多维数组!所谓的多维数组其实就是一维数组的延伸、只是数组的值存放的是另一个数组的引用、依次深入、我们可以扩展到任意维度的数组、注意:数组的扩展的过程中只能存放相同类型或者子类型的数组!
Java中的数组长度不变!存储的数据类型唯一!有时候我们会有种错觉觉得这两者都是不成立的、本质是因为java中继承的存在、我们定义的父类型的数组、他的数组元素也同样可以存放子类对象、这样就造成了数组存放对象可变的假象。当我们将数组的引用从一个对象改变到子类的对象的时候、感觉数组的长度变化、其实变的是引用指向的对象的长度、原来引用指向的对象长度没有变化、只是此对象被遗弃了而已、
对此只要记住一点:java中的数组时引用类型、当声明初始化一个对象时、数组的引用存放在栈内、实际对象存放在堆内存中、而我们平常所指的长度、类型都是具体对象的长度、类型!
此外、Arrays是一个操作java数组的类、提供了一些非常实用的用于操作数组的类、还有就是java数组与集合比较相似。后面会有两者间的相互转换的方法。
二:简单归纳:
java中有两种数据类型:
a)引用类型
b)基础类型
其中基础类型又有两种:
b1)数值类型
b2)及布尔类型。
数组——也为java的一个数据类型、归类为引用类型。本文意图说清楚两点:
1、数组的声明以及初始化。
2、常用的数组方法。
补充一点:对于我们常说的二维数组、多维数组其实是一维数组的延伸、这里暂时只围绕一维数组。
三:数组的声明及初始化
1、数组的声明:
作为一种引用类型、就如我们平常使用引用类型的时候声明一样、一般有两种写法:
a) type[] arrayName; exp: String[] strArray; b) type arrayName[]; exp: String strArray[];
第二种源于C的写法、由于很容易造成混淆、所以现在基本不使用这种声明方式了。
2、数组的初始化:
数组的初始化有两种:
a) 静态初始化——数组大小由系统分配、我们只为数组每个位置上赋值
String[] strArray1 = {"a", "b", "c", "d", "e"}; String[] strArray2 = new String[]{"a", "b", "c", "d", "e"};//在 new String[]中不能指定String数组的大小!
b)动态初始化——只指定数值的大小、初始化工作由系统为我们完成(即为数组的每个位置赋初始值)
String[] strArray3 = new String[5];//此时String数组的每个位置上的值都由系统来初始化、使用默认的值"" //我们能做的是动态的为strArray3每个位置上的值进行修改 for (int i = 0; i < strArray1.length; i++) { //这里仅用原始的方法进行赋值。 strArray3[i] = strArray1[i]; }
四:数组的常用方法
package com.chy.array.usefulMethods; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Set; import com.chy.array.bean.Student; @SuppressWarnings("all") public class ArrayUseFulMethoed { private static int[] intArray = {1, 2, 3, 4, 5}; private static String[] strArray = {"a", "b", "c", "d", "e"}; /** * 填充元素、比较大小、复制元素 */ public static void testFillArray(){ //注意字符串和对象的不同 Student[] student1 = new Student[4]; Student[] student2 = new Student[4]; System.out.println(Arrays.equals(student1, student2));//true Arrays.fill(student1, 0, 4, new Student(1,"chy")); Arrays.fill(student2, new Student(1,"chy")); System.out.println(Arrays.equals(student1, student2));//false String[] str1 = new String[4]; String[] str2 = new String[]{"a", "a", "a", "a"}; String[] str3 = {new String("a"), new String("a"), new String("a"), new String("a")}; Arrays.fill(str1, "a"); System.out.println(Arrays.equals(str1, str2));//true System.out.println(Arrays.equals(str2, str3));//true String[] str4 = Arrays.copyOf(str1, 2);//是将传入的数组拷贝len个元素到新的数组、相当于复制本身的一部分或者全部形成一个全新的数组 System.out.println(str4.length + "=======" + Arrays.toString(str4));// 2=======[a, a] String[] str5 = new String[8]; System.arraycopy(str4, 0, str5, 6, 2);//是将str4从下标0开的2个元素拷贝到从下标6开始放置的数组str5中 System.out.println(str5.length + "=======" + Arrays.toString(str5));// 8=======[null, null, null, null, null, null, a, a] } /** * 以字符串的形式输出指定数组的“模样” */ public static void printOriginalArray(){ String intArrayToString = Arrays.toString(intArray); System.out.println(intArrayToString); //result: [1, 2, 3, 4, 5] } /** * 将数组转化成List集合 * 注意:不能直接将int[]转化为集合、因为asList()方法的参数必须是对象。应该先把int[]转化为Integer[]。 * 对于其他primitive类型的数组也是如此,必须先转换成相应的wrapper类型数组。 */ public static void convetArrayToList(){ Integer[] integerArray = new Integer[intArray.length]; for (int i = 0; i < integerArray.length; i++) { integerArray[i] = intArray[i]; } ArrayList integerList1 = new ArrayList(Arrays.asList(integerArray)); /* * 不能写成下面: * ArrayList integerList2 = (ArrayList)Arrays.asList(integerArray); * 返回的是List、强转可以通过编译、但是不能正常使用。 */ for(int i : integerList1){ System.out.print(i); } //result: 12345 System.out.println(); } /** * 将List集合转换成数组 */ public static void convetListToArray(){ ArrayList strList = new ArrayList(Arrays.asList(strArray)); String[] strArrayFromList = new String[strList.size()]; strList.toArray(strArrayFromList); System.out.println(Arrays.toString(strArrayFromList)); //result: [a, b, c, d, e] /* * 注意:不能写成这样:String[] strArrayFromList = (String[])strList.toArray(strArrayFromList);会抛出ClassCastException。 * List.toArray()与List.toArray(T[] t)的区别在于: * List.toArray()返回的是一个Object[]、不能强转成String[]、强转的话可以通过编译、但是不能进行String[]的操作 * 而List.toArray(T[] t)会将list的值转换成T类型的数组。 */ } /** * 将数组转换成Set集合 */ public static void convertArrayToSet(){ Set set = new HashSet(Arrays.asList(strArray)); //Set具有无序性、所以输出结构不一定是原来数组元素存放顺序 System.out.println(set); //result: [d, e, b, c, a] } /** * 判断某个数组中是否包含一个元素、思路:将数组转换成list使用list的contains方法 */ public static void isContainObject(){ ArrayList strList = new ArrayList(Arrays.asList(strArray)); System.out.println(strList.contains("a")); //result: true //另一种实现 Arrays.sort(strArray); if(Arrays.binarySearch(strArray, "c") >= 0){ System.out.println(true); }else{ System.out.println(false); } } /** * 将两个相同类型的数组连接起来 */ public static void connTwoSameArray(){ int[] intArray2 = new int[]{6, 7, 8, 9, 10}; } /** * 将数组中数据排序 */ public static void sortArray(){ String[] str = {"c", "a" ,"d" ,"z" }; Arrays.sort(str); System.out.println(Arrays.toString(str)); //反序、 Arrays.sort(str, Collections.reverseOrder()); System.out.println(Arrays.toString(str)); } public static void main(String[] args) { /*printOriginalArray(); convetArrayToList(); convetListToArray(); isContainObject(); convertArrayToSet(); sortArray();*/ testFillArray(); } }