Java性能优化三:内存管理与垃圾回收机制,开发必备优化技巧!

一、Java 类加载机制的特点:

(1)基于父类的委托机制:运行一个程序时,总是由 AppClass Loader (系统类加载器)开始加载指定的类,在加载类时,每个类加载器会将加载任务上交给其父,如果其父找不到,再由自己去加载, Bootstrap Loader (启动类加载器)是最顶级的类加载器了,其父加载器为 null 。如果父类加载器找不到给定的类名,则交由子加载器去加载,如果最低一层的子加载器也无法找到,则抛出异常。

(2)全盘负责机制:所谓全盘负责,就是当一个类加载器负责加载某个 Class 时,该 Class 锁依赖的和引用的其他 Class 也将由该类加载器负责载入,除非显式使用另外一个类加载器来载入。

(3)缓存机制:缓存机制将会保证所有加载过的 Class 对象都会被缓存,当程序中需要使用某个 Class 时,类加载器会先从缓冲区中搜寻该 Class ,只有当缓存区中不存在该 Class 对象时,系统才会读取该类对应的二进制数据,并将其转化为 Class 对象,存入缓存区中。这就是为什么修改了 Class 后,必须重新启动 JVM ,程序所做的修改才会生效的原因。同时,往们比较 A.getClass() 与 B.getClass() 是否相等时,直接使用 == 比较,因为缓存机制保证类的字节码在内存中只可能存在一份。

(4)类加载器的三种方法以及其区别:

1、命令行启动应用时候由 JVM 初始化加载

2、通过 Class.forName() 方法动态加载

3、通过 ClassLoader.loadClass() 方法动态加载 // 使用 Class.forName() 来加载类,默认会执行初始化块 , // 使用 Class.forName() 来加载类,并指定 ClassLoader ,初始化时不执行静态块。

4、区别:使用 ClassLoader.loadClass() 来加载类,不会执行初始化块。

二、 什么时候判断一个对象可以被回收?

用可达性分析算法。这个算法的基本思路就是通过一系列的成为“ GC roots ”的对象作为起始点,从这些节点开始向下搜索,如果一个对象到 GCroots 没有任何引用链相连,则证明此对象是不可用的。可作为 GCroots 的对象包括虚拟机栈中引用的对象、方法区中常量引用的对象、方法区中静态属性引用的对象或者本地方法栈中 JNI 引用的对象,这些对象的共同点都是生命周期与程序的生命周期一样长,一般不会被 GC 。判断一个对象死亡,至少经历两次标记过程:如果对象在进行可达性算法后,发现没有与 GC Roots 相连接的引用链,那他将会被第一次标记,并在稍后执行其 finalize ()方法。执行是有机会,并不一定执行。稍后 GC 进行第二次标记,如果第一次标记的对象在 finalize ()方法中拯救自己,比如把自己赋值到某个引用上,则第二次标记时它将被移除出“即将回收”的集合,如果这个时候对象还没有逃脱,那基本上就会被 GC 了。

三、 垃圾回收器

( 1 )、 Serial 收集器

单线程收集器,收集时会暂停所有工作线程(我们将这件事情称之为 Stop The World ,下称 STW ),使用复制收集算法,虚拟机运行在 Client 模式时的默认新生代收集器。

(2)、 ParNew 收集器就是 Serial 的多线程版本,除了使用多条收集线程外,其余行为包括算法、 STW 、对象分配规则、回收策略等都与 Serial 收集器一摸一样。对应的这种收集器是虚拟机运行在 Server 模式的默认新生代收集器,在单 CPU 的环境中, ParNew 收集器并不会比 Serial 收集器有更好的效果。

(3)、Parallel Scavenge 收集器(下称 PS 收集器)也是一个多线程收集器,也是使用复制算法,但它的对象分配规则与回收策略都与 ParNew 收集器有所不同,它是以吞吐量最大化(即 GC 时间占总运行时间最小)为目标的收集器实现,它允许较长时间的 STW 换取总吞吐量最大化。

