内容太多,记不住。。
哥哥已经老了~
java.io.* 包
字节流 VS 字符流
- JAVA采用UTF-16,字符可以由多个代码单元组成
- 构造字符流的时候,可以指定编码集合
- 读取图片(字节) VS 读取汉字(字符)
节点流 VS 嵌套流
- 节点流对应数据源,例:
File/Memory Array(byte、char)/Memory String(只有字符)/Piped - 嵌套流可以修改读写数据的格式或者添加额外方法,如压缩加密等,例:
Buffered/Object(将数据持久化,只有字节)/Data(只有字节)/Print(只有输出没有输入哦)/字节-》字符/… - sun.net.TelnetOutputStream/sun.net.TelnetInputStream被有意的隐藏在sun包中。java.net中很多类的方法会返回TelnetOutputStream/TelnetInputStream类型对象,但方法声明只返回OutputStream/InputStream(多态)
InputStream
- abstract int read() 返回读取的字节(用0~255表示),读到流末尾时返回-1。InputStream抽象类有若干个非抽象的方法,它们都需要调用抽象的read方法。所以各个子类都只需要覆盖这个方法就可以了
- int read(byte[] buffer) 将读取的数据放置buffer里面,返回实际读取的字节数,末尾-1
- int read(byte[] buffer, int offset, int length) 从buffer[offset]开始,当length是0时,返回0
- long skip(long n) 跳过n字节不读,返回实际跳过的字节数
- int close() 关闭流释放内存资源
- void mark(int readLimit) 标记当前流的位置,如果输入流中已经读入的字节多于readlimit个,则这个流允许忽略这个标记,不能随心所欲的reset任意远的位置,重置太远会抛IOException。一个流在任何时刻都只能有一个标记,否则会被第二个替代
- void reset() 把流重置到之前标记的位置
- boolean void markSupported() 判断流是否支持标记重置,不支持的话,mark什么都不做,而reset会抛异常
读取一些字节后,其他的数据仍在传输中,尽管它们最终会到达,但此时却不可以,会返回实际读到的字节数。因为程序处理比网络要快,所以经常程序在数据到达前会清空网络缓冲区,此时流还是可以用的,就返回0。
可以使用available方法来确定不阻塞情况下有多少个字节可以读取,它会返回可以读取的最少字节数,事实上会更多。在流的最后,available方法会返回0。
标记位置之后的所有字节会存储在一个内部缓冲区中来实现。目前支持标记的流只有Buffered和ByteArray流。
OutputStream
- abstract void write(int b) 写入一个byte,范围是0~255,因为JAVA没有无符号的byte类型,所以写入一个int会获取最低的那个字节(如果超过255,会抛出IllegalArgumentException或者总是写入255)
- void write(byte[] b)
- void wirte(byte[] b, int offset, int length)
- void flush()
void close() 关闭与这个流相关的所有资源,一旦输出流关闭,继续写入会抛出IOException异常
释放模式(还可以用于socket,通道和JDBC连接):
OutputStream out = null; //作用域
try {
out = new FileOutputStream(path);
} catch( IOException) {
…
} finally {
if(out!=null) //避免NullPointerException
try {
out.close();
} catch(IOException) {
…
}
}JAVA 7支持带资源的try:
try(OutputStream out = new FileOutputStream(path)) {
} catch(IOException) {
…
}
现在不需要finally句子了,JAVA会对try块参数中声明的所有AutoCloseable对象自动调用close(),只要对象实现了Closeable接口,就可以使用带资源的try
输出流指定的文件不存在的时候,会自动创建一个,但不能自动创建目录。文件存在的话会删除重建,除非append为true(会直接将数据添加到文件尾)。输入和输出可能很慢,所以如果程序还有在做其他重要的工作,要尽量将I/O放在单独的线程中
int -> byte
int i=b>=0?b:256-b;
Reader
- abstract int read() 返回一个Unicode码元
- int read(char[] cbuf)
- boolean ready() 与available方法的用途相同,但是只是返回一个布尔值,告诉阅读器目前是否可以无阻塞的读取
Writer
- abstract void write(char c)
- void write(char[] cbuf)
- void write(String string) (String -> Char[] toCharArray)
不同的编码写入不同的字节序列,如果没有指定编码,则使用平台默认编码方式。如果指定了一个未知的编码,则会抛出UnsupportedEncoding异常。
字符+编码-》字节+编码-》 字符
String getEncoding()
File/String/CharArray
buffered/linenumber/pushback/print
缓存流
- mark(int readlimit)
- reset()
- readLine() 读取一行字符串,以换行符分隔
- newLine() 写入一个分隔符
- flush()
read()读取时会先一次性就读满缓冲区(缓存大小是可以指定的,字节默认8192字节,字符默认8192字符),然后下次就可以直接用
将过滤器串链在一起,要注意只对链中最后一个过滤器进行读写,为了避免错误可以采用重赋值,匿名参数链接~
PrintStream && PrintWriter
System.out就是一个printStream流,System.out会缓冲
public PrintStream(OutputStream out)
public PrintStream(OutputStream out, boolean autoflush)
PrintWriter(OutputStream out, boolean autoflush)
PrintWriter(Writer out, boolean autoflush)
PrintWriter拥有以文本格式打印字符串和数字的方法,它甚至还有一个将PrintWriter链接到FileWriter的便捷方法:
PrintWriter out = new PrintWriter(“employee.txt”) 等同于 PrintWriter out = new PrintWriter(new FileWriter(“employee.txt”))
autoflush为true的时候每次写入1字节或者换行或者调用println都会刷新输出流,默认是禁用的
print和println方法会将参数以可预见的方式转换成一个字符串,再用默认的编码方式把字符串写入底层输出流。println会在所写的行末尾追加一个与平台有关的行分隔符。
PrintStream是有害的!
- println结束符与平台相关
- 不能指定编码方式
- 吞掉了所有异常,一旦底层流抛出一个异常,就会设置一个内部错误标志。由程序员调用checkError方法来检查这个标志的值
public boolean checkError()
数据流
- final int read(byte[] input)
- final void readFully(byte[] input) 这个方法会一直读,没有得到足够的数据时,就会抛出IO异常
writeChars将字符变为一个2字节的big endian unicode字符
writeBytes处理String参数时只写入每个字符的低字节,会丢失信息
上面都不会输出流的字符串长度编码,无法区分原始字符和作为字符串一部分的字符
writeUTF包括了字符串长度,它将字符串本身用unicode UTF-8编码的一个变体进行编码,由于这个变体编码方式与大多数非JAVA软件有点不兼容,所以应当只用于与其他使用数据流读取字符串的JAVA程序进行数据交换。
final String readLine() 它读取用行结束符分隔的一行文本,并返回一个字符串。但是这个方法有bug!!第一,因为它大多数情况下不能正确的将非ASCII字符转换为字节。此时可以由BufferedReader的readLine方法来处理,但是他们都并不总能把一个回车识别为行结束:检测到一个回车,会继续等待,查看下一个字符是否为换行,如果是换行,则读出该行文本(抛掉回车和换行),如果非换行符号,则抛掉回车,输出该行文本,该非换行字符会成为下一行的一部分。不过如果回车是流的最后一个字符,那么会一直挂起。(假设很倒霉丢了文件最后一行,程序就会永远挂起)
对象流与序列化
只有实现Serializable接口的对象可以被序列化。写出对象的时候才能用writeObject/readObject方法,对于基本类型值,需要使用诸如writeInt/readInt或writeDouble/readDouble这样的方法。假设两个对象同时引用同一个对象,我们不能只存储内存引用地址,因为当对象重新被加载时,它可能占据了与原来完全不同的内存地址。
在序列化过程中,每个对象都有一个序列号,第一次遇到,就保存该对象的数据到流中,如果再次遇到,就只标记“与序列号X对象相同”。读取对象时,第一次遇到该序列号,就去构建它,再次遇到就去获取相关联的对象引用。
每个序列化文件都有一个两字节的“魔幻数字”AC ED开始,后面紧跟着对象序列化格式的版本号如00 05。存储一个对象时,该对象所属的类也必须存储。这个类的描述包含:
- 类名
- 序列化的版本唯一ID,SHA(当读入一个对象,会比较该对象和类的指纹,不匹配的话说明该类在写出之后发生过变化)
- 描述序列化方法的标志集 java.io.ObjectStreamConstants中定义的3位掩码构成
SC_WRITE_METHOD = 1 类有一个writeObject方法可以写入其他的数据
SC_SERIALIZABLE = 2 类实现了序列化接口
SC_EXTERNALIZABLE = 4 类实现了Externalizable接口
java.util.Date类定义了自己的
readObject/writeObject方法,并且实现序列化接口,标志值为03
- 对数据域的描述
1字节长的类型编码(B,C,D,F,L,Z,[等)
2字节长的域名长度
域名
类名
72 (开始)
用来表示类名长度的两字节
类名
8字节长的指纹
1字节长的标识
2字节长的数据域描述符的数量
数据域描述符
78 (结束)
超类类型 (如果没有就70)
组总是被存储为:
75 类描述符 4字节长的数组项数量 数组项
有些数据域是不可以被序列化的,比如只对本地方法有意义的存储句柄或窗口句柄的整数值,此时,我们就可以使用transient标记来表示某些字段可以被跳过。
可以序列化的类可以定义具有下列前面的方法:
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException
private void writeObject(ObjectOutputStream out) throws IOException
之后,数据域就再也不会被自动序列化,取而代之是调用这些方法。
对于单例和类型安全枚举,要加倍当心序列化:
public class Orientation {
public static final Orientation H = new Orientation(1);
public static final Orientation V = new Orientation(2);
private int value;
private Orientation(int value){
this.value = value;
}
}
Orientation o = Orientation.H;
ObjectOutputStream out = ...;
out.writeObject(o);
out.close();
ObjectInputStream in = ...;
Orientation saved = (Orientation) in.read();
saved == o会返回false,因为saved是一个新的对象。为了解决这个问题,我们需要定义另一个方法readResolve,它必须返回一个对象,而该对象之后会成为readObject的返回值:
protected Object readResolve() throws ObjectStreamException {
if(value == 1) return Orientation.H;
if(value == 2) return Orientation.V;
return null;
}
只要对象可以序列化,就可以直接将对象序列化输出流中(不需要是文件,ByteArrayOutputStream就可以保存字节数组),然后将其读回,这样产生的一个新对象是对现有对象的深拷贝deep copy。
版本控制:
serialver 类名(会输出类的serialVersionUID)
非瞬时和非静态的数据域发生改变时,不兼容
添加数据域时,这些添加的域会被设置为默认值(这样会丢失数据,也带来不安全性,我们需要去readObject去修正并提供额外的代码来处理null数据),兼容
文件
java.nio.file.Path表示一个目录名序列:
Path absolute = Paths.get(“/home”, “cay”);
Path relative = Paths.get(“conf”, “user.property”);
静态的Paths.get方法接受一个或多个字符串,并将它们用默认文件系统路径分隔符连接起来。如果路径不合法,就抛出InvalidPathException异常
Path resolve(Path other)
Path resolve(String other)
如果是绝对路径,那么久返回other,否则就连接this和other返回
Path resolveSibling(Path other)
Path resolveSibling(String other)
如果other是绝对路径,那就返回other。否则就连接this的父路径和other返回
Path getParent() 返回父路径,不存在就返回null
Path getRoot()
Path getFileName() 不存在返回null
Path toAbsolutePath() 返回与该路径等价的绝对路径
File toFile() 从该路径中创建一个File对象
java.io.File:
Path toPath() 从该文件中创建一个Path对象
java.nio.file.Files:
static byte[] readAllBytes(Path path)
static List<String> readAllLines(Path path, Charset)
static Path write(Path path, byte[] contents, OpenOption…options)
将给定内容写到文件中,并返回path
static InputStream newInputStream(Path path, OpenOption…options)
static OutputStream newOutputStream(Path path, OpenOption…options)
static BufferedReader newBufferedReader(Path path, Charset charset)
static BufferedWriter newBufferedWriter(Path path, Charset charset)
打开一个文件,用于读入或写出
static Path copy(Path from, Path to, CopyOption…options)
static Path move(Path from, Path to, CopyOption…options)
如果目标路径已经存在,则复制或移动就会失败,但是可以直接覆盖,使用REPLACE_EXISTING选项。移动=复制+删除,可以使用ATOMIC_MOVE选项来保证失败时源文件继续保持在原来位置
static void delete(Path path) 删除文件或空目录,当文件或目录不存在的时候抛出异常
static boolean deleteIfExists(Path path) 不存在的情况会抛出异常
static Path createFile(Path path, FileAttribute<?>…attrs)
static Path createDirectory(Path path, FileAttribute<?>…attrs) 中间目录必须事先存在
static Path createDiretories(Path path, FileAttribute<?>…attrs)
static boolean exists(Path path)
static boolean isHidden(Path path)
static boolean isReadable(Path path)
static boolean isDirectory(Path path)
static long size(Path path)
String contents = …;
byte[] bytes = contents.getBytes(charset);
try(DirectoryStream<Path> entries = Files.newDirectoryStream(dir)) {
for(Path entry : entries)
process entry
}
迭代目录中的文件,可以使用glob模式来过滤文件,
DirectoryStream<Path> entries = Files.newDirectoryStream(dir, “*.java”)
访问某个目录的所有子孙成员,可以调用walkFileTree方法,向其传递一个Filevisitor类型的对象,这个对象会得到下列通知:
1. 在遇到一个文件或目录时 FileVisitResult visitFile(T path, BasicFileAttributes attrs)
2. 在一个目录被处理前 FileVisitResult preVisitDirectory(T dir, IOException ex)
3. 在一个目录被处理后 FileVisitResult postVisitDirectory(T dir, IOException ex)
4. 在试图访问文件或目录时发生错误 FileVisitResult visitFileFailed(T path, IOException ex)
对于上述每种情况,都可以指定是否希望执行下面的操作:
1. 继续访问下一个文件 FileVisitResult.CONTINUE
2. 继续访问,但是不再访问这个目录下的任何项 FileVisitResult.SKIP_SUBTREE
3. 继续访问,但是不再访问这个文件的兄弟(和该文件在同一个目录下)FileVisitResult.SKIP_SIBLINGS
4. 终止访问 FileVisitResult.TERMINAL
例子:
Files.walkFileTree(dir, new SimpleFileVisitor<Path>(){
public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) throws IOException {
if(attrs.isDirectory())
System.out.println(path);
return FileVisitResult.CONTINUE;
}
public FileVisitResult visitFileFailed(Path path, IOException ecx) throws IOException {
return FileVisitResult.CONTINUE;
}
});
java.nio.attribute.BasicFileAttributes:
long size()
boolean isRegularFile()
FileTime creationTime()
Paths类会在默认文件系统中查找路径,对于ZIP文件系统,我们可以调用:
FileSystem fs = FileSystems.newFileSystem(Paths.get(zipname), null)
Files.copy(fs.getPath(sourceName), targetPath);
FileSystem fs = FileSystems.newFileSystem(Paths.get(zipname), null);
Files.walkFileTree(fs.getPath("/"), new SimpleFileVisitor<Path>)() {
System.out.println(file);
return FileVisitResult.CONTINUE;
}
}
内存映射文件
我们可以通过getChannel直接从某个stream中获得一个访问该流的通道。内存映射文件表示可以将文件或者文件的一部分映射到内存中。
从文件中获得一个channel,使我们可以使用诸如内存映射,文件加锁机制以及文件间快速数据传递等特性。
java.nio.channels.FileChannel:
static FileChannel open(Path path, OpenOption…options);
options: StandardOpenOption枚举中的WRITE, APPEND, TRUNCATE_EXISTING, CREATE值然后通过调用FileChannel类的map方法从这个通道获得一个Buffer,可以自己指定要映射的文件区域和映射模式:
FileChannel.MapMode.READ_ONLY
FileChannel.MapMode.READ_WRITE 缓冲区是可写的,任何修改都会在某个时刻写回到文件中
FileChannel.MapMode.PRIVATE 任何修改对于缓冲区来说都是私有的,不会传播到文件中
MappedByteBuffer map(FileChannel.MapMode mode, long position, long size)通过Buffer来读写数据(get & put),缓冲区支持顺序和随机数据访问:
1.顺序遍历
while(buffer.hasRemaining())
byte b = buffer.get();
2.随机访问
byte b = buffer.get(i);java.nio.Buffer是一个抽象类,它有众多的具体子类,包括ByteBuffer, CharBuffer, DoubleBuffer, IntBuffer等。每个缓存区都有:
*容量
*读写位置
*界限
*可选的标记,用来重复一个读入或写出操作
ByteBuffer:
1. 当写到达容量大小时,可以调用flip()将界限设置到当前位置,并把位置复位到0,现在remaining就会返回界限-位置,这时就可以读啦~
2. buffer.order(ByteOrder.LITTLE_ENDIAN) 设置缓冲区字节顺序
ByteOrder b = buffer.order() 获取缓冲区字节顺序
3. static ByteBuffer allocate(int capacity) 可以构建具有给定容量的缓存区
4. static ByteBuffer wrap(byte[] values)
5. CharBuffer asCharBuffer() 构建的字符缓冲区有自己的位置,界限和标记,但是在它上面的更改会变更在这个字节缓存区上
ByteBuffer buf = ByteBuffer.allocate(64);
channel.read(buf); //从channel中读取数据到buf
channel.position(newPos); //设置channel的当前位置
buffer.flip();
channel.write(buf); //将数据从缓存区读出写入channel
- 多个同时执行的程序需要修改同一个文件,就需要文件锁java.nio.channels.FileLock来解决这个问题,它可以控制对文件或者文件中某个范围的字节的访问:
FileLock lock = channel.lock(); //会阻塞直到获得锁
FileLock lock =channel.tryLock(); //立即返回锁或null
lock.close(); //关闭锁
FileLock tryLock(long start, long size, boolean shared) //锁定文件的一部分
FileLock lock(long start, long size, boolean shared) //锁定文件的一部分
shared标志位为true时,表示可以多个进程同时读取,并不是所有的操作系统都支持共享锁,需要使用FileLock的isShared方法来查看。如果我们锁定了文件的尾部,而这个文件的长度随后超过了锁定的部分,那么增长出来的额外区域是未锁定的,可以使用Long.MAX_VALUE来表示尺寸锁定所有的字节。锁也是需要在操作完成时释放,所以可以使用try-with-resouces结构。关闭一个通道会释放其上面所有的锁,文件锁是整个JAVA虚拟机持有的,如果两个程序是同一个虚拟机启动的,不能每一个程序获得一个在同一个文件上的锁,会抛出OverlappingFileLockException。
接口
Closeable: void close()
InputStream, OutputStream, Reader和Writer都实现了Closeable接口。该接口扩展了java.lang.AutoCloseable接口(Closeable接口只抛出IOException异常,而AutoCloseable的close方法可以抛出任何异常)Flushable: void flush()
OutputStream, Writer都实现了Flushable接口java.lang.Readable:int read(CharBuffer cb)
CharBuffer拥有按顺序和随机地进行读写访问的方法,表示一个内存中的缓冲区或者一个内存映像的文件- java.lang.Appendable:Appendable append(char c), Appendable append(CharSequence s) 返回this,只有Writer实现了它~
java.lang.CharSequence接口描述了一个char值序列的基本属性,String,CharBuffer,StringBuilder和StringBuffer都实现了它
- char charAt(int index) 返回给定索引处的码元
- int length() 序列中码元的数量
- CharSequence subSequence(int start, int end) start~end-1
- String toString()
BLABLA
1.所有在java.io中的类都将相对路径名解释为以用户工作目录开始,可以通过调用System.getProperty(“user.dir”)来获得这个信息
2.我们可以通过常量字符串java.io.File.separator来获得运行平台的文件分隔符
3.PushBackInputStream可以用来预读一个字节,并将其推回流中
PushBackInputStream(InputStream in)
PushBackInputStream(InputStream in, int size)
void unread(int b)
int read()
PushbackInputStream pbin = null;
DataInputStream din = new DataInputStream(
pbin = new PushbackInputStream(
new BufferedInputStream(
new FileInputStream(“employee.dat”);
)
)
)
4.字符集建立了两字节Unicode码元序列与使用本地字符编码方式的字节序列之间的映射,java.nio.Charset类使用的是由IANA字符集注册中心标准化的字符集名字,每个字符集可以拥有许多别名,对大小写不敏感
Charset cset = Charset.forName(“ISO-8859-1”);
Set aliases = cset.aliases();
Map<String, Charset> charsets = Charset.availableCharsets();
String str=”abc”;
ByteBuffer buffer = cset.encode(str);
byte[] bytes = buffer.array();
ByteBuffer bbuffer = ByteBuffer.wrap(bytes, 0, bytes.length);
CharBuffer cbuf = cset.decode(bbuf);
String str = cbuf.toString();
5.java.nio.CharBuffer
char[] array();
char charAt(int index);
String toString();
6.RandomAccessFile类可以在文件中的任何位置查找或者写入数据。磁盘文件都是随机访问的,但是从网络而来的数据流却不是。已有文件被作为RandomAccessFile打开时,这个文件就不能被删除
RandomAccessFile in = new RandomAccessFile(“employee.dat”, “rw”);
r表示读入,w表示写
long getFilePointer() 返回文件指针当前位置
void seek(long pos) 将文件指针设置到距文件开头pos个字节处
long length() 返回文件按照字节度量的长度
7.ZIP文档以压缩格式存储一个或者多个文件。每个ZIP文档都有一个头,包含诸如每个文件名字和所使用的压缩方法等信息。使用java.util.zip.ZipInputStream来读入ZIP文档,可以浏览文档中每个单独的项,getNextEntry可以返回一个描述这个项的ZipEntry类型的对象。read方法碰到当前项的结尾返回-1(而不是碰到ZIP文件的末尾),然后你必须调用closeEntry来读入下一项。
ZipInputStream zin = new ZipInputStream(new FileInputStream(zipname));
ZipEntry entry;
while((entry=zin.getNextEntry())!=null) {
analyze and read entry
zin.closeEntry();
}
zin.close();
Scanner in = new Scanner(zin)
while(in.hasNextLine())
do something with in.nextLine()
FileOutputStream fout = new FileOutputStream(“test.zip”);
ZipOutputStream zout = new ZipOutputStream();
for all files {
ZipEntry ze = new ZipEntry(filename);
zout.putNextEntry(ze);
send data to zout
zout.closeEntry();
}
zout.close;
ZipOutputStream可以通过setMethod(int method)来设置压缩方法,DEFLATED或STORED
Reference:
1. JAVA网络编程
2. JAVA核心技术 2
834

被折叠的 条评论
为什么被折叠?



