Java基础回顾(7)

来源:互联网 发布:陈国良院士 大数据 编辑:程序博客网 时间:2024/05/21 18:40
1.集合的分类,集合类中ArraylistHashSet的特点 
2.ArrayList和迭代器的配合使用  
3.流的分类
4. 文件读写,File类常用操作

.集合的分类,集合类中ArraylistHashSet的特点
java集合类

看了一些所谓大公司的JAVA面试问题,发现对于JAVA集合类的使用都比较看重似的,而自己在这方面还真的是所真甚少,抽空也学习学习吧。

java.util包中就包含了一系列重要的集合类,而对于集合类,主要需要掌握的就是它的内部结构,以及遍历集合的迭代模式。

接口:Collection

所有集合类的根类型,主要的一个接口方法:boolean add(Ojbect c)
虽返回的是boolean,但不是表示添加成功与否,因为Collection规定:一个集合拒绝添加这个元素,无论什么原因,都必须抛出异常,这个返回值表示的意义是add()执行后,集合的内容是否改了(就是元素有无数量、位置等变化)。类似的addAllremoveremoveAllremainAll也是一样的。

Iterator模式实现遍历集合

Collection有一个重要的方法:iterator(),返回一个Iterator(迭代子),用于遍历集合的所有元素。Iterator模式可以把访问逻辑从不同类的集合类中抽象出来,从而避免向客户端暴露集合的内部结构。

for(Iterator it =c.iterator(); it.hasNext();) {...}

不需要维护遍历集合的指针,所有的内部状态都有Iterator来维护,而这个Iterator由集合类通过工厂方法生成。

每一种集合类返回的Iterator具体类型可能不同,但它们都实现了Iterator接口,因此,我们不需要关心到底是哪种Iterator,它只需要获得这个Iterator接口即可,这就是接口的好处,面向对象的威力。

要确保遍历过程顺利完成,必须保证遍历过程中不更改集合的内容(Iteratorremove()方法除外),所以,确保遍历可靠的原则是:只在一个线程中使用这个集合,或者在多线程中对遍历代码进行同步。

JAVA Map集合类介绍

直接贴链接好了

http://dev.csdn.net/article/54/54534.shtm

线性表,链表,哈希表是常用的数据结构,在进行Java开发时,JDK已经为我们提供了一系列相应的类来实现基本的数据结构。这些类均在java.util包中。本文试图通过简单的描述,向读者阐述各个类的作用以及如何正确使用这些类。

Collection
├List
│├LinkedList
│├ArrayList
│└Vector
 └Stack
└Set
Map
├Hashtable
├HashMap
└WeakHashMap

Collection接口
  Collection是最基本的集合接口,一个Collection代表一组Object,即Collection的元素(Elements)。一些Collection允许相同的元素而另一些不行。一些能排序而另一些不行。Java SDK不提供直接继承自Collection的类,Java SDK提供的类都是继承自Collection子接口ListSet
  所有实现Collection接口的类都必须提供两个标准的构造函数:无参数的构造函数用于创建一个空的Collection,有一个Collection参数的构造函数用于创建一个新的Collection,这个新的Collection与传入的Collection有相同的元素。后一个构造函数允许用户复制一个Collection
  如何遍历Collection中的每一个元素?不论Collection的实际类型如何,它都支持一个iterator()的方法,该方法返回一个迭代子,使用该迭代子即可逐一访问Collection中每一个元素。典型的用法如下:
    Iterator it =collection.iterator(); // 获得一个迭代子
    while(it.hasNext()){
      Object obj =it.next(); // 得到下一个元素
    }
  由Collection接口派生的两个接口是ListSet

List接口
  List是有序的Collection,使用此接口能够精确的控制每个元素插入的位置。用户能够使用索引(元素在List中的位置,类似于数组下标)来访问List中的元素,这类似于Java的数组。
和下面要提到的Set不同,List允许有相同的元素。
  除了具有Collection接口必备的iterator()方法外,List还提供一个listIterator()方法,返回一个ListIterator接口,和标准的Iterator接口相比,ListIterator多了一些add()之类的方法,允许添加,删除,设定元素,还能向前或向后遍历。
  实现List接口的常用类有LinkedListArrayListVectorStack

LinkedList
  LinkedList实现了List接口,允许null元素。此外LinkedList提供额外的getremoveinsert方法在LinkedList的首部或尾部。这些操作使LinkedList可被用作堆栈(stack),队列(queue)或双向队列(deque)。
  注意LinkedList没有同步方法。如果多个线程同时访问一个List,则必须自己实现访问同步。一种解决方法是在创建List时构造一个同步的List
    List list =Collections.synchronizedList(new LinkedList(...));

ArrayList
  ArrayList实现了可变大小的数组。它允许所有元素,包括nullArrayList没有同步。
size
isEmptygetset方法运行时间为常数。但是add方法开销为分摊的常数,添加n个元素需要O(n)的时间。其他的方法运行时间为线性。
  每个ArrayList实例都有一个容量(Capacity),即用于存储元素的数组的大小。这个容量可随着不断添加新元素而自动增加,但是增长算法并没有定义。当需要插入大量元素时,在插入前可以调用ensureCapacity方法来增加ArrayList的容量以提高插入效率。
  和LinkedList一样,ArrayList也是非同步的(unsynchronized)。

Vector
  Vector非常类似ArrayList,但是Vector是同步的。由Vector创建的Iterator,虽然和ArrayList创建的Iterator是同一接口,但是,因为Vector是同步的,当一个Iterator被创建而且正在被使用,另一个线程改变了Vector的状态(例如,添加或删除了一些元素),这时调用Iterator的方法时将抛出ConcurrentModificationException,因此必须捕获该异常。

Stack 
  Stack继承自Vector,实现一个后进先出的堆栈。Stack提供5个额外的方法使得Vector得以被当作堆栈使用。基本的pushpop方法,还有peek方法得到栈顶的元素,empty方法测试堆栈是否为空,search方法检测一个元素在堆栈中的位置。Stack刚创建后是空栈。

Set接口
  Set是一种不包含重复的元素的Collection,即任意的两个元素e1e2都有e1.equals(e2)=falseSet最多有一个null元素。
  很明显,Set的构造函数有一个约束条件,传入的Collection参数不能包含重复的元素。
  请注意:必须小心操作可变对象(Mutable Object)。如果一个Set中的可变元素改变了自身状态导致Object.equals(Object)=true将导致一些问题。

Map接口
  请注意,Map没有继承Collection接口,Map提供keyvalue的映射。一个Map中不能包含相同的key,每个key只能映射一个valueMap接口提供3种集合的视图,Map的内容可以被当作一组key集合,一组value集合,或者一组key-value映射。

Hashtable
  Hashtable继承Map接口,实现一个key-value映射的哈希表。任何非空(non-null)的对象都可作为key或者value
  添加数据使用put(key, value),取出数据使用get(key),这两个基本操作的时间开销为常数。
Hashtable
通过initial capacityload factor两个参数调整性能。通常缺省的load factor 0.75较好地实现了时间和空间的均衡。增大load factor可以节省空间但相应的查找时间将增大,这会影响像getput这样的操作。
使用Hashtable的简单示例如下,将123放到Hashtable中,他们的key分别是”one””two””three”
    Hashtable numbers= new Hashtable();
    numbers.put(“one”,new Integer(1));
    numbers.put(“two”,new Integer(2));
    numbers.put(“three”,new Integer(3));
  要取出一个数,比如2,用相应的key
    Integer n =(Integer)numbers.get(“two”);
    System.out.println(“two= ” + n);
  由于作为key的对象将通过计算其散列函数来确定与之对应的value的位置,因此任何作为key的对象都必须实现hashCodeequals方法。hashCodeequals方法继承自根类Object,如果你用自定义的类当作key的话,要相当小心,按照散列函数的定义,如果两个对象相同,即obj1.equals(obj2)=true,则它们的hashCode必须相同,但如果两个对象不同,则它们的hashCode不一定不同,如果两个不同对象的hashCode相同,这种现象称为冲突,冲突会导致操作哈希表的时间开销增大,所以尽量定义好的hashCode()方法,能加快哈希表的操作。
  如果相同的对象有不同的hashCode,对哈希表的操作会出现意想不到的结果(期待的get方法返回null),要避免这种问题,只需要牢记一条:要同时复写equals方法和hashCode方法,而不要只写其中一个。
  Hashtable是同步的。

HashMap
  HashMapHashtable类似,不同之处在于HashMap是非同步的,并且允许null,即null valuenull key。,但是将HashMap视为Collection时(values()方法可返回Collection),其迭代子操作时间开销和HashMap的容量成比例。因此,如果迭代操作的性能相当重要的话,不要将HashMap的初始化容量设得过高,或者load factor过低。

WeakHashMap
  WeakHashMap是一种改进的HashMap,它对key实行弱引用,如果一个key不再被外部所引用,那么该key可以被GC回收。