(4)、4.Serial Old 收集器 Serial Old 是单线程收集器,使用标记-整理算法,是老年代的收集器

(5)、 Parallel Old 收集器

老年代版本吞吐量优先收集器,使用多线程和标记-整理算法, JVM 1.6 提供,在此之前,新生代使用了 PS 收集器的话,老年代除 Serial Old 外别无选择,因为 PS 无法与 CMS 收集器配合工作。

(6)、CMS ( Concurrent Mark Sweep )收集器

CMS 是一种以最短停顿时间为目标的收集器,使用 CMS 并不能达到 GC 效率最高(总体 GC时间最小),但它能尽可能降低 GC 时服务的停顿时间,这一点对于实时或者高交互性应用(譬如证券交易)来说至关重要。

( 7 )、 G1 收集器。

四 、关于 Java 中生成对象的 4 种方式与区别:

( 1 )、使用 new 操作符,这是最普遍的一种(会调用相应的构造函数):

如: String s=new String("abc");

( 2 )、使用反射动态生成( 会调用相应的构造函数 ):

利用 Class , ClassLoader , Constructor 中的方法可以动态的生成类实例

如: Object o=Class.forName("java.lang.String").newInstance();

Object o=String.class.getClassLoader.loadClass("java.lang.String").newInstance();

以上的方式需要目标类拥有公有无参构造函数

以下使用 Constructor 进行动态生成

class User{

public User(String user,Integer id){}

}

Constructor c=User.class.getConstructor(new Class[]{String.class,Integer.class});

User user=(User)c.newInstance(new Object[]{"zhang san",123});

( 3 )、使用克隆生成对象( 不会调用构造函数 )

例如使用一个实现了 Cloneable 接口的对象,调用其 clone() 方法获得该对象的一份拷贝,使用 Java 序列化方式实现深拷贝。

( 4 )、利用反序列化从流中生成对象( 不会调用构造函数 ):

利用 ObjectInptuStream 的 readObject() 方法生成对象

五 、 JVM 运行时数据区域。

( 1 )、程序计数器:每一个 Java 线程都有一个程序计数器来用于保存程序执行到当前方法的哪一个指令。此内存区域是唯一一个在 JVM Spec 中没有规定任何 OutOfMemoryError 情况的区域。

( 2 )、 Java 虚拟机栈:该块内存描述的是 Java 方法调用的内存模型,每个方法在被执行的时候,都会同时创建一个帧( Frame )用于存储本地变量表、操作栈、动态链接、方法出入口等信息。

( 3 )、本地方法栈。本地方法调用的内存模型。

( 4 )、 Java 堆。 Java 中的对象以及类的静态变量的存放地方。

( 5 )、方法区:方法区中存放了每个 Class 的结构信息,包括常量池、字段描述、方法描述等等

( 6 )、运行时常量池: Class 文件中除了有类的版本、字段、方法、接口等描述等信息外,还有一项信息是常量表 (constant_pool table) ,用于存放编译期已可知的常量,这部分内容将在类加载后进入方法区(永久代)存放。但是 Java 语言并不要求常量一定只有编译期预置入 Class 的常量表的内容才能进入方法区常量池,运行期间也可将新内容放入常量池(最典型的 String.intern() 方法)。运行时常量池是方法区的一部分,自然受到方法区内存的限制,当常量池无法在申请到内存时会抛出 OutOfMemoryError 异常。

( 7 )、本机直接内存( Direct Memory )

在 JDK1.4 中新加入了 NIO 类,引入一种基于渠道与缓冲区的 I/O 方式,它可以通过本机 Native 函数库直接分配本机内存,然后通过一个存储在 Java 堆里面的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在 Java 对和本机堆中来回复制数据。

六 、 Java 对象的创建过程以及如何保证对象创建的多线程的安全性:

虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载、解析和初始化过。如果没有则进行类加载过程。

在类加载通过后,接下来虚拟机将为新生对象分配内存。对象所需的内存的大小在类加载完成后便可完全确定。为对象分配空间的任务等价于把一块确定大小的内存从 Java 堆中划分出来。

