与之前写的Java随笔一样,此篇博客主要是用来记录我之前存放在本地的Word文档中的一些Java的自身理解,由于水平有限所以可能只适合自己加深理解与记忆。
Java中大文件相关的读写
RandomAccessFile
如下,就是一个简单的使用RandomAccessFile实现的,将文件复制到另外地方。
import java.io.IOException;
import java.io.RandomAccessFile;
public class Test{
public static void main(String[] args) throws IOException {
try(RandomAccessFile file = new RandomAccessFile("D:\\a.txt","rw");
RandomAccessFile fileB = new RandomAccessFile("D:\\b.txt","rw")
){
byte[] mm1 = new byte[(int) file.length()];
file.read(mm1);
fileB.write(mm1);
}
}
}
可以看到这里RandomAccessFile也是需要跟输入输出流一样进行关闭资源的,这里我们使用try-with-resource关闭资源。这里就是直接将a文件 全部读取到内存中,然后写入到fileB,看起来就跟我们正常的输入输出流差不多。
任意位置读写
下面演示使用RandomAccessFile对文件进行任意位置读取与追加。
假设一个文件a.txt,内容是123456789 而b.txt是一个有000000000内容的文件。
import java.io.IOException;
import java.io.RandomAccessFile;
public class TestFileChannel {
public static void main(String[] args) throws IOException {
try(RandomAccessFile fileA = new RandomAccessFile("D:\\a.txt","rw");
RandomAccessFile fileB = new RandomAccessFile("D:\\b.txt","rw")){
byte[] mm1 = new byte[(int) fileA.length()];
fileA.read(mm1);
fileB.seek(9);
fileB.write(mm1);
}
}
}
上面代码 就是读取a.txt所有字节,然后 fileB.seek(9) 就是跳过9个字节,之后执行fileB.write()就会将 123456789写入到b.txt中。最后b.txt的文件内容就是000000000123456789。
如果fileA.read(mm1) 之前写入fileA.seek(4) 我们会发现最后b.txt中会写入56789以及4个空白字节(实际应该是字节0,因为我们声明的byte数组大小是a.txt的字节数,我们跳过4个字节然后写入到byte数组,所以有4个字节不会被设值)。
所以通过Seek方法我们就能实现 在任意位置进行文件的读取和写入。
Seek与skipBytes
另外我们还能使用fileB.skipBytes(9)实现上面的效果,skipBytes() 含义就是从当前位置跳过指定字节,如果就算得出的新的偏移位置大于文件长度,那么就将偏移位置设置为文件长度。下面是seek和skipBytes的源码说明。
seek的相关代码实现如下:
public void seek(long pos) throws IOException {
if (pos < 0) {
throw new IOException("Negative seek offset");
} else {
seek0(pos);
}
}
private native void seek0(long pos) throws IOException;
可以看到如果穿进去的参数不为负数,就调用了一个本地方法seek0,也就是这里真实操作是使用本地方法实现的。
而skipBytes相关代码如下:
public int skipBytes(int n) throws IOException {
long pos;
long len;
long newpos;
if (n <= 0) {
return 0;
}
pos = getFilePointer();
len = length();
newpos = pos + n;
if (newpos > len) {
newpos = len;
}
seek(newpos);
/* return the actual number of bytes skipped */
return (int) (newpos - pos);
}
public native long getFilePointer() throws IOException;
我们可以看到在这里面也调用了seek方法进行位置的跳转。这里首先判断了传递的参数是否小于等于0,如果是 那么什么都不操作,直接返回0,而之后会调用getFilePointer 获取当前操作点的位置与文件开头的偏移量,(read、write、seek等方法 会影响这个getFilePointer返回的值),这里getFilePointer()也是一个本地方法。之后 用当前的偏移量与文件大小做对比,如果大于那么就将新的偏移量设置为文件大小,然后调用seek将当前偏移位置设置为新偏移位置,最后返回新旧位置差值。
Seek 可以将偏移位置设置为任意位置,skipBytes可以设置当前偏移位置向后跳过多少字节(如果新位置小于文件长度),skipBytes最大能到达文件末尾,skipBytes内部实际也是调用seek。
FileChannel
使用FileChannel可以更加容易 快速的进行文件复制。FileChannel减少了文件从内核态转为用户态(从Native方法的内存转到Java堆内存),因此快一些。
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class TestFileChannel {
public static void main(String[] args) throws IOException {
try(RandomAccessFile fileA = new RandomAccessFile("D:\\a.txt","rw");
RandomAccessFile fileB = new RandomAccessFile("D:\\b.txt","rw");
FileChannel fileChannelA = fileA.getChannel();
FileChannel fileChannelB = fileB.getChannel()){
fileChannelB.transferFrom(fileChannelA, 0, fileChannelA.size());
}
}
}
如上面 就是直接使用fileChannelB.transFrom 从fileChannelA中复制信息到b.txt,或者使用fileChannelA.transTo,以及使用如下通过将ByteBuffer作为中间对象实现复制。
ByteBuffer buffer = ByteBuffer.allocate((int) fileChannelA.size());
fileChannelA.read(buffer);
buffer.flip();
fileChannelB.write(buffer,0);
对RandomAccessFile做seek等操作,也能影响fileChannelB.write的位置。
或者可以使用FileChannel.open(Paths.get("D:\\a.txt"),StandardOpenOption.READ, StandardOpenOption.WRITE) 来得到FileChannel。然后操作FileChannel。
MappedByteBuffer
我们可以通过FileChannel对象的map方法,得到文件的区域位置映射。
如MappedByteBuffer mappedByteBufferA = fileChannelA.map(MapMode.READ_WRITE, 0, fileChannelA.size());得到可读可写的MappedByteBuffer。我们通过对这个可读可写的MappedByteBuffer操作,从而来影响文件数据。
public class TestFileChannel {
public static void main(String[] args) throws IOException {
try(RandomAccessFile fileA = new RandomAccessFile("D:\\a.txt","rw");
RandomAccessFile fileB = new RandomAccessFile("D:\\b.txt","rw");
FileChannel fileChannelA = FileChannel.open(Paths.get("D:\\a.txt"),
StandardOpenOption.READ, StandardOpenOption.WRITE);
FileChannel fileChannelB = fileB.getChannel()){
MappedByteBuffer mappedByteBufferA = fileChannelA.map(
MapMode.READ_ONLY, 0, fileChannelA.size());
MappedByteBuffer mappedByteBufferB = fileChannelB.map(
MapMode.READ_WRITE, 0, fileChannelA.size());
mappedByteBufferB.put(mappedByteBufferA);
mappedByteBufferB.force();
}
}
}
如上面的操作就会将a.txt的数据复制到b.txt,我们可以认为MappedByteBuffer就是文件在Java对象的一个映射,操作这个对象,就能影响文件内容。
FileChannel提供了map方法来把文件映射为MappedByteBuffer: MappedByteBuffer map(int mode,long position,long size); 可以把文件的从position开始的size大小的区域映射为MappedByteBuffer,mode指出了可访问该内存映像文件的方式,共有三种,分别为:
MapMode.READ_ONLY(只读): 试图修改得到的缓冲区将导致抛出 ReadOnlyBufferException。
MapMode.READ_WRITE(读/写): 对得到的缓冲区的更改最终将写入文件;但该更改对映射到同一文件的其他程序不一定是可见的(无处不在的“一致性问题”又出现了)。
MapMode.PRIVATE(专用): 可读可写,但是修改的内容不会写入文件,只是buffer自身的改变,这种能力称之为”copy on write”。
再简单的说一下,MappedByteBuffer较之ByteBuffer新增的三个方法:
fore()缓冲区是READ_WRITE模式下,此方法对缓冲区内容的修改强行写入文件。
load()将缓冲区的内容载入内存,并返回该缓冲区的引用。
isLoaded()如果缓冲区的内容在物理内存中,则返回真,否则返回假。
Java中Debug追踪源码,无法查看局部变量解决方法
在java中,我们使用Debug追踪到源码,发现Debug中无法查看局部变量的值,这造成了一定的困扰。这里出现这个问题的原因在于oracle提供的jre中rt.jar不带debug信息:orcale在编译src时使用了 javac -g:none,意思是不带任何调试信息,这样可以减小rt.jar的大小。若想正常调试jdk,就只能重新编译src.zip。这里介绍下编译src.zip的方法。
首先我们在eclipse里面建立一个Java项目,名为jdk,然后在src目录上导入"Archive File",选择对应jdk下的源码src.zip导入。
然后我们在项目上右键导出选择JAR file,导出的jar包名设置为rt_debug.jar。
最后修改我们eclipse里面对应的jre设置,将rt_debug.jar添加到JRE system libraries里面,并将其设置到最前面,这样我们在使用这个jre进行debug的时候就能看到局部变量的值了。