总结
  如果涉及到堆栈,队列等操作,应该考虑用List,对于需要快速插入,删除元素,应该使用LinkedList,如果需要快速随机访问元素,应该使用ArrayList
  如果程序在单线程环境中,或者访问仅仅在一个线程中进行,考虑非同步的类,其效率较高,如果多个线程可能同时操作一个类,应该使用同步的类。
  要特别注意对哈希表的操作,作为key的对象要正确复写equalshashCode方法。
  尽量返回接口而非实际的类型,如返回List而非ArrayList,这样如果以后需要将ArrayList换成LinkedList时,客户端代码不用改变。这就是针对抽象编程。

集合类说明及区别
Collection
├List
│├LinkedList
│├ArrayList
│└Vector
 └Stack
└Set
Map
├Hashtable
├HashMap
└WeakHashMap

Collection接口
  Collection是最基本的集合接口,一个Collection代表一组Object,即Collection的元素(Elements)。一些 Collection允许相同的元素而另一些不行。一些能排序而另一些不行。Java SDK不提供直接继承自Collection的类,Java SDK提供的类都是继承自Collection子接口ListSet
  所有实现Collection接口的类都必须提供两个标准的构造函数:无参数的构造函数用于创建一个空的Collection,有一个 Collection参数的构造函数用于创建一个新的Collection,这个新的Collection与传入的Collection有相同的元素。后 一个构造函数允许用户复制一个Collection
  如何遍历Collection中的每一个元素?不论Collection的实际类型如何,它都支持一个iterator()的方法,该方法返回一个迭代子,使用该迭代子即可逐一访问Collection中每一个元素。典型的用法如下:
    Iterator it =collection.iterator(); // 获得一个迭代子
    while(it.hasNext()){
      Object obj =it.next(); // 得到下一个元素
    }
  由Collection接口派生的两个接口是ListSet

List接口
  List是有序的Collection,使用此接口能够精确的控制每个元素插入的位置。用户能够使用索引(元素在List中的位置,类似于数组下标)来访问List中的元素,这类似于Java的数组。
和下面要提到的Set不同,List允许有相同的元素。
  除了具有Collection接口必备的iterator()方法外,List还提供一个listIterator()方法,返回一个 ListIterator接口,和标准的Iterator接口相比,ListIterator多了一些add()之类的方法,允许添加,删除,设定元素, 还能向前或向后遍历。
  实现List接口的常用类有LinkedListArrayListVectorStack

LinkedList
  LinkedList实现了List接口,允许null元素。此外LinkedList提供额外的getremoveinsert方法在 LinkedList的首部或尾部。这些操作使LinkedList可被用作堆栈(stack),队列(queue)或双向队列(deque)。
  注意LinkedList没有同步方法。如果多个线程同时访问一个List,则必须自己实现访问同步。一种解决方法是在创建List时构造一个同步的List
    List list =Collections.synchronizedList(new LinkedList(...));

ArrayList
  ArrayList实现了可变大小的数组。它允许所有元素,包括nullArrayList没有同步。
size
isEmptygetset方法运行时间为常数。但是add方法开销为分摊的常数,添加n个元素需要O(n)的时间。其他的方法运行时间为线性。
  每个ArrayList实例都有一个容量(Capacity),即用于存储元素的数组的大小。这个容量可随着不断添加新元素而自动增加,但是增长算法 并没有定义。当需要插入大量元素时,在插入前可以调用ensureCapacity方法来增加ArrayList的容量以提高插入效率。
  和LinkedList一样,ArrayList也是非同步的(unsynchronized)。

Vector
  Vector非常类似ArrayList,但是Vector是同步的。由Vector创建的Iterator,虽然和 ArrayList创建的Iterator是同一接口,但是,因为Vector是同步的,当一个Iterator被创建而且正在被使用,另一个线程改变了 Vector的状态(例如,添加或删除了一些元素),这时调用Iterator的方法时将抛出 ConcurrentModificationException,因此必须捕获该异常。

Stack 
  Stack继承自Vector,实现一个后进先出的堆栈。Stack提供5个额外的方法使得Vector得以被当作堆栈使用。基本的pushpop 方法,还有peek方法得到栈顶的元素,empty方法测试堆栈是否为空,search方法检测一个元素在堆栈中的位置。Stack刚创建后是空栈。

Set接口
  Set是一种不包含重复的元素的Collection,即任意的两个元素e1e2都有e1.equals(e2)=falseSet最多有一个null元素。
  很明显,Set的构造函数有一个约束条件,传入的Collection参数不能包含重复的元素。
  请注意:必须小心操作可变对象(Mutable Object)。如果一个Set中的可变元素改变了自身状态导致Object.equals(Object)=true将导致一些问题。

Map接口
  请注意,Map没有继承Collection接口,Map提供keyvalue的映射。一个Map中不能包含相同的key,每个key只能映射一个valueMap接口提供3种集合的视图,Map的内容可以被当作一组key集合,一组value集合,或者一组key-value映射。

Hashtable
  Hashtable继承Map接口,实现一个key-value映射的哈希表。任何非空(non-null)的对象都可作为key或者value
  添加数据使用put(key, value),取出数据使用get(key),这两个基本操作的时间开销为常数。
Hashtable
通过initial capacityload factor两个参数调整性能。通常缺省的load factor 0.75较好地实现了时间和空间的均衡。增大load factor可以节省空间但相应的查找时间将增大,这会影响像getput这样的操作。
使用Hashtable的简单示例如下,将123放到Hashtable中,他们的key分别是”one””two””three”
    Hashtable numbers =new Hashtable();
    numbers.put(“one”,new Integer(1));
    numbers.put(“two”,new Integer(2));
    numbers.put(“three”,new Integer(3));
  要取出一个数,比如2,用相应的key
    Integer n =(Integer)numbers.get(“two”);
    System.out.println(“two= ” + n);
  由于作为key的对象将通过计算其散列函数来确定与之对应的value的位置,因此任何作为key的对象都必须实现hashCodeequals 法。hashCodeequals方法继承自根类Object,如果你用自定义的类当作key的话,要相当小心,按照散列函数的定义,如果两个对象相 同,即obj1.equals(obj2)=true,则它们的hashCode必须相同,但如果两个对象不同,则它们的hashCode不一定不同,如 果两个不同对象的hashCode相同,这种现象称为冲突,冲突会导致操作哈希表的时间开销增大,所以尽量定义好的hashCode()方法,能加快哈希 表的操作。
  如果相同的对象有不同的hashCode,对哈希表的操作会出现意想不到的结果(期待的get方法返回null),要避免这种问题,只需要牢记一条:要同时复写equals方法和hashCode方法,而不要只写其中一个。
  Hashtable是同步的。

HashMap
  HashMapHashtable类似,不同之处在于HashMap是非同步的,并且允许null,即null valuenull key。,但是将HashMap视为Collection时(values()方法可返回Collection),其迭代子操作时间开销和HashMap 的容量成比例。因此,如果迭代操作的性能相当重要的话,不要将HashMap的初始化容量设得过高,或者load factor过低。

WeakHashMap
  WeakHashMap是一种改进的HashMap,它对key实行弱引用,如果一个key不再被外部所引用,那么该key可以被GC回收。

总结
  如果涉及到堆栈,队列等操作,应该考虑用List,对于需要快速插入,删除元素,应该使用LinkedList,如果需要快速随机访问元素,应该使用ArrayList
  如果程序在单线程环境中,或者访问仅仅在一个线程中进行,考虑非同步的类,其效率较高,如果多个线程可能同时操作一个类,应该使用同步的类。
  要特别注意对哈希表的操作,作为key的对象要正确复写equalshashCode方法。
  尽量返回接口而非实际的类型,如返回List而非ArrayList,这样如果以后需要将ArrayList换成LinkedList时,客户端代码不用改变。这就是针对抽象编程。

同步性
Vector
是同步的。这个类中的一些方法保证了Vector中的对象是线程安全的。而ArrayList则是异步的,因此ArrayList中的对象并 不是线程安全的。因为同步的要求会影响执行的效率,所以如果你不需要线程安全的集合那么使用ArrayList是一个很好的选择,这样可以避免由于同步带 来的不必要的性能开销。
数据增长
从内部实现机制来讲ArrayListVector都是使用数组(Array)来控制集合中的对象。当你向这两种类型中增加元素的时候,如果元素的数目超出了内部数组目前的长度它们都需要扩展内部数组的长度,Vector缺省情况下自动增长原来一倍的数组长度,ArrayList是原来的50%,所以最 后你获得的这个集合所占的空间总是比你实际需要的要大。所以如果你要在集合中保存大量的数据那么使用Vector有一些优势,因为你可以通过设置集合的初 始化大小来避免不必要的资源开销。
使用模式
ArrayListVector中,从一个指定的位置(通过索引)查找数据或是在集合的末尾增加、移除一个元素所花费的时间是一样的,这个时间我们用 O(1)表示。但是,如果在集合的其他位置增加或移除元素那么花费的时间会呈线形增长:O(n-i),其中n代表集合中元素的个数,i代表元素增加或移除 元素的索引位置。为什么会这样呢?以为在进行上述操作的时候集合中第i和第i个元素之后的所有元素都要执行位移的操作。这一切意味着什么呢?
这意味着,你只是查找特定位置的元素或只在集合的末端增加、移除元素,那么使用VectorArrayList都可以。如果是其他操作,你最好选择其他 的集合操作类。比如,LinkList集合类在增加或移除集合中任何位置的元素所花费的时间都是一样的?O(1),但它在索引一个元素的使用缺比较慢 O(i),其中i是索引的位置.使用ArrayList也很容易,因为你可以简单的使用索引来代替创建iterator对象的操作。LinkList 会为每个插入的元素创建对象,所有你要明白它也会带来额外的开销。
最后,在《Practical Java》一书中Peter Haggar建议使用一个简单的数组(Array)来代替VectorArrayList。尤其是对于执行效率要求高的程序更应如此。因为使用数组 (Array)避免了同步、额外的方法调用和不必要的重新分配空间的操作。