保证多线程的安全性。 有两种方案,一种是对分配内存的动作进行同步操作,实际上虚拟机采用 CAS 加上失败重试的方式保证更新操作的原子性。另一种是把内存分配的动作按照线程划分在不同的空间中进行。即为每个线程在 Java 堆中预先分配一小块内存,成为本地线程分配缓冲( TLAB )。哪个线程要分配内存,就在哪个 TLAB 上分配,只有 TLAB 用完并分配新的 TLAB时,才需要分配新的 TLAB 。

七 、类的主动引用

什么情况下需要开始类加载过程的第一个阶段,也即类的初始化阶段。 Java 虚拟机规定了有且只有 5 种情况下必须立即对类进行初始化:

(1)、遇到 new 、 getstatic 、 putstatic 或 invokestatic 这四条字节码指令时,如果类没有进行过初始化,则需要触发其初始化。(而且初始化的时候按照先父后子的顺序)。这四条指令最常见的 Java 代码场景是:使用 new 关键字实例化对象的时候、读取或设置一个类的静态字段(被 final 修饰,已在编译时期把结果放入常量池的静态字段除外)、调用一个类的静态方法的的时候。

(2)、使用 java.lang.reflect 包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先对其进行初始化。

(3)、当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。但是一个接口在初始化时,并不要求其父类接口全部都完成了初始化,只有在真正使用到父接口的时候(如引用接口中定义的常量)才会被初始化。

(4)、当虚拟机启动时,用户需要指定一个要执行的主类(包含 main ()方法的那个类),虚拟机会先初始化这个主类。

(5)、当使用 jdk1.7 的动态语言支持时,如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果 REF_getStatic 、 REF_putStatic 、 REF_invokeStatic 的方法句柄,并且这个方法句柄所对应的类没有初始化过,则需要先触发其初始化。

八 、类的被动引用

1、对于静态字段,只有直接定义这个字段的类才会被初始化,因此通过其子类来引用父类中定义的静态字段,只会触发父类的初始化而不会触发子类的初始化。

2、通过数组定义来引用类,不会触发类的初始化 SuperClass[]sca=new SuperClass[10].

3、常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发常量的类的初始化。

4 、 关于 finalize ()方法的作用的说明:

finalize ()方法的工作原理理论上是这样的:一旦垃圾回收器准备好释放占用的存储空间,将首先调用其 finalize ()方法,并且在下一次垃圾回收动作发生时,才会真正回收对象占用的内存,所以使用 finalize ()的目的就是在垃圾回收时刻做一些重要的清理工作。我们知道,使用 GC 的唯一原因就是回收程序不再使用的内存,所以对于与垃圾回收有关的任何行为来说,包括 finalize() 方法,它们也必须同内存及其回收有关。个人认为 Java 对象的 finalize ()方法有两个作用( 1 )回收通过创建对象方式以外的方式为对象分配了存储空间。比如,比如在 Java 代码中采用了 JNI 操作,即在内存分配时,采用了类似 C 语言中的 malloc 函数来分配内存,而且没有调用free 函数进行释放。此时就需要在 finalize ()中用本地方法调用 free 函数以释放内存。( 2 )对象终结条件的验证,即用来判定对象是否符合被回收条件。比如,如果要回收一个对象,对象被清理时应该处于某种状态,比如说是一个打开的文件,在回收之前应该关闭这个文件。只要对象中存在没有被适当清理的部分, finalize ()就可以用来最终法相这种情况。因为对象在被清理的时候肯定处于生命周期的最后一个阶段,如果此时还含有一些未释放的资源,则有能力释放这些资源。这个不是 C/C++ 里面的析构函数,它运行代价高昂,不确定性大,无法保证各个对象的调用顺序。需要关闭外部资源之类的事情,基本上它能做的使用 try-finally 可以做的更好。

九、 一个类被回收的条件

(1)、该类所有的实例都已经为 GC ,也就是说 JVM 中不存在该 Class 的任何实例。

