一,java基础题
1.JVM的工作原理
在Java中引入了虚拟机的概念,即在机器和编译程序之间加入了一层抽象的虚拟的机器。这台虚拟的机器在任何平台上都提供给编译程序一个的共同的接口。编译程序只需要面向虚拟机,生成虚拟机能够理解的代码,然后由解释器来将虚拟机代码转换为特定系统的机器码执行。在Java中,这种供虚拟机理解的代码叫做字节码(ByteCode),它不面向任何特定的处理器,只面向虚拟机。每一种平台的解释器是不同的,但是实现的虚拟机是相同的。Java源程序经过编译器编译后变成字节码,字节码由虚拟机解释执行,虚拟机将每一条要执行的字节码送给解释器,解释器将其翻译成特定机器上的机器码,然后在特定的机器上运行。
操作系统装入jvm是通过jdk中java.exe来完成,通过下面4步来完成jvm环境. 1.创建jvm装载环境和配置 2.装载jvm.dll 3.初始化jvm.dll并挂界到JNIENV(JNI调用接口)实例 4.调用JNIEnv实例装载并处理class类。 一.jvm装入环境,jvm提供的方式是操作系统的动态连接文件.既然是文件那就一个装入路径的问题,java是怎么找这个路径的呢?当你在调用java test的时候,操作系统会在path下在你的java.exe程序,java.exe就通过下面一个过程来确定jvm的路径和相关的参数配置了.下面基于windows的实现的分析. 首先查找jre路径,java是通过GetApplicationHome api来获得当前的java.exe绝对路径,c:/j2sdk1.4.2_09/bin/java.exe,那么它会截取到绝对路径c:/j2sdk1.4.2_09/,判断c:/j2sdk1.4.2_09/bin/java.dll文件是否存在,如果存在就把c:/j2sdk1.4.2_09/作为jre路径,如果不存在则判断c:/j2sdk1.4.2_09/jre/bin/java.dll是否存在,如果存在这c:/j2sdk1.4.2_09/jre作为jre路径.如果不存在调用GetPublicJREHome查HKEY_LOCAL_MACHINE/Software/JavaSoft/Java Runtime Environment/“当前JRE版本号”/JavaHome的路径为jre路径。 然后装载jvm.cfg文件JRE路径+/lib+/ARCH(CPU构架)+/jvm.cfgARCH(CPU构架)的判断是通过java_md.c中GetArch函数判断的,该函数中windows平台只有两种情况:WIN64的‘ia64’,其他情况都为‘i386’。以我的为例:C:/j2sdk1.4.2_09/jre/lib/i386/jvm.cfg.主要的内容如下: -client KNOWN -server KNOWN -hotspot ALIASED_TO -client -classic WARN -native ERROR -green ERROR 在我们的jdk目录中jre/bin/server和jre/bin/client都有jvm.dll文件存在,而java正是通过jvm.cfg配置文件来管理这些不同版本的jvm.dll的.通过文件我们可以定义目前jdk中支持那些jvm,前面部分(client)是jvm名称,后面是参数,KNOWN表示jvm存在,ALIASED_TO表示给别的jvm取一个别名,WARN表示不存在时找一个jvm替代,ERROR表示不存在抛出异常.在运行java XXX是,java.exe会通过CheckJvmType来检查当前的jvm类型,java可以通过两种参数的方式来指定具体的jvm类型,一种按照jvm.cfg文件中的jvm名称指定,第二种方法是直接指定,它们执行的方法分别是“java -J”、“java -XXaltjvm=”或“java -J-XXaltjvm=”。如果是第一种参数传递方式,CheckJvmType函数会取参数‘-J’后面的jvm名称,然后从已知的jvm配置参数中查找如果找到同名的则去掉该jvm名称前的‘-’直接返回该值;而第二种方法,会直接返回“-XXaltjvm=”或“-J-XXaltjvm=”后面的jvm类型名称;如果在运行java时未指定上面两种方法中的任一一种参数,CheckJvmType会取配置文件中第一个配置中的jvm名称,去掉名称前面的‘-’返回该值。CheckJvmType函数的这个返回值会在下面的函数中汇同jre路径组合成jvm.dll的绝对路径。如果没有指定这会使用jvm.cfg中第一个定义的jvm.可以通过set _JAVA_LAUNCHER_DEBUG=1在控制台上测试. 最后获得jvm.dll的路径,JRE路径+/bin+/jvm类型字符串+/jvm.dll就是jvm的文件路径了,但是如果在调用java程序时用-XXaltjvm=参数指定的路径path,就直接用path+/jvm.dll文件做为jvm.dll的文件路径. 二:装载jvm.dll 通过第一步已经找到了jvm的路径,java通过LoadJavaVM来装入jvm.dll文件.装入工作很简单就是调用windows API函数: LoadLibrary装载jvm.dll动态连接库.然后把jvm.dll中的导出函数JNI_CreateJavaVM和JNI_GetDefaultJavaVMInitArgs挂接到InvocationFunctions变量的CreateJavaVM和GetDefaultJavaVMInitArgs函数指针变量上。jvm.dll的装载工作宣告完成。 三:初始化jvm,获得本地调用接口,这样就可以在java中调用jvm的函数了.调用InvocationFunctions->CreateJavaVM也就是jvm中JNI_CreateJavaVM方法获得JNIEnv结构的实例. 四:运行java程序. java程序有两种方式一种是jar包,一种是class. 运行jar,java -jar XXX.jar运行的时候,java.exe调用GetMainClassName函数,该函数先获得JNIEnv实例然后调用java类java.util.jar.JarFileJNIEnv中方法getManifest()并从返回的Manifest对象中取getAttributes("Main-Class")的值即jar包中文件:META-INF/MANIFEST.MF指定的Main-Class的主类名作为运行的主类。之后main函数会调用java.c中LoadClass方法装载该主类(使用JNIEnv实例的FindClass)。main函数直接调用java.c中LoadClass方法装载该类。如果是执行class方法。main函数直接调用java.c中LoadClass方法装载该类。
2.java有几种基本数据类型,它们有什么区别?
java的8种基本类型: byte,short, char, int, long,float,double,boolean. 与此对应的,java提供了8种包装类型: Byte,Short,Character,Integer,Long,Float,Double,Boolean.
基本型别 大小 最小值 最大值 boolean ----- ----- ------ char 16-bit Unicode 0 Unicode 2^16-1 byte 8-bit -128 +127 short 16-bit -2^15 +2^15-1 int 32-bit -2^31 +2^31-1 long 64-bit -2^63 +2^63-1 float 32-bit IEEE754 IEEE754 double 64-bit IEEE754 IEEE754
3.数组循环排序(冒泡法)
using System; using System.Collections.Generic; using System.Text; using System.Collections;
namespace ljun_CSharp_Study { /// <summary> /// 使用for循环对数组进行排序 /// </summary> class Paixu { static void Main(string[] args) { int[] myArray = new int[5] {12,9,35,68,51 }; int a = 0;
Console.WriteLine("排序前:"); //创建一个循环语句,从0开始,每次加1,并输出数组排序前的相应元素 for (int i = 0; i < 5; i++) { Console.WriteLine("myArray[{0}]的值为:{1}",i,myArray[i]); }
//采用冒泡法对数组进行排序 for (int i = 1; i < 5; i++) { for (int j = 0; j < 5 - i; j++) { if (myArray[j] > myArray[j + 1]) { a = myArray[j]; myArray[j] = myArray[j + 1]; myArray[j + 1] = a; } } }
Console.WriteLine("排序后:"); for (int i = 0; i < 5; i++) { Console.WriteLine("myArray[{0}]的值为:{1}",i,myArray[i]); }
Console.ReadLine(); } } }
4.Map、List、Set区别
有序否
|
允许元素重复否
| ||
Collection
|
否
|
是
| |
List
|
是
|
是
| |
Set
|
AbstractSet
|
否
|
否
|
HashSet
| |||
TreeSet
|
是(用二叉树排序)
| ||
Map
|
AbstractMap
|
否
|
使用key-value来映射和存储数据,Key必须惟一,value可以重复
|
HashMap
| |||
TreeMap
|
是(用二叉树排序)
|
List接口对Collection进行了简单的扩充,它的具体实现类常用的有ArrayList和LinkedList。你可以将任何东西放到一个List容器中,并在需要时从中取出。ArrayList从其命名中可以看出它是一种类似数组的形式进行存储,因此它的随机访问速度极快,而LinkedList的内部实现是链表,它适合于在链表中间需要频繁进行插入和删除操作。在具体应用时可以根据需要自由选择。前面说的Iterator只能对容器进行向前遍历,而ListIterator则继承了Iterator的思想,并提供了对List进行双向遍历的方法。 Set接口也是Collection的一种扩展,而与List不同的时,在Set中的对象元素不能重复,也就是说你不能把同样的东西两次放入同一个Set容器中。它的常用具体实现有HashSet和TreeSet类。HashSet能快速定位一个元素,但是你放到HashSet中的对象需要实现hashCode()方法,它使用了前面说过的哈希码的算法。而TreeSet则将放入其中的元素按序存放,这就要求你放入其中的对象是可排序的,这就用到了集合框架提供的另外两个实用类Comparable和Comparator。一个类是可排序的,它就应该实现Comparable接口。有时多个类具有相同的排序算法,那就不需要在每分别重复定义相同的排序算法,只要实现Comparator接口即可。集合框架中还有两个很实用的公用类:Collections和Arrays。Collections提供了对一个Collection容器进行诸如排序、复制、查找和填充等一些非常有用的方法,Arrays则是对一个数组进行类似的操作。 Map是一种把键对象和值对象进行关联的容器,而一个值对象又可以是一个Map,依次类推,这样就可形成一个多级映射。对于键对象来说,像Set一样,一个Map容器中的键对象不允许重复,这是为了保持查找结果的一致性;如果有两个键对象一样,那你想得到那个键对象所对应的值对象时就有问题了,可能你得到的并不是你想的那个值对象,结果会造成混乱,所以键的唯一性很重要,也是符合集合的性质的。当然在使用过程中,某个键所对应的值对象可能会发生变化,这时会按照最后一次修改的值对象与键对应。对于值对象则没有唯一性的要求。你可以将任意多个键都映射到一个值对象上,这不会发生任何问题(不过对你的使用却可能会造成不便,你不知道你得到的到底是那一个键所对应的值对象)。Map有两种比较常用的实现:HashMap和TreeMap。HashMap也用到了哈希码的算法,以便快速查找一个键,TreeMap则是对键按序存放,因此它便有一些扩展的方法,比如firstKey(),lastKey()等,你还可以从TreeMap中指定一个范围以取得其子Map。键和值的关联很简单,用pub(Object key,Object value)方法即可将一个键与一个值对象相关联。用get(Object key)可得到与此key对象所对应的值对象。
5.垃圾回收机制
1. class Bar { } 1. class Test { 2. Bar doBar() { 3. Bar b = new Bar(); 4. return b; 5. } 6. public static void main (String args[]) { 7. Test t = new Test(); 8. Bar newBar = t.doBar(); 9. System.out.println(“newBar”); 10. newBar = new Bar(); 11. System.out.println(“finishing”); 12. } 13. } At what point is the Bar object, created on line 3, eligible for garbage collection? A. After line 8. B. After line 10. C. After line 4, when doBar() completes. D. After line 11, when main() completes. Answer: C 这道题考察了2个知识点,1.垃圾回收器回收对象的原则,2.return返回的是引用还是新的对象。 关于第1点,垃圾回收器回收对象的原则是:如果一个对象没有在任何地方被引用,即没有任何一个引用指向这个对象,那么这个单元就是可以被回收的。 关于第2点,如果return返回时是新开辟一个内存空间,并且将原对象复制一份到这个新开辟的空间,然后返回指向这个新开辟空间的引用,那么行3创建的对象将在运行到行4时变成一个没有被任何地方引用的垃圾单元,可以被回收。如果return仅仅返回的是原对象的一个引用,那么行3创建的对象在行10的代码运行之前一直被引用,直到行10将变量newBar重定向到另一个新创建的Bar对象后,才不被任何一个地方引用,可以被回收。 在我印象中,对于非基本数据类型,return返回的仅仅是原对象的引用,不新建对象,因此我个人认为答案C是错误的,dakiler的回答是正确的,答案应该是B。
1.JVM的gc概述 gc即垃圾收集机制是指JVM用于释放那些不再使用的对象所占用的内存。Java语言并不要求JVM有gc,也没有规定gc如何工作。不过常用的JVM都有gc,而且大多数gc都使用类似的算法管理内存和执行收集操作。 在充分理解了垃圾收集算法和执行过程后,才能有效的优化它的性能。有些垃圾收集专用于特殊的应用程序。比如,实时应用程序主要是为了避免垃圾收集中断,而大多数OLTP应用程序则注重整体效率。理解了应用程序的工作负荷和JVM支持的垃圾收集算法,便可以进行优化配置垃圾收集器。 垃圾收集的目的在于清除不再使用的对象。gc通过确定对象是否被活动对象引用来确定是否收集该对象。gc首先要判断该对象是否是时候可以收集。两种常用的方法是引用计数和对象引用遍历。 1.1.引用计数 引用计数存储对特定对象的所有引用数,也就是说,当应用程序创建引用以及引用超出范围时,JVM必须适当增减引用数。当某对象的引用数为0时,便可以进行垃圾收集。 1.2.对象引用遍历 早期的JVM使用引用计数,现在大多数JVM采用对象引用遍历。对象引用遍历从一组对象开始,沿着整个对象图上的每条链接,递归确定可到达(reachable)的对象。如果某对象不能从这些根对象的一个(至少一个)到达,则将它作为垃圾收集。在对象遍历阶段,gc必须记住哪些对象可以到达,以便删除不可到达的对象,这称为标记(marking)对象。 下一步,gc要删除不可到达的对象。删除时,有些gc只是简单的扫描堆栈,删除未标记的未标记的对象,并释放它们的内存以生成新的对象,这叫做清除(sweeping)。这种方法的问题在于内存会分成好多小段,而它们不足以用于新的对象,但是组合起来却很大。因此,许多gc可以重新组织内存中的对象,并进行压缩(compact),形成可利用的空间。 为此,gc需要停止其他的活动活动。这种方法意味着所有与应用程序相关的工作停止,只有gc运行。结果,在响应期间增减了许多混杂请求。另外,更复杂的gc不断增加或同时运行以减少或者清除应用程序的中断。有的gc使用单线程完成这项工作,有的则采用多线程以增加效率。 2.几种垃圾回收机制 2.1.标记-清除收集器 这种收集器首先遍历对象图并标记可到达的对象,然后扫描堆栈以寻找未标记对象并释放它们的内存。这种收集器一般使用单线程工作并停止其他操作。 2.2.标记-压缩收集器 有时也叫标记-清除-压缩收集器,与标记-清除收集器有相同的标记阶段。在第二阶段,则把标记对象复制到堆栈的新域中以便压缩堆栈。这种收集器也停止其他操作。 2.3.复制收集器 这种收集器将堆栈分为两个域,常称为半空间。每次仅使用一半的空间,JVM生成的新对象则放在另一半空间中。gc运行时,它把可到达对象复制到另一半空间,从而压缩了堆栈。这种方法适用于短生存期的对象,持续复制长生存期的对象则导致效率降低。 2.4.增量收集器 增量收集器把堆栈分为多个域,每次仅从一个域收集垃圾。这会造成较小的应用程序中断。 2.5.分代收集器 这种收集器把堆栈分为两个或多个域,用以存放不同寿命的对象。JVM生成的新对象一般放在其中的某个域中。过一段时间,继续存在的对象将获得使用期并转入更长寿命的域中。分代收集器对不同的域使用不同的算法以优化性能。 2.6.并发收集器 并发收集器与应用程序同时运行。这些收集器在某点上(比如压缩时)一般都不得不停止其他操作以完成特定的任务,但是因为其他应用程序可进行其他的后台操作,所以中断其他处理的实际时间大大降低。 2.7.并行收集器 并行收集器使用某种传统的算法并使用多线程并行的执行它们的工作。在多cpu机器上使用多线程技术可以显著的提高Java应用程序的可扩展性。 3.Sun HotSpot 1.4.1 JVM堆大小的调整 Sun HotSpot 1.4.1使用分代收集器,它把堆分为三个主要的域:新域、旧域以及永久域。JVM生成的所有新对象放在新域中。一旦对象经历了一定数量的垃圾收集循环后,便获得使用期并进入旧域。 在永久域中JVM则存储class和method对象。就配置而言,永久域是一个独立域并且不认为是堆的一部分。下面介绍如何控制这些域的大小。可使用-Xms和-Xmx 控制整个堆的原始大小或最大值。 下面的命令是把初始大小设置为128M: Java –Xms128m –Xmx256m为控制新域的大小,可使用-XX:NewRatio设置新域在堆中所占的比例。下面的命令把整个堆设置成128m,新域比率设置成3,即新域与旧域比例为1:3,新域为堆的1/4或32M: Java –Xms128m –Xmx128m –XX:NewRatio =3可使用-XX:NewSize和-XX:MaxNewsize设置新域的初始值和最大值。 下面的命令把新域的初始值和最大值设置成64m: Java –Xms256m –Xmx256m –Xmn64m 永久域默认大小为4m。运行程序时,JVM会调整永久域的大小以满足需要。每次调整时,JVM会对堆进行一次完全的垃圾收集。 使用-XX:MaxPerSize标志来增加永久域搭大小。在WebLogic Server应用程序加载较多类时,经常需要增加永久域的最大值。当JVM加载类时,永久域中的对象急剧增加,从而使JVM不断调整永久域大小。为了避免调整,可使用-XX:PerSize标志设置初始值。下面把永久域初始值设置成32m,最大值设置成64m。 Java -Xms512m -Xmx512m -Xmn128m -XX:PermSize=32m -XX:MaxPermSize=64m 默认状态下,HotSpot在新域中使用复制收集器。该域一般分为三个部分。第一部分为Eden,用于生成新的对象。另两部分称为救助空间,当Eden充满时,收集器停止应用程序,把所有可到达对象复制到当前的from救助空间,一旦当前的from救助空间充满,收集器则把可到达对象复制到当前的to救助空间。 From和to救助空间互换角色。维持活动的对象将在救助空间不断复制,直到它们获得使用期并转入旧域。使用-XX:SurvivorRatio可控制新域子空间的大小。 同NewRation一样,SurvivorRation规定某救助域与Eden空间的比值。比如,以下命令把新域设置成64m,Eden占32m,每个救助域各占16m: Java -Xms256m -Xmx256m -Xmn64m -XX:SurvivorRation =2 如前所述,默认状态下HotSpot对新域使用复制收集器,对旧域使用标记-清除-压缩收集器。在新域中使用复制收集器有很多意义,因为应用程序生成的大部分对象是短寿命的。理想状态下,所有过渡对象在移出Eden空间时将被收集。 如果能够这样的话,并且移出Eden空间的对象是长寿命的,那么理论上可以立即把它们移进旧域,避免在救助空间反复复制。但是,应用程序不能适合这种理想状态,因为它们有一小部分中长寿命的对象。 最好是保持这些中长寿命的对象并放在新域中,因为复制小部分的对象总比压缩旧域廉价。为控制新域中对象的复制,可用-XX:TargetSurvivorRatio控制救助空间的比例(该值是设置救助空间的使用比例。 如救助空间位1M,该值50表示可用500K)。该值是一个百分比,默认值是50。当较大的堆栈使用较低的sruvivorratio时,应增加该值到80至90,以更好利用救助空间。用-XX:maxtenuring threshold可控制上限。 为放置所有的复制全部发生以及希望对象从eden扩展到旧域,可以把MaxTenuring Threshold设置成0。设置完成后,实际上就不再使用救助空间了,因此应把SurvivorRatio设成最大值以最大化Eden空间,设置如下: Java … -XX:MaxTenuringThreshold=0 –XX:SurvivorRatio=50000 … 4.BEA JRockit JVM的使用 Bea WebLogic 8.1使用的新的JVM用于Intel平台。在Bea安装完毕的目录下可以看到有一个类似于jrockit81sp1_141_03的文件夹。这就是Bea新JVM所在目录。不同于HotSpot把Java字节码编译成本地码,它预先编译成类。 JRockit还提供了更细致的功能用以观察JVM的运行状态,主要是独立的GUI控制台(只能适用于使用Jrockit才能使用jrockit81sp1_141_03自带的console监控一些cpu及memory参数)或者WebLogic Server控制台。 Bea JRockit JVM支持4种垃圾收集器: 4.1.1.分代复制收集器 它与默认的分代收集器工作策略类似。对象在新域中分配,即JRockit文档中的nursery。这种收集器最适合单cpu机上小型堆操作。 4.1.2.单空间并发收集器 该收集器使用完整堆,并与背景线程共同工作。尽管这种收集器可以消除中断,但是收集器需花费较长的时间寻找死对象,而且处理应用程序时收集器经常运行。如果处理器不能应付应用程序产生的垃圾,它会中断应用程序并关闭收集。 分代并发收集器 这种收集器在护理域使用排它复制收集器,在旧域中则使用并发收集器。由于它比单空间共同发生收集器中断频繁,因此它需要较少的内存,应用程序的运行效率也较高,注意,过小的护理域可以导致大量的临时对象被扩展到旧域中。这会造成收集器超负荷运作,甚至采用排它性工作方式完成收集。 4.1.3.并行收集器 该收集器也停止其他进程的工作,但使用多线程以加速收集进程。尽管它比其他的收集器易于引起长时间的中断,但一般能更好的利用内存,程序效率也较高。 默认状态下,JRockit使用分代并发收集器。要改变收集器,可使用: -Xgc: 对应四个收集器分别为gencopy,singlecon,gencon以及parallel。可使用-Xms和-Xmx设置堆的初始大小和最大值。要设置护理域,则使用: -Xns:Java –jrockit –Xms512m –Xmx512m –Xgc:gencon –Xns128m… 尽管JRockit支持-verbose:gc开关,但它输出的信息会因收集器的不同而异。JRockit还支持memory、load和codegen的输出。 注意 :如果 使用JRockit JVM的话还可以使用WLS自带的console(C:/bea/jrockit81sp1_141_03/bin下)来监控一些数据,如cpu,memery等。要想能构监控必须在启动服务时startWeblogic.cmd中加入-Xmanagement参数。 5.如何从JVM中获取信息来进行调整 -verbose.gc开关可显示gc的操作内容。打开它,可以显示最忙和最空闲收集行为发生的时间、收集前后的内存大小、收集需要的时间等。打开-xx:+ printgcdetails开关,可以详细了解gc中的变化。 打开-XX: + PrintGCTimeStamps开关,可以了解这些垃圾收集发生的时间,自JVM启动以后以秒计量。最后,通过-xx: + PrintHeapAtGC开关了解堆的更详细的信息。
6.输入输出流
IO两大主流,16位和8位 16位对应Writer 和 Reader 根据编程目的不同有 FileWriter和FileWriter 对文件进行读写。 StringWriter、StringReader 对内存里的位置进行读写。 PipedWrier、PipedReader 两个线程间通讯用 8位对应InputStream 和 OutputStream 根据编程目的不同有 FileInputStream、FileOutputStream 对文件进行读写 ByteArrayInputStream、ByteArrayOutputStream 对字节数组进行读写 PipedInputStream、PipedOutputStream 线程间通讯用注意:在数据流里不存在字符串的IO操作,那样就要用Reader和Writer类 二者的桥梁在InputStreamReader、OutputStreamWriter BufferedWriter、BufferedReader、BufferedInputStream、BufferedOutputStream 是用来提高IO速度的,注意包装的时候最好最先包装Buffered,这样效果会好些。 包装模式的理解。Package Pattern 各种Data Type的长度的记忆. 还要注意对象流的使用. 高级IO操作:信道IO,java.nio.channels包他的主要特征是可以对内存进行快读写操作,进行内存映射. 同时可以创建各种数据类型的缓冲区,从而提高IO操作效率. 一般使用比如: BufferedReader output_file_one=new BufferedReader(new InputStreamReader(new FileInputStream(input_file_one),"UTF8"));
Input和Output stream代表的是任何有能力产出数据的数据源,或是任何有能力接收数据的接收源。在Java的IO中,所有的stream(包括Input和Out stream)都包括两种类型: 1 以字节为导向的stream 以字节为导向的stream,表示以字节为单位从stream中读取或往stream中写入信息。以字节为导向的stream包括下面几种类型: input stream: 1) ByteArrayInputStream:把内存中的一个缓冲区作为InputStream使用 2) StringBufferInputStream:把一个String对象作为InputStream 3) FileInputStream:把一个文件作为InputStream,实现对文件的读取操作 4) PipedInputStream:实现了pipe的概念,主要在线程中使用 5) SequenceInputStream:把多个InputStream合并为一个InputStream Out stream: 1) ByteArrayOutputStream:把信息存入内存中的一个缓冲区中 2) FileOutputStream:把信息存入文件中 3) PipedOutputStream:实现了pipe的概念,主要在线程中使用 4) SequenceOutputStream:把多个OutStream合并为一个OutStream 2 以Unicode字符为导向的stream 以Unicode字符为导向的stream,表示以Unicode字符为单位从stream中读取或往stream中写入信息。以Unicode字符为导向的stream包括下面几种类型: Input Stream: 1) CharArrayReader:与ByteArrayInputStream对应 2) StringReader:与StringBufferInputStream对应 3) FileReader:与FileInputStream对应 4) PipedReader:与PipedInputStream对应 Out Stream: 1) CharArrayWrite:与ByteArrayOutputStream对应 2) StringWrite:无与之对应的以字节为导向的stream 3) FileWrite:与FileOutputStream对应 4) PipedWrite:与PipedOutputStream对应 以字符为导向的stream基本上对有与之相对应的以字节为导向的stream。两个对应类实现的功能相同,字是在操作时的导向不同。如 CharArrayReader:和ByteArrayInputStream的作用都是把内存中的一个缓冲区作为InputStream使用,所不同的是前者每次从内存中读取一个字节的信息,而后者每次从内存中读取一个字符。 3 两种不现导向的stream之间的转换 InputStreamReader和OutputStreamReader:把一个以字节为导向的stream转换成一个以字符为导向的stream。 stream添加属性 1 “为stream添加属性”的作用 运用上面介绍的Java中操作IO的API,我们就可完成我们想完成的任何操作了。但通过FilterInputStream和FilterOutStream的子类,我们可以为stream添加属性。下面以一个例子来说明这种功能的作用。 如果我们要往一个文件中写入数据,我们可以这样操作: FileOutStream fs = new FileOutStream(“test.txt”); 然后就可以通过产生的fs对象调用write()函数来往test.txt文件中写入数据了。但是,如果我们想实现“先把要写入文件的数据先缓存到内存中,再把缓存中的数据写入文件中”的功能时,上面的API就没有一个能满足我们的需求了。但是通过FilterInputStream和 FilterOutStream的子类,为FileOutStream添加我们所需要的功能。 2 FilterInputStream的各种类型 用于封装以字节为导向的InputStream 1) DataInputStream:从stream中读取基本类型(int、char等)数据。 2) BufferedInputStream:使用缓冲区 3) LineNumberInputStream:会记录input stream内的行数,然后可以调用getLineNumber()和setLineNumber(int) 4) PushbackInputStream:很少用到,一般用于编译器开发 用于封装以字符为导向的InputStream 1) 没有与DataInputStream对应的类。除非在要使用readLine()时改用BufferedReader,否则使用DataInputStream 2) BufferedReader:与BufferedInputStream对应 3) LineNumberReader:与LineNumberInputStream对应 4) PushBackReader:与PushbackInputStream对应 3 FilterOutStream的各种类型 用于封装以字节为导向的OutputStream 1) DataIOutStream:往stream中输出基本类型(int、char等)数据。 2) BufferedOutStream:使用缓冲区 3) PrintStream:产生格式化输出 用于封装以字符为导向的OutputStream 1) BufferedWrite:与对应 2) PrintWrite:与对应 RandomAccessFile
1) 可通过RandomAccessFile对象完成对文件的读写操作 2) 在产生一个对象时,可指明要打开的文件的性质:r,只读;w,只写;rw可读写 3) 可以直接跳到文件中指定的位置 I/O应用的一个例子 import java.io.*; public class TestIO{ public static void main(String[] args) throws IOException{ //1.以行为单位从一个文件读取数据 BufferedReader in = new BufferedReader( new FileReader("F:epalon/TestIO.java")); String s, s2 = new String(); while((s = in.readLine()) != null) s2 += s + ""; in.close(); //1b. 接收键盘的输入 BufferedReader stdin = new BufferedReader( new InputStreamReader(System.in)); System.out.println("Enter a line:"); System.out.println(stdin.readLine()); //2. 从一个String对象中读取数据 StringReader in2 = new StringReader(s2); int c; while((c = in2.read()) != -1) System.out.println((char)c); in2.close(); //3. 从内存取出格式化输入 try{ DataInputStream in3 = new DataInputStream( new ByteArrayInputStream(s2.getBytes())); while(true) System.out.println((char)in3.readByte()); } catch(EOFException e){ System.out.println("End of stream"); } //4. 输出到文件 try{ BufferedReader in4 = new BufferedReader( new StringReader(s2)); PrintWriter out1 = new PrintWriter( new BufferedWriter( new FileWriter("F:epalon/ TestIO.out"))); int lineCount = 1; while((s = in4.readLine()) != null) out1.println(lineCount++ + ":" + s); out1.close(); in4.close(); } catch(EOFException ex){ System.out.println("End of stream"); } //5. 数据的存储和恢复 try{ DataOutputStream out2 = new DataOutputStream( new BufferedOutputStream( new FileOutputStream("F:epalon/ Data.txt"))); out2.writeDouble(3.1415926); out2.writeChars("Thas was pi:writeChars"); out2.writeBytes("Thas was pi:writeByte"); out2.close(); DataInputStream in5 = new DataInputStream( new BufferedInputStream( new FileInputStream("F:epalon/ Data.txt"))); BufferedReader in5br = new BufferedReader( new InputStreamReader(in5)); System.out.println(in5.readDouble()); System.out.println(in5br.readLine()); System.out.println(in5br.readLine()); } catch(EOFException e){ System.out.println("End of stream"); } //6. 通过RandomAccessFile操作文件 RandomAccessFile rf = new RandomAccessFile("F:epalon/ rtest.dat", "rw"); for(int i=0; i<10; i++) rf.writeDouble(i*1.414); rf.close(); rf = new RandomAccessFile("F:epalon/ rtest.dat", "r"); for(int i=0; i<10; i++) System.out.println("Value " + i + ":" + rf.readDouble()); rf.close(); rf = new RandomAccessFile("F:epalon/ rtest.dat", "rw"); rf.seek(5*8); rf.writeDouble(47.0001); rf.close(); rf = new RandomAccessFile("F:epalon/ rtest.dat", "r"); for(int i=0; i<10; i++) System.out.println("Value " + i + ":" + rf.readDouble()); rf.close(); } } 关于代码的解释(以区为单位): 1区中,当读取文件时,先把文件内容读到缓存中,当调用in.readLine()时,再从缓存中以字符的方式读取数据(以下简称“缓存字节读取方式”)。 1b区中,由于想以缓存字节读取方式从标准IO(键盘)中读取数据,所以要先把标准IO(System.in)转换成字符导向的stream,再进行BufferedReader封装。 2区中,要以字符的形式从一个String对象中读取数据,所以要产生一个StringReader类型的stream。 4区中,对String对象s2读取数据时,先把对象中的数据存入缓存中,再从缓冲中进行读取;对TestIO.out文件进行操作时,先把格式化后的信息输出到缓存中,再把缓存中的信息输出到文件中。 5区中,对Data.txt文件进行输出时,是先把基本类型的数据输出屋缓存中,再把缓存中的数据输出到文件中;对文件进行读取操作时,先把文件中的数据读取到缓存中,再从缓存中以基本类型的形式进行读取。注意in5.readDouble()这一行。因为写入第一个writeDouble(),所以为了正确显示。也要以基本类型的形式进行读取。 6区是通过RandomAccessFile类对文件进行操作。 |