相互区别

VectorArrayList

1vector是线程同步的,所以它也是线程安全的,而arraylist是线程异步的,是不安全的。如果不考虑到线程的安全因素,一般用

arraylist效率比较高。
2
,如果集合中的元素的数目大于目前集合数组的长度时,vector增长率为目前数组长度的100%,arraylist增长率为目前数组长度

50%.如过在集合中使用数据量比较大的数据,用vector有一定的优势。
3
,如果查找一个指定位置的数据,vectorarraylist使用的时间是相同的,都是0(1),这个时候使用vectorarraylist都可以。而

如果移动一个指定位置的数据花费的时间为0(n-i)n为总长度,这个时候就应该考虑到使用linklist,因为它移动一个指定位置的数据

所花费的时间为0(1),而查询一个指定位置的数据时花费的时间为0(i)

ArrayList Vector是采用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,都允许直接序号索引元素,但是插入数据要设计到数组元素移动 等内存操作,所以索引数据快插入数据慢,Vector由于使用了synchronized方法(线程安全)所以性能上比ArrayList 差,LinkedList使用双向链表实现存储,按序号索引数据需要进行向前或向后遍历,但是插入数据时只需要记录本项的前后项即可,所以插入数度较快!

arraylistlinkedlist

1.ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
2.
对于随机访问getsetArrayList觉得优于LinkedList,因为LinkedList要移动指针。
3.
对于新增和删除操作addremoveLinedList比较占优势,因为ArrayList要移动数据。
    
这一点要看实际情况的。若只对单条数据插入或删除,ArrayList的速度反而优于LinkedList。但若是批量随机的插入删除数据,LinkedList的速度大大优于ArrayList. 因为ArrayList每插入一条数据,要移动插入点及之后的所有数据。


HashMapTreeMap
        ()
       
文章出处:http://www.diybl.com/course/3_program/java/javaxl/200875/130233.html

       1
HashMap通过hashcode对其内容进行快速查找,而TreeMap中所有的元素都保持着某种固定的顺序,如果你需要得到一个有序的结果你就应该使用TreeMapHashMap中元素的排列顺序是不固定的)。

HashMap中元素的排列顺序是不固定的)。

       2  HashMap通过hashcode对其内容进行快速查找,而TreeMap中所有的元素都保持着某种固定的顺序,如果你需要得到一个有序的结果你就应该 使用TreeMapHashMap中元素的排列顺序是不固定的)。集合框架提供两种常规的Map实现:HashMapTreeMap (TreeMap实现SortedMap接口)

        3、在Map 中插入、删除和定位元素,HashMap 是最好的选择。但如果您要按自然顺序或自定义顺序遍历键,那么TreeMap会更好。使用HashMap要求添加的键类明确定义了hashCode() equals()的实现。  这个TreeMap没有调优选项,因为该树总处于平衡状态。

     结过研究,在原作者的基础上我还发现了一点,二树map一样,但顺序不一样,导致hashCode()不一样。
      
同样做测试:
      
hashMap中,同样的值的map,顺序不同,equals时,false;
      
而在treeMap中,同样的值的map,顺序不同,equals时,true,说明,treeMapequals()时是整理了顺序了的。

hashtablehashmap

.历史原因:Hashtable是基于陈旧的Dictionary类的,HashMapJava 1.2引进的Map接口的一个实现

.同步性:Hashtable是线程安全的,也就是说是同步的,而HashMap是线程序不安全的,不是同步的

.值:只有HashMap可以让你将空值作为一个表的条目的keyvalue

HashSet
ArrayList
使用java中的ArrayList类实例方法

使用ArrayList
ArrayList
类实现了List接口,由ArrayList类实现的List集合采用数组结构保存对象。数组结构的优点是便于对集合进行快速的随机访问,如果经常需要根据索引位置访问集合中的对象,使用由ArrayList类实现的List集合的效率较好。数组结构的缺点是向指定索引位置插入对象和删除指定索引位置对象的速度较慢,如果经常需要向List集合的指定索引位置插入对象,或者是删除List集合的指定索引位置的对象,使用由ArrayList类实现的List集合的效率则较低,并且插入或删除对象的索引位置越小效率越低,原因是当向指定的索引位置插入对象时,会同时将指定索引位置及之后的所有对象相应的向后移动一位,如图1所示。当删除指定索引位置的对象时,会同时将指定索引位置之后的所有对象相应的向前移动一位,如图2所示。如果在指定的索引位置之后有大量的对象,将严重影响对集合的操作效率。

 
1  向由ArrayList类实现的List集合中插入对象
 