(2)、加载该类的 ClassLoader 已经被 GC 。

(3)、该类对应的 java.lang.Class 对象没有在任何地方被引用,如不能在任何地方通过反射访问类的方法。

十、 垃圾回收算法 :

(1)、标记 - 清除算法:标记阶段根据根节点标记所有从根节点开始的可达对象。则未被标记的对象就是未被引用的垃圾对象,然后在清除阶段,清楚所有未被标记的对象。其最大缺点是空间碎片。

(2)、复制算法:将原有的内存空间分为两块,每次只使用其中一块,在垃圾回收时,将正在使用的内存中的存活对象复制到未使用的内存块中,之后清楚正在使用的内存快中的所有对象,然后交换两个内存的角色。完成垃圾回收。这种算法比较适合新生代,因为在新生代,垃圾对象通常会多于存活对象,复制算法效果较好。 Java 的新生代串行 GC 中,就使用了复制算法的思想。新生代分为 eden 空间、 from 空间和 to 空间三个部分。 From 和 to 空间可以视为用于复制的两块大小相同、地位相等、且可以进行角色互换的空间块。 From 和 to 空间也成为 survivor 空间,即幸存者空间,用于存放未被回收的对象。

(3)、标记 - 压缩算法:标记过程与标记清楚算法一样,但后续不是直接对可回收对象进行清理,而是让所有存活的对象向一段移动,然后直接清理掉端边界以外的内存。适合老年代的回收。

(4)、分代收集算法。

十一 、内存分配与回收策略:

( 1 )、规则一:通常情况下,对象在 eden 中分配。当 eden 无法分配时,触发一次 Minor GC 。

( 2 )、规则二:配置了 PretenureSizeThreshold 的情况下,对象大于设置值将直接在老年代分配。

( 3 )、规则三:在 eden 经过 GC 后存活,并且 survivor 能容纳的对象,将移动到 survivor 空间内,如果对象在 survivor 中继续熬过若干次回收(默认为 15 次)将会被移动到老年代中。回收次数由 MaxTenuringThreshold 设置。

( 4 )、规则四:如果在 survivor 空间中相同年龄所有对象大小的累计值大于 survivor 空间的一半,大于或等于该年龄的对象就可以直接进入老年代,无需达到 MaxTenuringThreshold 中要求的年龄。

( 5 )、规则五:在 Minor GC 触发时,会检测之前每次晋升到老年代的平均大小是否大于老年代的剩余空间,如果大于,改为直接进行一次 Full GC ,如果小于则查看 HandlePromotionFailure 设置看看是否允许担保失败,如果允许,那仍然进行 Minor GC ,如果不允许,则也要改为进行一次 Full GC 。

十二、 关于 Minor GC 与 Full GC

Java 堆,分配对象实例所在空间,是 GC 的主要对象。分为新生代 (Young Generation/New)和老年代 (Tenured Generation/Old) 。新生代又划分成 Eden Space 、 From Survivor/Survivor 0 、

To Survivor/Survivor 1 。

新生代要如此划分是因为新生代使用的 GC 算法是复制收集算法。新生代使用赋值收集算法,但是为了内存利用率,只使用一个 Survivor 空间来作为轮转备份(之所以把该空间分为 FromSpace 和 ToSpace 两部分是为了在 Minor GC 的时候把一些 age 大的对象从新生代空间中复制到老年代空间中)这种算法效率较高,而 GC 主要是发生在对象经常消亡的新生代,因此新生代适合使用这种复制收集算法。由于有一个假设:在一次新生代的 GC(Minor GC) 后大部分的对象占用的内存都会被回收,因此留存的放置 GC 后仍然活的对象的空间就比较小了。这个留存的空间就是 Survivor space : From Survivor 或 To Survivor 。这两个 Survivor 空间是一样大小的。例如,新生代大小是 10M(Xmn10M) ,那么缺省情况下 (-XX:SurvivorRatio=8) , Eden Space 是 8M , From 和 To 都是 1M 。

在 new 一个对象时,先在 Eden Space 上分配,如果 Eden Space 空间不够就要做一次 Minor GC 。 Minor GC 后,要把 Eden 和 From 中仍然活着的对象们复制到 To 空间中去。如果 To 空间不能容纳 Minor GC 后活着的某个对象,那么该对象就被 promote 到老年代空间。从 Eden 空间被复制到 To 空间的对象就有了 age=1 。此 age=1 的对象如果在下一次的 Minor GC 后仍然存活,它还会被复制到另一个 Survivor 空间 ( 如果认为 From 和 To 是固定的,就是又从 To 回到了From 空间 ) ,而它的 age=2 。如此反复,如果 age 大于某个阈值 (-XX:MaxTenuringThreshold=n),那个该对象就也可以 promote 到老年代了。

如果 Survivor 空间中相同 age( 例如, age=5) 对象的总和大于等于 Survivor 空间的一半,那么 age>=5 的对象在下一次 Minor GC 后就可以直接 promote 到老年代,而不用等到 age 增长到阈值。

在做 Minor GC 时,只对新生代做回收,不会回收老年代。即使老年代的对象无人索引也将仍然存活,直到下一次 Full GC 。

在发生 Minor GC 之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那么 Minor GC 可以确保是安全的。如果经过 Minor GC 后仍有大量对象存活的情况,则需要老年代进行分配担保,把 Survior 无法容纳的对象直接进入老年代。

十三 、四种引用类型:

( 1 )、强引用:直接关联,虚拟机永远不会回收。

( 2 )、软引用:描述一些还有用但并非必须的对象,虚拟机会在抛出内存溢出异常之前会对 这些对象进行第二次回收。

( 3 )弱引用:虚拟机一定会回收的对象

( 4 )虚引用:为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。

十四、Java性能优化系列之五--JavaIO

1 、关于 Java 序列化与反序列化:

(1)、作用:

实现对象状态的保存到本地,以便下一次启动虚拟机的时候直接读取保存的序列化字节生成对象,而不是初始化对象;

实现对象的网络传输( RMI 分布对象);

实现对象的深拷贝。

(2)、基本方式:

ObjectOutputStream 只能对 Serializable 接口的类的对象进行序列化。默认情况下, ObjectOutputStream 按照默认方式序列化,这种序列化方式仅仅对对象的非 transient 的实例变量进行序列化,而不会序列化对象的 transient 的实例变量,也不会序列化静态变量。

当 ObjectOutputStream 按照默认方式反序列化时,具有如下特点:

a、 如果在内存中对象所属的类还没有被加载,那么会先加载并初始化这个类。如果在 classpath 中不存在相应的类文件,那么会抛出 ClassNotFoundException ;

b、在反序列化时不会调用类的任何构造方法。

如果用户希望控制类的序列化方式,可以在可序列化类中提供以下形式的 writeObject() 和 readObject() 方法。

private void writeObject(java.io.ObjectOutputStream out) throws IOException

private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException;

当 ObjectOutputStream 对一个 Customer 对象进行序列化时,如果该对象具有 writeObject() 方法,那么就会执行这一方法,否则就按默认方式序列化。在该对象的 writeObjectt() 方法中,可以先调用 ObjectOutputStream 的 defaultWriteObject() 方法,使得对象输出流先执行默认的序列化操作。同理可得出反序列化的情况,不过这次是 defaultReadObject() 方法。

有些对象中包含一些敏感信息,这些信息不宜对外公开。如果按照默认方式对它们序列化,那么它们的序列化数据在网络上传输时,可能会被不法份子窃取。对于这类信息,可以对它们进行加密后再序列化,在反序列化时则需要解密,再恢复为原来的信息。

默认的序列化方式会序列化整个对象图,这需要递归遍历对象图。如果对象图很复杂,递归遍历操作需要消耗很多的空间和时间,它的内部数据结构为双向列表。

在应用时,如果对某些成员变量都改为 transient 类型,将节省空间和时间,提高序列化的性能。