2  从由ArrayList类实现的List集合中删除对象
就是因为用ArrayList类实现的List集合在插入和删除对象时存在这样的缺点,在编写例程06时才没有利用ArrayList类实例化List集合,下面看一个模仿经常需要随机访问集合中对象的例子。
在编写该例子时,用到了java.lang.Math类的random()方法,通过该方法可以得到一个小于10double型随机数,将该随机数乘以5后再强制转换成整数,将得到一个04的整数,并随机访问由ArrayList类实现的List集合中该索引位置的对象,具体代码如下:
src\com\mwq\TestCollection.java
关键代码:
public static void main(String[] args) {
String a = "A", b = "B", c = "C", d ="D", e = "E";
List<String> list = new ArrayList<String>();
list.add(a);      // 
索引位置为 0
list.add(b);      // 
索引位置为 1
list.add(c);      // 
索引位置为 2
list.add(d);      // 
索引位置为 3
list.add(e);      // 
索引位置为 4
System.out.println(list.get((int) (Math.random() *5)));     // 
模拟随机访问集合中的对象
}
src\com\mwq\TestCollection.java
完整代码:
package com.mwq;
import java.util.ArrayList;
import java.util.List;
package com.mwq;
import java.util.ArrayList;
import java.util.List;
public class TestCollection {
public static void main(String[] args) {
System.out.println("
开始:");
String a = "A", b = "B", c = "C", d ="D", e = "E";
List<String> list = new ArrayList<String>();
list.add(a); // 
索引位置为 0
list.add(b); // 
索引位置为 1
list.add(c); // 
索引位置为 2
list.add(d); // 
索引位置为 3
list.add(e); // 
索引位置为 4
System.out.println(list.get((int) (Math.random() * 5)));// 
模拟随机访问集合中的对象
System.out.println("
结束!");
}
}
public class TestCollection {
public static void main(String[] args) {
System.out.println("
开始:");
String a = "A", b = "B", c = "C", d ="D", e = "E";
List<String> list = new ArrayList<String>();
list.add(a); // 
索引位置为 0
list.add(b); // 
索引位置为 1
list.add(c); // 
索引位置为 2
list.add(d); // 
索引位置为 3
list.add(e); // 
索引位置为 4
System.out.println(list.get((int) (Math.random() * 5)));// 
模拟随机访问集合中的对象
System.out.println("
结束!");
}
}
执行上面的代码,当得到的04之间的随机数为1时,在控制台将输出“B”,当得到的04之间的随机数为3时,在控制台将输出“D”,等等。
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
HashSet类的用法

HashSet是实现Set接口的一个类,具有以下的特点:

Ø         不能保证元素的排列顺序,顺序有可能发生变化。

Ø         另外HashSet不是同步的,如果多个线程同时访问一个Set,只要有一个线程修改Set中的值,就必须进行同步处理,通常通过同步封装这个Set的对象来完成同步,如果不存在这样的对象,可以使用Collections.synchronizedSet()方法完成。

Set s = Collections.synchronizedSet(new HashSet(...));

Ø         元素值可以是null

方法不是很多,在前面介绍Set接口的时候方法基本上都涉及到,这里使用例子来介绍各个方法的用法。

package com.li.common;

 

importjava.util.ArrayList;

import java.util.HashSet;

import java.util.Iterator;

 

public class HashSetTest{

 

      /**

       * @param args

       */

      public static void main(String[] args) {

             // TODO Auto-generated method stub

             HashSetTest test = new HashSetTest();

             test.testHashSet();

      }

      public void testHashSet(){

             //实例化HashSet对象

             HashSet hs = new HashSet();

             

             System.out.println("添加第一个元素");

             hs.add(new String("第一个元素"));

             

             System.out.println("创建一个ArrayList对象,添加两个元素");

             ArrayList list = new ArrayList();

             list.add("第二个元素");

             list.add("第三个元素");

             

             System.out.println("ArrayList对象添加到HashSet");

             hs.addAll(list);

             

             System.out.println("HashSet中添加一个元素");

             hs.add("第四个元素");

             

             System.out.println("添加一个null元素");

             hs.add(null);  

             

             System.out.println("/n通过转换成数组遍历的结果:");

             System.out.println("HashSet中的数据如下:");

             this.show2(hs);            

 

             System.out.println("/n通过得到Iterator遍历的结果:");

             hs.remove("第一个元素");

             System.out.println("删除/"第一个元素/"之后:");

             this.show1(hs);

             

             System.out.println("HashSet中元素的个数为:"+hs.size());

             if(hs.isEmpty()){

                    System.out.println("HashSet是空的");

             }

             else{

                    System.out.println("HashSet不是空的");

             }

 

             System.out.println("清空所有的元素:");

             hs.clear();

             if(hs.isEmpty()){

                    System.out.println("HashSet是空的");

             }

             else{

                    System.out.println("HashSet不是空的");

             }

      }

      

      /*

       * 得到Iterator,然后遍历输出

       */

      public void show1(HashSet hs){

             Iterator i = hs.iterator();

             while(i.hasNext()){

                    String temp = (String)i.next();

                    System.out.println(temp);

             }           

      }

      

      /*

       * 转换成数组,遍历并输出HashSet中的元素

       */

      public void show2(HashSet hs){

             Object o[] = hs.toArray();

             for(int i=0;i<o.length;i++){

                     System.out.println((String)o[i]);

              }

      }

 

} 

 

 -------------------------------------------------------------------------------------------------------------------------------------------

HashSet 是不重复的 而且是无序的!

唯一性保证重复对象equals方法返回为true ,重复对象hashCode方法返回相同的整数

HashSet其实就是一个HashMap,只是你只能通过Set接口操作这个HashMapkey部分,

ArrayList
是可重复的 有序的

特点:查询效率高,增删效率低 轻量级 线程不安全。

arraylist:在数据的插入和删除方面速度不佳,但是在随意提取方面较快


TreeMap
将会对键排列顺序

集合是指一个对象可以容纳了多个对象(不是引用),这个集合对象主要用来管理维护一系列相似的对象。
-----------------------------------------------------------------------------------------------------------------------------------------
ArrayList
:底层用数组实现的List 
特点:查询效率高,增删效率低 轻量级 线程不安全。
HashSet
:采用哈希算法来实现Set接口, 唯一性保证:重复对象equals方法返回为true ,重复对象hashCode方法返回相同的整数 
不同对象 哈希码 尽量保证不同(提高效率)。
TreeMap
 
集合是指一个对象可以容纳了多个对象(不是引用),这个集合对象主要用来管理维护一系列相似的对象。 

 --------------------------------------------------------------------------------------------------------------------------------------------

Set 集合是无序不可以重复的的、List 集合是有序可以重复的。

Java 集合:HashSet  hashCodeequals 博客里面已经说到这个问题,但是解释的还是不够清楚。

 

看一个小例子:

 

package mark.zhang;

import java.util.ArrayList;
import java.util.HashSet;

public class Test {

    public static void main(String[] args) {
        ArrayList<Integer> loadsList = newArrayList<Integer>();
        loadsList.add(1);
        loadsList.add(2);
        loadsList.add(0);
        loadsList.add(3);
        loadsList.add(2);
        loadsList.add(1);
        loadsList.add(3);
        loadsList.add(5);
        loadsList.add(0);
        System.out.println("the arrayList: " +loadsList);
        
        HashSet<Integer> loadsSet = newHashSet<Integer>();
        loadsSet.add(1);
        loadsSet.add(2);
        loadsSet.add(0);
        loadsSet.add(3);
        loadsSet.add(2);
        loadsSet.add(1);
        loadsSet.add(3);
        loadsSet.add(5);
        loadsSet.add(0);
        System.out.println("the hashSet:   "+ loadsSet);
    }
}

 

代码很简单,分别使用 ArrayListHashSet 装载 Integer 数据,然后打印集合的内容。

 

List 中的元素是按照 add 顺序加载的,并且里面有重复的元素。这就是有序可重复的意思。

Set 中的元素并没有按照 add 顺序加载的,并且里面没有重复的元素。这就是无序不可重复的意思。

 

换句话说,有序不是指按照字母顺序或者数字大小来排列的,重复是指元素之间 equals  true

 

这里选择 Integer,因为其重写了 equals 方法。

 

那麽,我们思考一个问题:如何去掉 List 里面重复的元素?参考代码:

 

package mark.zhang;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;

public class Test {

    public static void main(String[] args) {
        ArrayList<Integer> loadsList = newArrayList<Integer>();
        loadsList.add(1);
        loadsList.add(2);
        loadsList.add(0);
        loadsList.add(3);
        loadsList.add(2);
        loadsList.add(1);
        loadsList.add(3);
        loadsList.add(5);
        loadsList.add(0);
        System.out.println("remove before--- thearrayList: " + loadsList);
        // remove the repeated element
       // rmRepeatedElement(loadsList);
        rmRepeadtedElementByOrder(loadsList);
        System.out.println("remove after--- thearrayList: " + loadsList);
        
    }

    public static void rmRepeatedElement(List<Integer> list) {
        HashSet<Integer> loadsSet = newHashSet<Integer>(list);
        list.clear();
        list.addAll(loadsSet);
    }
    
    public static void rmRepeadtedElementByOrder(List<Integer>list) {
        HashSet<Integer> loadsSet = newHashSet<Integer>();
        ArrayList<Integer> loadsList = newArrayList<Integer>();
        for(Iterator<Integer> iterator =list.iterator(); iterator.hasNext();) {
            Integer element = iterator.next();
            if(loadsSet.add(element)) {
                loadsList.add(element);
            }
        }
        
        list.clear();
        list.addAll(loadsList);
    }
    
}

 

 

 

移除相同元素之后,还是按照原来的排列顺序。

 

二.ArrayList和迭代器的配合使用 

ArrayList Linklist遍历使用Iteratorfor的选择方法。

看看下面2个程序:

import java.util.Iterator;
import java.util.List;
import java.util.ArrayList;
import java.util.LinkedList;

/**
* IteratorTest
* @author SageZk
*/
public class IteratorTest {

public static long testForloops(List<String> list) {
long start = 0L, end = 0L;
@SuppressWarnings("unused")
String le = null;
start = System.nanoTime();
for (int i = list.size() - 1; i >= 0; --i) {
le = list.get(i);
}
end = System.nanoTime();
return end - start;
}

public static long testIterator(List<String> list) {
long start = 0L, end = 0L;
@SuppressWarnings("unused")
String le = null;
start = System.nanoTime();
Iterator<String> it = list.iterator();
while (it.hasNext()) {
le = it.next();
}
end = System.nanoTime();
return end - start;
}

public static void main(String[] args) {
//
测试列表长度
final int LEN = 10000;

//
初始化测试用数据
List<String> arraylist = new ArrayList<String>();
List<String> linkedlist = new LinkedList<String>();
for (int i = 0; i < LEN; ++i) {
String s = Integer.toString(i, 2);
arraylist.add(s);
linkedlist.add(s);
}

//
打印测试结果
final String FORMAT = "%1$-16s%2$-16s%3$16d\n";
System.out.println("List\t\tType\t\tTime(nanoseconds)");
System.out.println("-------------------------------------------------");
System.out.printf(FORMAT, "ArrayList", "for", testForloops(arraylist));
System.out.printf(FORMAT, "ArrayList", "Iterator", testIterator(arraylist));
System.out.printf(FORMAT, "LinkedList", "for", testForloops(linkedlist));
System.out.printf(FORMAT, "LinkedList", "Iterator", testIterator(linkedlist));
}

}
代码如上:
测试结果如下:
第一组
List Type Time(nanoseconds)
-------------------------------------------------
ArrayList for 4140471
ArrayList Iterator 4492470
LinkedList for 170876466
LinkedList Iterator 3871163
第二组:
List Type Time(nanoseconds)
-------------------------------------------------
ArrayList for 3961397
ArrayList Iterator 4324572
LinkedList for 172783133
LinkedList Iterator 3462451
第三组:
List Type Time(nanoseconds)
-------------------------------------------------
ArrayList for 6560331
ArrayList Iterator 4883582
LinkedList for 181580341
LinkedList Iterator 4678248
通过数据可以看到使用 Iterator 的好处在于可以使用相同方式去遍历集合(List 是有序集合)中元素,而不用考虑集合类的内部实现(只要它实现了 java.lang.Iterable 接口)。
比如果使用 Iterator 来遍历集合中元素,一旦不再使用 List 转而使用 TreeSet 来组织数据,那遍历元素的代码不用做任何修改,如果使用 for 来遍历,那所有遍历此集合的算法都得做相应调整。

三.流的分类 
   
Java流操作有关的类或接口:

Java流类图结构:

 

流的概念和作用

流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。 


IO流的分类

  • 根据处理数据类型的不同分为:字符流和字节流
  • 根据数据流向不同分为:输入流和输出流
 

字符流和字节流

字符流的由来: 因为数据编码的不同,而有了对字符进行高效操作的流对象。本质其实就是基于字节流读取时,去查了指定的码表。 字节流和字符流的区别:

  • 读写单位不同:字节流以字节(8bit)为单位,字符流以字符为单位,根据码表映射字符,一次可能读多个字节。
  • 处理对象不同:字节流能处理所有类型的数据(如图片、avi等),而字符流只能处理字符类型的数据。

结论:只要是处理纯文本数据,就优先考虑使用字符流。 除此之外都使用字节流。

 

输入流和输出流

对输入流只能进行读操作,对输出流只能进行写操作,程序中需要根据待传输数据的不同特性而使用不同的流。  


Java IO流对象

1.输入字节流InputStreamIO 中输入字节流的继承图可见上图,可以看出:

  1. InputStream 是所有的输入字节流的父类,它是一个抽象类。
  2. ByteArrayInputStream、StringBufferInputStream、FileInputStream 是三种基本的介质流,它们分别从Byte 数组、StringBuffer、和本地文件中读取数据。PipedInputStream 是从与其它线程共用的管道中读取数据,与Piped 相关的知识后续单独介绍。
  3. ObjectInputStream 和所有FilterInputStream 的子类都是装饰流(装饰器模式的主角)。

 

2.输出字节流OutputStream

IO 中输出字节流的继承图可见上图,可以看出:

  1. OutputStream 是所有的输出字节流的父类,它是一个抽象类。
  2. ByteArrayOutputStream、FileOutputStream 是两种基本的介质流,它们分别向Byte 数组、和本地文件中写入数据。PipedOutputStream 是向与其它线程共用的管道中写入数据,
  3. ObjectOutputStream 和所有FilterOutputStream 的子类都是装饰流。

 

3.字节流的输入与输出的对应

 

图中蓝色的为主要的对应部分,红色的部分就是不对应部分。紫色的虚线部分代表这些流一般要搭配使用。从上面的图中可以看出Java IO 中的字节流是极其对称的。“存在及合理”我们看看这些字节流中不太对称的几个类吧!

  1. LineNumberInputStream 主要完成从流中读取数据时,会得到相应的行号,至于什么时候分行、在哪里分行是由改类主动确定的,并不是在原始中有这样一个行号。在输出部分没有对应的部分,我们完全可以自己建立一个LineNumberOutputStream,在最初写入时会有一个基准的行号,以后每次遇到换行时会在下一行添加一个行号,看起来也是可以的。好像更不入流了。
  2. PushbackInputStream 的功能是查看最后一个字节,不满意就放入缓冲区。主要用在编译器的语法、词法分析部分。输出部分的BufferedOutputStream 几乎实现相近的功能。
  3. StringBufferInputStream 已经被Deprecated,本身就不应该出现在InputStream 部分,主要因为String 应该属于字符流的范围。已经被废弃了,当然输出部分也没有必要需要它了!还允许它存在只是为了保持版本的向下兼容而已。
  4. SequenceInputStream 可以认为是一个工具类,将两个或者多个输入流当成一个输入流依次读取。完全可以从IO 包中去除,还完全不影响IO 包的结构,却让其更“纯洁”――纯洁的Decorator 模式。
  5. PrintStream 也可以认为是一个辅助工具。主要可以向其他输出流,或者FileInputStream 写入数据,本身内部实现还是带缓冲的。本质上是对其它流的综合运用的一个工具而已。一样可以踢出IO 包!System.out 和System.out 就是PrintStream 的实例!

 

4.字符输入流Reader

在上面的继承关系图中可以看出:

  1. Reader 是所有的输入字符流的父类,它是一个抽象类。
  2. CharReader、StringReader 是两种基本的介质流,它们分别将Char 数组、String中读取数据。PipedReader 是从与其它线程共用的管道中读取数据。
  3. BufferedReader 很明显就是一个装饰器,它和其子类负责装饰其它Reader 对象。
  4. FilterReader 是所有自定义具体装饰流的父类,其子类PushbackReader 对Reader 对象进行装饰,会增加一个行号。
  5. InputStreamReader 是一个连接字节流和字符流的桥梁,它将字节流转变为字符流。FileReader 可以说是一个达到此功能、常用的工具类,在其源代码中明显使用了将FileInputStream 转变为Reader 的方法。我们可以从这个类中得到一定的技巧。Reader 中各个类的用途和使用方法基本和InputStream 中的类使用一致。后面会有Reader 与InputStream 的对应关系。

 

5.字符输出流Writer

在上面的关系图中可以看出:

  1. Writer 是所有的输出字符流的父类,它是一个抽象类。
  2. CharArrayWriter、StringWriter 是两种基本的介质流,它们分别向Char 数组、String 中写入数据。PipedWriter 是向与其它线程共用的管道中写入数据,
  3. BufferedWriter 是一个装饰器为Writer 提供缓冲功能。
  4. PrintWriter 和PrintStream 极其类似,功能和使用也非常相似。
  5. OutputStreamWriter 是OutputStream 到Writer 转换的桥梁,它的子类FileWriter 其实就是一个实现此功能的具体类(具体可以研究一SourceCode)。功能和使用和OutputStream 极其类似,后面会有它们的对应图。

 

6.字符流的输入与输出的对应

 

7.字符流与字节流转换

转换流的特点:

  1. 其是字符流和字节流之间的桥梁
  2. 可对读取到的字节数据经过指定编码转换成字符
  3. 可对读取到的字符数据经过指定编码转换成字节

何时使用转换流?

  1. 当字节和字符之间有转换动作时;
  2. 流操作的数据需要编码或解码时。

具体的对象体现:

  1. InputStreamReader:字节到字符的桥梁
  2. OutputStreamWriter:字符到字节的桥梁

这两个流对象是字符体系中的成员,它们有转换作用,本身又是字符流,所以在构造的时候需要传入字节流对象进来。

 

8.File类

File类是对文件系统中文件以及文件夹进行封装的对象,可以通过对象的思想来操作文件和文件夹。 File类保存文件或目录的各种元数据信息,包括文件名、文件长度、最后修改时间、是否可读、获取当前文件的路径名,判断指定文件是否存在、获得当前目录中的文件列表,创建、删除文件和目录等方法。  

9.RandomAccessFile类

该对象并不是流体系中的一员,其封装了字节流,同时还封装了一个缓冲区(字符数组),通过内部的指针来操作字符数组中的数据。 该对象特点:

  1. 该对象只能操作文件,所以构造函数接收两种类型的参数:a.字符串文件路径;b.File对象。
  2. 该对象既可以对文件进行读操作,也能进行写操作,在进行对象实例化时可指定操作模式(r,rw)

注意:该对象在实例化时,如果要操作的文件不存在,会自动创建;如果文件存在,写数据未指定位置,会从头开始写,即覆盖原有的内容。 可以用于多线程下载或多个线程同时写数据到文件。

 四.文件读写
 

 
Java文件file操作总结

由于一直在使用Java file功能,但是总是很混乱,今天将Java File的所有功能做一个总结。

Java文件操作我个人认为重要的问题有:

  a:如何跨平台问题
  b:文件编码问题,尤其是多语言平台情况下如何正常工作。
  c:文件读写效率、操作效率
  d:文件加密和文件安全
  e:文件快速检索,强烈建议使用lence进行文件检索及文件管理。

以下是本人做的一些整理:

一:建立文件

   File file1 = new File ("C://temp//myNote.txt"); // in Windows  这是windows文件系统下的方法
   File file2 = new File ("/tmp/myNote.txt"); // in Linux/Unix        unix文件系统的方法

最安全的建立文件的方法:

  File myFile = new File("C:" + File.separator + "jdk1.5.0" + File.separator, "File.java");

   File.separator 是文件路径符号。
  System.out.println(myFile.getName());//取得文件名称的方法
  System.out.println(myFile.getPath());//取得文件路径的方法
  System.out.println(myFile.isAbsolute());//判断文件是否完整
  System.out.println(myFile.getParent());//取得文件的根目录
  System.out.println(myFile.exists());//判断文件是否存在
  System.out.println(myFile.isDirectory());//判断是否是目录
  System.out.println(myFile.isFile());//判断是否是文件
  System.out.println(myFile.isHidden());//判断是否是隐藏文件
  System.out.println(myFile.canRead());//判断是否可读
  System.out.println(myFile.canWrite());//判断是否可写

  File myFile_A = new File("C:" + File.separator);
       for(String s: myFile_A.list()){//读取某个目录下所有文件
       System.out.println(s);
       }

    File myFile_C=new File("d:/test.txt");
           System.out.println(new Date(myFile_C.lastModified()));//最后一次编辑时间
       myFile_C.renameTo(new File("c:/text.txt.bak"));//从命名
       myFile_C.setReadOnly();//设置为只读
二:文件过滤方法

   java提供了很好的文件过滤借口:FilenameFilter 过滤以后的文件可以用listFiles显示出来。效率还是非常高的。

import java.io.File;
import java.io.FilenameFilter;
import java.util.Date;
/**
* 文件过滤器过滤类
* @author fb
*/
class FileListFilter implements FilenameFilter {
 private String name;
 private String extension;
 public FileListFilter(String name, String extension) {
   this.name = name;
   this.extension = extension;
 }
 public boolean accept(File directory, String filename) {
   boolean fileOK = true;
   if (name != null) {
     fileOK = filename.startsWith(name);
   }
   if (extension != null) {
     fileOK = filename.endsWith('.' + extension);
   }
   return fileOK;
 }
}
测试方法:

import java.io.File;
import java.io.FilenameFilter;
import java.util.Date;
/**
* 文件过滤器   运行函数
* @author fb
*/
public class Run_FileListFilter {
 public static void main(String[] args) {
     File myDir = new File("d:/");
     FilenameFilter select = new FileListFilter("F", "txt");
     File[] contents = myDir.listFiles(select);
     for (File file : contents) {
       System.out.println(file + " is a " + (file.isDirectory() ? "directory" : "file")
           + " last modified on/n" + new Date(file.lastModified()));
     }
 }
}

三:建立目录、文件、临时文件、删除文件或目录
import java.io.File;
import java.io.IOException;

public class MakeDir {
 
 public static void main(String[] args) {
   File myFile=new File("D:/myFubin/");
   if(myFile.mkdir()){//单级目录
     System.out.println("建立目录成功");
   }else{
     System.out.println("建立目录失败");
   }
   
   File myFile_A=new File("D:/myFubin/test/");
   if(myFile_A.mkdirs()){//多级目录
     System.out.println("建立目录成功");
   }else{
     System.out.println("建立目录失败");
   }
   
   File file = new File("d://myFubin//test.txt");
   try {
     file.createNewFile();//建立空文件
   } catch (IOException e) {
     e.printStackTrace();
   }
   
   System.out.println(file.canRead());
   
   if(file.delete()){//删除文件或删除目录
     //删除文件的另外一个方法:file.deleteOnExit() 这种方法是在程序退出的时候将文件删除
     System.out.println("删除成功");
   }else{
     System.out.println("删除失败");
   }  
   
   try {
     File  tmp = File.createTempFile("foo", "tmp");//建立临时文件
     System.out.println("刚才建立的临时文件在:" + tmp.getCanonicalPath());
   } catch (IOException e) {
     e.printStackTrace();
   }
   
   
 }
 
}

 

import java.io.File;
/**
* @author fb  www.cujava.com
* 文件操作里Java 1.6 新功能:查询磁盘空间
*/
public class SpaceChecker {

  public static void main(String[] args) {
  File[] roots = File.listRoots();//取得所有的根,如果是windows系统那么将取得所有的磁盘
      for (int i = 0; i < roots.length; i++) {
        System.out.println(roots[i]);
        System.out.println("Free space = " + roots[i].getFreeSpace());
        System.out.println("Usable space = " + roots[i].getUsableSpace());
        System.out.println("Total space = " + roots[i].getTotalSpace());
        System.out.println();
      }
  }
}

 --------------------------------------------------------------------------------------------------------------------------------------------

JAVA文件操作

11.3 I/O类使用

         由于在IO操作中,需要使用的数据源有很多,作为一个IO技术的初学者,从读写文件开始学习IO技术是一个比较好的选择。因为文件是一种常见的数据源,而且读写文件也是程序员进行IO编程的一个基本能力。本章IO类的使用就从读写文件开始。

11.3.1 文件操作

         文件(File)是 最常见的数据源之一,在程序中经常需要将数据存储到文件中,例如图片文件、声音文件等数据文件,也经常需要根据需要从指定的文件中进行数据的读取。当然, 在实际使用时,文件都包含一个的格式,这个格式需要程序员根据需要进行设计,读取已有的文件时也需要熟悉对应的文件格式,才能把数据从文件中正确的读取出 来。

         文件的存储介质有很多,例如硬盘、光盘和U盘等,由于IO类设计时,从数据源转换为流对象的操作由API实现了,所以存储介质的不同对于程序员来说是透明的,和实际编写代码无关。

11.3.1.1 文件的概念

         文件是计算机中一种基本的数据存储形式,在实际存储数据时,如果对于数据的读写速度要求不是很高,存储的数据量不是很大时,使用文件作为一种持久数据存储的方式是比较好的选择。

         存储在文件内部的数据和内存中的数据不同,存储在文件中的数据是一种“持久存储”,也就是当程序退出或计算机关机以后,数据还是存在的,而内存内部的数据在程序退出或计算机关机以后,数据就丢失了。

         在不同的存储介质中,文件中的数据都是以一定的顺序依次存储起来,在实际读取时由硬件以及操作系统完成对于数据的控制,保证程序读取到的数据和存储的顺序保持一致。

         每个文件以一个文件路径和文件名称进行表示,在需要访问该文件的时,只需要知道该文件的路径以及文件的全名即可。在不同的操作系统环境下,文件路径的表示形式是不一样的,例如在Windows操作系统中一般的表示形式为C:\windows\system,而Unix上的表示形式为/user/my。所以如果需要让Java程序能够在不同的操作系统下运行,书写文件路径时还需要比较注意。

11.3.1.1.1 绝对路径和相对路径

         绝对路径是指书写文件的完整路径,例如d:\java\Hello.java,该路径中包含文件的完整路径d:\java以及文件的全名Hello.java。使用该路径可以唯一的找到一个文件,不会产生歧义。但是使用绝对路径在表示文件时,受到的限制很大,且不能在不同的操作系统下运行,因为不同操作系统下绝对路径的表达形式存在不同。

         相对路径是指书写文件的部分路径,例如\test\Hello.java,该路径中只包含文件的部分路径\test和文件的全名Hello.java,部分路径是指当前路径下的子路径,例如当前程序在d:\abc下运行,则该文件的完整路径就是d:\abc\test。使用这种形式,可以更加通用的代表文件的位置,使得文件路径产生一定的灵活性。

         Eclipse项目中运行程序时,当前路径是项目的根目录,例如工作空间存储在d:\javaproject,当前项目名称是Test,则当前路径是:d:\javaproject\Test。在控制台下面运行程序时,当前路径是class文件所在的目录,如果class文件包含包名,则以该class文件最顶层的包名作为当前路径。

         另外在Java语言的代码内部书写文件路径时,需要注意大小写,大小写需要保持一致,路径中的文件夹名称区分大小写。由于’\’Java语言中的特殊字符,所以在代码内部书写文件路径时,例如代表“c:\test\java\Hello.java”时,需要书写成“c:\\test\\java\\Hello.java”或“c:/test/java/Hello.java”,这些都需要在代码中注意。

11.3.1.1.2 文件名称

         文件名称一般采用“文件名.后缀名”的形式进行命名,其中“文件名”用来表示文件的作用,而使用后缀名来表示文件的类型,这是当前操作系统中常见的一种形式,例如“readme.txt”文件,其中readme代表该文件时说明文件,而txt后缀名代表文件时文本文件类型,在操作系统中,还会自动将特定格式的后缀名和对应的程序关联,在双击该文件时使用特定的程序打开。

         其实在文件名称只是一个标示,和实际存储的文件内容没有必然的联系,只是使用这种方式方便文件的使用。在程序中需要存储数据时,如果自己设计了特定的文件格式,则可以自定义文件的后缀名,来标示自己的文件类型。

         和文件路径一样,在Java代码内部书写文件名称时也区分大小写,文件名称的大小写必须和操作系统中的大小写保持一致。

         另外,在书写文件名称时不要忘记书写文件的后缀名。

11.3.1.2 File

         为了很方便的代表文件的概念,以及存储一些对于文件的基本操作,在java.io包中设计了一个专门的类——File类。

         File类中包含了大部分和文件操作的功能方法,该类的对象可以代表一个具体的文件或文件夹,所以以前曾有人建议将该类的类名修改成FilePath,因为该类也可以代表一个文件夹,更准确的说是可以代表一个文件路径。

         下面介绍一下File类的基本使用。

         1File对象代表文件路径

File类的对象可以代表一个具体的文件路径,在实际代表时,可以使用绝对路径也可以使用相对路径。

下面是创建的文件对象示例。

                   public File(String pathname)

         该示例中使用一个文件路径表示一个File类的对象,例如:

                   File f1 = new File(“d:\\test\\1.txt”);

                   File f2 = new File(“1.txt”);

                  File f3 = new File(“e:\\abc”);

这里的f1f2对象分别代表一个文件,f1是绝对路径,而f2是相对路径,f3则代表一个文件夹,文件夹也是文件路径的一种。

public File(String parent, String child)

                            也可以使用父路径和子路径结合,实现代表文件路径,例如:

                                     File f4 = new File(“d:\\test\\”,”1.txt”);

                            这样代表的文件路径是:d:\test\1.txt

         2File类常用方法

File类中包含了很多获得文件或文件夹属性的方法,使用起来比较方便,下面将常见的方法介绍如下:

                   acreateNewFile方法

                                     public boolean createNewFile() throws IOException

该方法的作用是创建指定的文件。该方法只能用于创建文件,不能用于创建文件夹,且文件路径中包含的文件夹必须存在。

                   bdelect方法

                                     public boolean delete()

该方法的作用是删除当前文件或文件夹。如果删除的是文件夹,则该文件夹必须为空。如果需要删除一个非空的文件夹,则需要首先删除该文件夹内部的每个文件和文件夹,然后在可以删除,这个需要书写一定的逻辑代码实现。

                   cexists方法

                                     public boolean exists()

                            该方法的作用是判断当前文件或文件夹是否存在。

                   dgetAbsolutePath方法

                                     public String getAbsolutePath()

该方法的作用是获得当前文件或文件夹的绝对路径。例如c:\test\1.t则返回c:\test\1.t

                   egetName方法

                                     public String getName()

                            该方法的作用是获得当前文件或文件夹的名称。例如c:\test\1.t,则返回1.t

                   fgetParent方法

                                     public String getParent()

                            该方法的作用是获得当前路径中的父路径。例如c:\test\1.t则返回c:\test

                   gisDirectory方法

                                     public boolean isDirectory()

                            该方法的作用是判断当前File对象是否是目录。

                   hisFile方法

                                     public boolean isFile()

                            该方法的作用是判断当前File对象是否是文件。

                   ilength方法

                                     public long length()

该方法的作用是返回文件存储时占用的字节数。该数值获得的是文件的实际大小,而不是文件在存储时占用的空间数。

                   jlist方法

                                     public String[] list()

该方法的作用是返回当前文件夹下所有的文件名和文件夹名称。说明,该名称不是绝对路径。

                   klistFiles方法

                                     public File[] listFiles()

                            该方法的作用是返回当前文件夹下所有的文件对象。

                   lmkdir方法

                                     public boolean mkdir()

该方法的作用是创建当前文件文件夹,而不创建该路径中的其它文件夹。假设d盘下只有一个test文件夹,则创建d:\test\abc文件夹则成功,如果创建d:\a\b文件夹则创建失败,因为该路径中d:\a文件夹不存在。如果创建成功则返回true,否则返回false

                   mmkdirs方法

                                     public boolean mkdirs()

该方法的作用是创建文件夹,如果当前路径中包含的父目录不存在时,也会自动根据需要创建。

                   nrenameTo方法

                                     public boolean renameTo(File dest)

该方法的作用是修改文件名。在修改文件名时不能改变文件路径,如果该路径下已有该文件,则会修改失败。

                   osetReadOnly方法

                                     public boolean setReadOnly()

                            该方法的作用是设置当前文件或文件夹为只读。

         3File类基本示例

                   以上各方法实现的测试代码如下:

                            import java.io.File;

/**

 * File类使用示例

 */

public class FileDemo {

         public static void main(String[] args) {

                   //创建File对象

                   File f1 = new File("d:\\test");

                   File f2 = new File("1.txt");

                   File f3 = new File("e:\\file.txt");

                   File f4 = new File("d:\\","1.txt");

                   //创建文件

                   try{

                            boolean b = f3.createNewFile();

                   }catch(Exception e){

                            e.printStackTrace();

                   }

                   //判断文件是否存在

                   System.out.println(f4.exists());

                   //获得文件的绝对路径

                   System.out.println(f3.getAbsolutePath());

                   //获得文件名

                   System.out.println(f3.getName());

                   //获得父路径

                   System.out.println(f3.getParent());

                   //判断是否是目录

                   System.out.println(f1.isDirectory());

                   //判断是否是文件

                   System.out.println(f3.isFile());

                   //获得文件长度

                   System.out.println(f3.length());

                   //获得当前文件夹下所有文件和文件夹名称

                   String[] s = f1.list();

                   for(int i = 0;i < s.length;i++){

                            System.out.println(s[i]);

                   }

                   //获得文件对象

                   File[] f5 = f1.listFiles();

                   for(int i = 0;i < f5.length;i++){

                            System.out.println(f5[i]);

                   }

                   //创建文件夹

                   File f6 = new File("e:\\test\\abc");

                   boolean b1 = f6.mkdir();

                   System.out.println(b1);

                   b1 = f6.mkdirs();

                   System.out.println(b1);

                   //修改文件名

                   File f7 = new File("e:\\a.txt");

                   boolean b2 = f3.renameTo(f7);

                   System.out.println(b2);

                   //设置文件为只读

                   f7.setReadOnly();             

         }

}

         4File类综合示例

下面以两个示例演示File类的综合使用。第一个示例是显示某个文件夹下的所有文件和文件夹,原理是输出当前名称,然后判断当前File对 象是文件还是文件夹,如果则获得该文件夹下的所有子文件和子文件夹,并递归调用该方法实现。第二个示例是删除某个文件夹下的所有文件和文件夹,原理是判断 是否是文件,如果是文件则直接删除,如果是文件夹,则获得该文件夹下所有的子文件和子文件夹,然后递归调用该方法处理所有子文件和子文件夹,然后将空文件 夹删除。则测试时谨慎使用第二个方法,以免删除自己有用的数据文件。示例代码如下:

                            import java.io.File;

/**

 * 文件综合使用示例

 */

public class AdvanceFileDemo {

         public static void main(String[] args) {

                   File f = new File("e:\\Book");

                   printAllFile(f);

                   File f1 = new File("e:\\test");

                   deleteAll(f1);

         }

        

         /**

          * 打印f路径下所有的文件和文件夹

          * @param f 文件对象

          */

         public static void printAllFile(File f){

                   //打印当前文件名

                   System.out.println(f.getName());

                   //是否是文件夹

                   if(f.isDirectory()){

                            //获得该文件夹下所有子文件和子文件夹

                            File[] f1 = f.listFiles();

                            //循环处理每个对象

                            int len = f1.length;

                            for(int i = 0;i < len;i++){

                                     //递归调用,处理每个文件对象

                                     printAllFile(f1[i]);

                            }

                   }

         }

        

         /**

          * 删除对象f下的所有文件和文件夹

          * @param f 文件路径

          */

         public static void deleteAll(File f){

                   //文件

                   if(f.isFile()){

                            f.delete();

                   }else{ //文件夹

                            //获得当前文件夹下的所有子文件和子文件夹

                            File f1[] = f.listFiles();

                            //循环处理每个对象

                            int len = f1.length;

                            for(int i = 0;i < len;i++){

                                     //递归调用,处理每个文件对象

                                     deleteAll(f1[i]);

                            }

                            //删除当前文件夹

                            f.delete();

                   }

         }

}

         关于File类的使用就介绍这么多,其它的方法和使用时需要注意的问题还需要多进行练习和实际使用。

11.3.1.3 读取文件

         虽然前面介绍了流的概念,但是这个概念对于初学者来说,还是比较抽象的,下面以实际的读取文件为例子,介绍流的概念,以及输入流的基本使用。

         按照前面介绍的知识,将文件中的数据读入程序,是将程序外部的数据传入程序中,应该使用输入流——InputStreamReader。而由于读取的是特定的数据源——文件,则可以使用输入对应的子类FileInputStreamFileReader实现。

         在实际书写代码时,需要首先熟悉读取文件在程序中实现的过程。在Java语言的IO编程中,读取文件是分两个步骤:1、将文件中的数据转换为流,2、读取流内部的数据。其中第一个步骤由系统完成,只需要创建对应的流对象即可,对象创建完成以后步骤1就完成了,第二个步骤使用输入流对象中的read方法即可实现了。

         使用输入流进行编程时,代码一般分为3个部分:1、创建流对象,2、读取流对象内部的数据,3、关闭流对象。下面以读取文件的代码示例:

                   import java.io.*;

/**

 * 使用FileInputStream读取文件

 */

public class ReadFile1 {

         public static void main(String[] args) {

                   //声明流对象

                   FileInputStream fis = null;                 

                   try{

                            //创建流对象

                            fis = new FileInputStream("e:\\a.txt");

                            //读取数据,并将读取到的数据存储到数组中

                            byte[] data = new byte[1024]; //数据存储的数组

                            int i = 0; //当前下标

                            //读取流中的第一个字节数据

                            int n = fis.read();

                            //依次读取后续的数据

                            while(n != -1){ //未到达流的末尾

                                     //将有效数据存储到数组中

                                     data[i] = (byte)n;

                                     //下标增加

                                     i++;

                                     //读取下一个字节的数据

                                      n = fis.read();

                            }

                           

                            //解析数据

                            String s = new String(data,0,i);

                            //输出字符串

                            System.out.println(s);

                   }catch(Exception e){

                            e.printStackTrace();

                   }finally{

                            try{

                                     //关闭流,释放资源

                                     fis.close();

                            }catch(Exception e){}

                   }

         }

}

         在该示例代码中,首先创建一个FileInputStream类型的对象fis

                   fis = new FileInputStream("e:\\a.txt");

         这样建立了一个连接到数据源e:\a.txt的流,并将该数据源中的数据转换为流对象fis,以后程序读取数据源中的数据,只需要从流对象fis中读取即可。

         读取流fis中的数据,需要使用read方法,该方法是从InputStream类中继承过来的方法,该方法的作用是每次读取流中的一个字节,如果需要读取流中的所有数据,需要使用循环读取,当到达流的末尾时,read方法的返回值是-1

         在该示例中,首先读取流中的第一个字节:

                   int n = fis.read();

         并将读取的值赋值给intn,如果流fis为空,则n的值是-1,否则n中的最后一个字节包含的时流fis中的第一个字节,该字节被读取以后,将被从流fis中删除。

         然后循环读取流中的其它数据,如果读取到的数据不是-1,则将已经读取到的数据n强制转换为byte,即取n中的有效数据——最后一个字节,并存储到数组data中,然后调用流对象fis中的read方法继续读取流中的下一个字节的数据。一直这样循环下去,直到读取到的数据是-1,也就是读取到流的末尾则循环结束。

         这里的数组长度是1024,所以要求流中的数据长度不能超过1024,所以该示例代码在这里具有一定的局限性。如果流的数据个数比较多,则可以将1024扩大到合适的个数即可。

         经过上面的循环以后,就可以将流中的数据依次存储到data数组中,存储到data数组中有效数据的个数是i个,即循环次数。

         其实截至到这里,IO操作中的读取数据已经完成,然后再按照数据源中的数据格式,这里是文件的格式,解析读取出的byte数组即可。

         该示例代码中的解析,只是将从流对象中读取到的有效的数据,也就是data数组中的前n个数据,转换为字符串,然后进行输出。

         在该示例代码中,只是在catch语句中输出异常的信息,便于代码的调试,在实际的程序中,需要根据情况进行一定的逻辑处理,例如给出提示信息等。

         最后在finally语句块中,关闭流对象fis,释放流对象占用的资源,关闭数据源,实现流操作的结束工作。

         上面详细介绍了读取文件的过程,其实在实际读取流数据时,还可以使用其它的read方法,下面的示例代码是使用另外一个read方法实现读取的代码:

                   import java.io.FileInputStream;

/**

 * 使用FileInputStream读取文件

 */

public class ReadFile2 {

         public static void main(String[] args) {

                   //声明流对象

                   FileInputStream fis = null;                 

                   try{

                            //创建流对象

                            fis = new FileInputStream("e:\\a.txt");

                            //读取数据,并将读取到的数据存储到数组中

                            byte[] data = new byte[1024]; //数据存储的数组

                            int i = fis.read(data);

                           

                            //解析数据

                            String s = new String(data,0,i);

                            //输出字符串

                            System.out.println(s);

                   }catch(Exception e){

                            e.printStackTrace();

                   }finally{

                            try{

                                     //关闭流,释放资源

                                     fis.close();

                            }catch(Exception e){}

                   }

         }

}

         该示例代码中,只使用一行代码:

                   int i = fis.read(data);

         就实现了将流对象fis中的数据读取到字节数组data中。该行代码的作用是将fis流中的数据读取出来,并依次存储到数组data中,返回值为实际读取的有效数据的个数。

         使用该中方式在进行读取时,可以简化读取的代码。

         当然,在读取文件时,也可以使用Reader类的子类FileReader进行实现,在编写代码时,只需要将上面示例代码中的byte数组替换成char数组即可。

使用FileReader读取文件时,是按照char为单位进行读取的,所以更适合于文本文件的读取,而对于二进制文件或自定义格式的文件来说,还是使用FileInputStream进行读取,方便对于读取到的数据进行解析和操作。

读取其它数据源的操作和读取文件类似,最大的区别在于建立流对象时选择的类不同,而流对象一旦建立,则基本的读取方法是一样,如果只使用最基本的read方法进行读取,则使用基本上是一致的。这也是IO类设计的初衷,使得对于流对象的操作保持一致,简化IO类使用的难度。

 程。

         基本的输出流包含OutputStreamWriter两个,区别是OutputStream体系中的类(也就是OutputStream的子类)是按照字节写入的,而Writer体系中的类(也就是Writer的子类)是按照字符写入的。

         使用输出流进行编程的步骤是:

                   1、建立输出流

                            建立对应的输出流对象,也就是完成由流对象到外部数据源之间的转换。

                   2、向流中写入数据

                            将需要输出的数据,调用对应的write方法写入到流对象中。

                   3、关闭输出流

                            在写入完毕以后,调用流对象的close方法关闭输出流,释放资源。

         在使用输出流向外部输出数据时,程序员只需要将数据写入流对象即可,底层的API实现将流对象中的内容写入外部数据源,这个写入的过程对于程序员来说是透明的,不需要专门书写代码实现。

         在向文件中输出数据,也就是写文件时,使用对应的文件输出流,包括FileOutputStreamFileWriter两个类,下面以FileOutputStream为例子说明输出流的使用。示例代码如下:

                   import java.io.*;

/**

 * 使用FileOutputStream写文件示例

 */

public class WriteFile1 {

         public static void main(String[] args) {

                   String s = "Java语言";

                   int n = 100;

                   //声明流对象

                   FileOutputStream fos = null;

                   try{

                            //创建流对象

                            fos = new FileOutputStream("e:\\out.txt");

                            //转换为byte数组

                            byte[] b1 = s.getBytes();

                            //换行符

                            byte[] b2 = "\r\n".getBytes();

                            byte[] b3 = String.valueOf(n).getBytes();

                            //依次写入文件

                            fos.write(b1);

                            fos.write(b2);

                            fos.write(b3);

                   } catch (Exception e) {

                            e.printStackTrace();

                   }finally{

                            try{

                                     fos.close();

                            }catch(Exception e){}

                   }

         }

}

         该示例代码写入的文件使用记事本打开以后,内容为:

                   Java语言

100

         在该示例代码中,演示了将一个字符串和一个int类型的值依次写入到同一个文件中。在写入文件时,首先创建了一个文件输出流对象fos

                   fos = new FileOutputStream("e:\\out.txt");

         该对象创建以后,就实现了从流到外部数据源e:\out.txt的连接。说明:当外部文件不存在时,系统会自动创建该文件,但是如果文件路径中包含未创建的目录时将出现异常。这里书写的文件路径可以是绝对路径也可以是相对路径。

         在 实际写入文件时,有两种写入文件的方式:覆盖和追加。其中“覆盖”是指清除原文件的内容,写入新的内容,默认采用该种形式写文件,“追加”是指在已有文件 的末尾写入内容,保留原来的文件内容,例如写日志文件时,一般采用追加。在实际使用时可以根据需要采用适合的形式,可以使用:

                   public FileOutputStream(String name, boolean append) throws FileNotFoundException

         只需要使用该构造方法在构造FileOutputStream对象时,将第二个参数append的值设置为true即可。

         流对象创建完成以后,就可以使用OutputStream中提供的wirte方法向流中依次写入数据了。最基本的写入方法只支持byte数组格式的数据,所以如果需要将内容写入文件,则需要把对应的内容首先转换为byte数组。

         这里以如下格式写入数据:首先写入字符串s,使用String类的getBytes方法将该字符串转换为byte数组,然后写入字符串“\r\n”,转换方式同上,该字符串的作用是实现文本文件的换行显示,最后写入int数据n,首先将n转换为字符串,再转换为byte数组。这种写入数据的顺序以及转换为byte数组的方式就是流的数据格式,也就是该文件的格式。因为这里写的都是文本文件,所以写入的内容以明文的形式显示出来,也可以根据自己需要存储的数据设定特定的文件格式。

         其实,所有的数据文件,包括图片文件、声音文件等等,都是以一定的数据格式存储数据的,在保存该文件时,将需要保存的数据按照该文件的数据格式依次写入即可,而在打开该文件时,将读取到的数据按照该文件的格式解析成对应的逻辑即可。

         最后,在数据写入到流内部以后,如果需要立即将写入流内部的数据强制输出到外部的数据源,则可以使用流对象的flush方法实现。如果不需要强制输出,则只需要在写入结束以后,关闭流对象即可。在关闭流对象时,系统首先将流中未输出到数据源中的数据强制输出,然后再释放该流对象占用的内存空间。

         使用FileWriter写入文件时,步骤和创建流对象的操作都和该示例代码一致,只是在转换数据时,需要将写入的数据转换为char数组,对于字符串来说,可以使用String中的toCharArray方法实现转换,然后按照文件格式写入数据即可。

         对于其它类型的字节输出流/字符输出流来说,只是在逻辑上连接不同的数据源,在创建对象的代码上会存在一定的不同,但是一旦流对象创建完成以后,基本的写入方法都是write方法,也需要首先将需要写入的数据按照一定的格式转换为对应的byte数组/char数组,然后依次写入即可。

         所以IO类的这种设计形式,只需要熟悉该体系中的某一个类的使用以后,就可以触类旁通的学会其它相同类型的类的使用,从而简化程序员的学习,使得使用时保持统一。 

0 0