(3)、Java 自定义序列化反序列化:复写实现了 seriliable 的实体类的 readObject() 和 writeObject() 的方法的原因:

有些对象中包含一些敏感信息,这些信息不宜对外公开。如果按照默认方式对它们序列化,那么它们的序列化数据在网络上传输时,可能会被不法份子窃取。对于这类信息,可以对它们进行加密后再序列化,在反序列化时则需要解密,再恢复为原来的信息。此时便不能使用默认的 readObject 和 writeObject() 方法。

一般情况直接实现 Serializable 接口就可以实现序列化的要求,但是有些情况需要对序列化做一些特殊的要求。

(4)、Externalize 的作用:

Externalizable 接口继承自 Serializable 接口,如果一个类实现了 Externalizable 接口,那么将完全由这个类控制自身的序列化行为。 Externalizable 接口声明了两个方法:

public void writeExternal(ObjectOutput out) throws IOException

public void readExternal(ObjectInput in) throws IOException , ClassNotFoundException

前者负责序列化操作,后者负责反序列化操作。

在对实现了 Externalizable 接口的类的对象进行反序列化时, 会先调用类的不带参数的构造方法,这是有别于默认反序列方式的。如果把类的不带参数的构造方法删除 ,或者把该构造方法的访问权限设置为 private 、默认或 protected 级别,会抛出 java.io.InvalidException: no valid constructor 异常。

(5)、与 Java 构造函数的关系:

实现了 Externalizable 接口的类的对象进行反序列化时,会先调用类的不带参数的构造方法;而实现了 Serializable 接口的类的对象进行反序列化时,不会调用任何构造方法。仅仅是根据所保存的对象的状态信息,在内存中重新构建对象!

(6)、注意事项:

a、序列化运行时使用一个称为 serialVersionUID 的版本号与每个可序列化类相关联,该序列号在反序列化过程中用于验证序列化对象的发送者和接收者是否为该对象加载了与序列化兼容的类。为它赋予明确的值。显式地定义 serialVersionUID 有两种用途:

在某些场合,希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有相同的 serialVersionUID ;

在某些场合,不希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有不同的 serialVersionUID 。

b、 java 有很多基础类已经实现了 serializable 接口,比如 string,vector 等。但是比如 hashtable 就没有实现 serializable 接口。

c、并不是所有的对象都可以被序列化。由于安全方面的原因一个对象拥有 private,public 等 field, 对于一个要传输的对象 , 比如写到文件 , 或者进行 rmi 传输等等 , 在序列化进行传输的过程中 ,这个对象的 private 等域是不受保护的;资源分配方面的原因 , 比如 socket,thread 类 , 如果可以序列化 , 进行传输或者保存 , 也无法对他们进行重新的资源分配 , 而且 , 也是没有必要这样实现 .

d、反序列化对象时,并不会调用该对象的任何构造方法,仅仅是根据所保存的对象的状态信息,在内存中重新构建对象!

e、当一个对象被序列化时,只保存对象的非静态成员变量,不能保存任何的成员方法和静态的成员变量

f、如果一个对象的成员变量是一个对象,那么这个对象的数据成员也会被保存!这是能用序列化解决深拷贝的重要原因。

(7)、序列化与单例模式的冲突解决办法:

另外还有两个自定义序列化方法 writeReplace 和 readResolve ,分别用来在序列化之前替换序列化对象 和 在反序列化之后的对返回对象的处理。一般可以用来避免 singleTon 对象跨 jvm 序列化和反序列化时产生多个对象实例,事实上 singleTon 的对象一旦可序列化,它就不能保证 singleTon 了。 JVM 的 Enum 实现里就是重写了 readResolve 方法,由 JVM 保证 Enum 的值都是 singleTon 的,所以建议多使用 Enum 代替使用 writeReplace 和 readResolve 方法。

Java 代码

private Object readResolve(){

return INSTANCE;

}

private Object writeReplace(){

return INSTANCE;

}

注: writeReplace 调用在 writeObject 前 ;readResolve 调用在 readObject 之后。

2、JavaIO 中的装饰模式:

Java 中使用的最广泛的装饰器模式就是 JavaIO 类的设计。比如, OutPutStream 是输出流的基类,其子类有 FileOutputStream 和 FilterOutputStream, 而 FilterOutputStream 的子类有 BufferedOutputStream 和 DataOutputStream 两个子类。其中, FileOutputStream 为系统的核心类,它实现了向文件写数据的功能,使用 DataOutputStream 可以在 FileOutputStream 的基础上增加多种数据类型的写操作支持( DataOutputStream 类中有 writeUTF 、 writeInt 等函数),而 BufferdOutputStream 装饰器可以对 FileOutputStream 增加缓冲功能,优化 I/O 性能。

3、JavaIO 流的使用场景:

(1)、IO 流:用于处理设备上的数据,这里的设备指的是:硬盘上的文件、内存、键盘输入、屏幕显示。

(2)、字节流和字符流:字节流好理解,因为所有格式的文件都是以字节形式硬盘上存储的,包括图片、 MP3 、 avi 等,因此字节流可以处理所有类型的数据。字符流读取的时候读到一个或多个字节时(中文对应的 字节数是两个,在 UTF-8 码表中是三个字节)时,先去查指定的编码表,将查到的字符返回。字符流之所以出现,就是因为有了文件编码的不同,而有了对字符进行高效操作的字符流对象。因此,只要是处理纯文本数据,就要优先考虑使用字符流,除此之外都使用字节流。

十五、其他流对象:

( 1 )、打印流:

PrintStream :是一个字节打印流 System.out 对应的就是 PrintStream 。它的构造函数可以接收三种数据类型的值:字符串路径、 File 对象、 OutputStream (当为 System.out 的时候即把输入显示到屏幕上)

PrintWriter :是一个字符打印流。构造函数可以接收四种类型的值。字符串路径、 File 对象(对于这两中类型的数据,还可以指定编码表。也即是是字符集)、 OutPutSream 、 Writer (对于三、四类型的数据,可以指定自动刷新,注意:当自动刷新的值为 true 时,只有三个方法可以用: printlf 、 printf 、 format )

(2)、管道流: PipedOutputStream 和 PipedInputStream 。一般在多线程中通信的时候用。

(3)、RandomAccessFile :该对象不是流体系中的一员,但是该队选中封装了字节流,同时还封装了一个缓冲区(字节数组),通过内部的指针来操作数组中的数据。该对象特点:只能操作文件和对文件读写都可以。多用于多线程下载。、

(4)、合并流:可以将多个读取流合并成一个流。其实就是将每一个读取流对象存储到一个集合中,最后一个流对象结尾作为这个流的结尾。

(5)、对象的序列化。 ObjectInputStream 和 ObjectInputStream 。

(6)、操作基本数据类型的流对象: DataInputStream 和 DataOutputStream 。

(7)、操纵内存数组的流对象,这些对象的数据源是内存,数据汇也是内存: ByteArrayInputStream 和 ByteArrayOutputStream , CharArrayReader 和 CharArrayWriter 。这些流并未调用系统资源,使用的是内存中的数组,所以在使用的时候不用 close 。

(8)、编码转换:

在 IO 中涉及到编码转换的流是转换流和打印流,但是打印流只有输出。转换流是可以指定编码表的,默认情况下,都是本机默认的编码表, GBK 。可以通过: Syetem.getProperty( “file.encoding”) 得到。字符串到字节数组成为编码的过程,通过 getBytes(charset) 完成,从字节数组到字符串的过程是解码的过程,通过 String 类的构造函数完成 String ( byte[],charset)。

好了,本篇文章就分享到这里了。有兴趣的新手伙伴们可以关注收藏起来,以后需要的时候可以多看看。如果有正在学java的程序员,可来我们的java技术学习扣qun哦:59789,1510里面免费送java的视频系统教程!

猜你喜欢

转载自blog.csdn.net/weixin_43660525/article/details/85685316
今日推荐