Java语言基础
容器
这个世界是有序的,将Java对象零散地放到内存中是不符合世界常理的,特别是有一大组相似的甚至不知道有多少数据的时候。把Java对象装进盒子里可以有序收纳,这个盒子就叫容器。
初次了解泛型
泛型,就是泛泛而指的类型,就是不确定具体类型的类型。Java提供的容器,一般都支持泛型,也就是说不管是什么对象,都可以丢到容器中进行收纳。但在Java中,所有类都继承自Object,所以把对象当作Object对象丢到容器里不会有任何问题,但当拿出来这个Object的时候,由于不知道这个对象的真正类型是什么,就可能在向下转型的时候出现错误,为了避免这种情况,一般对容器使用<type>
表示容器的具体类型参数,也就是容器里放什么类就确定了,编译器就会对类型进行检查,避免运行时Object对象向下转型时错误的发生。例如:
List<Apple> apples = new ArrayList<Apple>();
你应当创建一个具体类的对象,将其转型为对应的接口,然后在其余的代码中都使用这个接口。
容器的类型
Collection
一个独立元素的序列,符合某种规则,提供迭代器,可以用foreach进行迭代遍历,也就是提供一个方法,可以每次从序列中从前到后依次拿到每一个元素。
- List
有序的序列,对象元素按照插入顺序保存- ArrayList
连续储存的List,可以使用数字来查找值,随机查找快,但插入和移除元素时慢。 - LinkedList
链表储存的List,只能顺序遍历,但插入和移除元素快,可以实现栈和队列的功能。- Queue 队列,先进先出
- PriorityQueue 优先级队列,先进和优先级高的先出
- ArrayList
- Set
不能保存重复数据,常用来处理归属性问题等。- HashSet
使用Hash散列的Set,查找元素快 - TreeSet
使用红黑树实现了排序的Set,按照排序结果升序保存对象 - LinkedHashSet
按照添加顺序保存对象的Set
- HashSet
- Queue
队列,只能从队头删除元素从队尾插入元素,元素按照插入顺序先进先出。
Map
成对的“键-值对”对象,可以用键来查找值。
- 映射表/关联数组/字典
可以使用对象来查找对象,键不能存在重复。
在Map中,可以使用get()
方法通过key获取value的值;还可以使用方法检查一个k或者是v是否在map中;使用方法获取k或v的set。
容器的方法
具体方法不再赘述,用多了自然就会了,编程是一个纸上得来终觉浅的技术,真正的写代码才能熟练掌握。
但是容器的方法常见的有
add()
remove()
indexOf()
subList()
retainAll()
removeAll()
等,也就是增删改查方法......
容器内对象的比较使用的是equals()
。
迭代器
迭代器是一个可以每次从容器中顺序抽取出一个对象进行遍历的工具,迭代器掩盖了遍历的底层实现,让遍历的操作与低层的实现相分离,遍历者只需要知道“从这个迭代器里可以不重复地从前到后拿到容器中的每一个元素”就好,而不用关心是从什么容器,和从容器中具体怎么拿的。
迭代器的使用
- 首先通过
Iterator()
获取一个迭代器对象 - 然后使用
next()
方法获取下一个元素 hasNext()
可以检查序列中是否还有元素
迭代器可以搭配循环使用,还可以直接使用foreach循环。容器基于Collection,而Collection是继承Iterable接口的,是与iterator()方法绑定到一起的(参见Java文档)。
for(Obj o : ListA){
}
while(Obj.next()){
}
可以通过增加或者覆盖默认迭代器的方法,返回一个迭代器对象,实现自己的迭代器效果。
ListIterator
仅可以在List中使用的双向的迭代器
Java的IO
File类
Java中的目录类,可以表示文件也可以表示文件夹。
File类除了可以表示已经存在的文件和目录,还可以创建新的文件和目录,还可以对文件的特性进行查看。
FileObj.list()
方法可以列出文件,也可以传入一个实现了FilenameFilter接口的过滤器,进行文件名过滤。
输入输出流
在Java中,一切输入和输出都是流式的对象,Java中常见的是使用装饰器对流进行修饰以达到更丰富的效果。其根基为Inputstream和OutputStream。
具体InputStream和Outputstream的类型可以由文档得知。Java 文档
InputStream类和OutputStream类都实现了对ByteArray,File,Pipe(多线程相关)对象控制和Filter修饰器的功能,InputStream类还实现了对StringBuffer和Sequence对象控制的功能,也就是字符串对象输入和多个Input对象拼合的Sequence输入。
Java的灵活性也带来了复杂性,在核心的I/O类型上加上所有的修饰器才能获得希望的单个I/O对象。
FilterInputStream
该的实现类可以进行两类操作,一种是DataInputStream,可以对基本数据类型和String对象读写,与DataOutputStream功能相反。而其他的BufferedInputStream/LineNumberInputStream/PushbackInputStream则对InputStream的行为进行了修改。
FilterOutputStream
该类包括与DataInputStream相反的DataOutputStream,用于格式化输出的PrintStream和用于缓冲的BufferedOutputStream。
Reader和Writer
Reader和Writer是为了兼容Unicode与面向字符的I/O功能。在In/OutputStream和Reader/Writer之间,可以使用适配器类进行转换。
我个人理解,在Java中,Reader/Writer和Stream更像是上下层的关系,而更新更“高级”的Reader和Writer更好用。
RandomAccessFile
该类可以适用于大小已知的记录组成的文件,所以可以使用seek()方法对文件进行随机访问,使用getFilePointer()获取文件指针当前访问位置,length()方法获取文件大小。
标准I/O
Java提供了System.in/System.out和System.err三种标准流
out和err流是封装好的PrintStream对象,in是原始的InputString对象。
使用setIn();setOut();setErr()
方法可以对流进行重定向。
进程控制
使用ProcessBuilder可以在Java程序内部,像命令行一样调用操作系统内的其他程序。
使用getInputStream()可以获取到程序的流(此时输入和输出是相对的)。
新I/O
不必显式第使用新的I/O系统,I/O的低层已经被重新实现过。其低层是ByteBuffer与系统进行交互。
通过FileInputStream、FileOutputStream和RandomAccessFile可以用来产生FileChannel。通过getChannel()方法,可以产生一个FileChannel,向其传送用于读/写的ByteBuffer,可以锁定文件的某些区域用于独占式的访问。
transferTO()和transferFrom()可以将两个通道直接相连。
ByteBuffer的使用
通过allocate()方法可以分配ByteBuffer,然后使用wrap()等方法填充数据。ByteBuffer的大小与程序操控文件性能有关。还可以使用allocateDirect()方法产生一个与操作系统更耦合的“直接”缓冲器,这种缓冲器速度更快但是分配开支更大。
在Java中,缓冲器容纳的是字节,而输出时需要的是字符,需要对其进行encode()/decode()
转换才能正常工作。
在使用ByteBuffer时,可以建立更高层于Byte的视图,使其可以针对基本数据类型进行处理。具体使用方法是使用asCharBuffer()、asShortBuffer()等方法获取视图,然后使用put()方法向其中放置数据。
在ByteBuffer中,Java使用“Big endian”,也就是高位优先,大端序,从前到后按照书写顺序记录。
内存映射文件
通过Channel的map方法,可以获取MappedByteBuffer,将文件映射到内存中(还可以映射文件的一部分),以更高效的方式操作文件。在映射建立的过程中花费很大,但是但是整体执行速度比流式更快。
在使用多个进程/线程同时访问同一文件的时候,因为涉及文件的同步问题,所以需要使用文件的锁。Java的锁是直接映射到操作系统的锁上的。
tryLock()是非阻塞的尝试获取锁,Lock()是阻塞的获取锁。release()方法可以释放锁。
通过方法参数,可以对文件的全部或者一部分加锁。如果全部加锁那么文件改变时依旧有锁,如果部分加锁那么锁区域外的文件不会被锁定。
压缩
基于InflaterInputStream和DeflaterOutputStream,派生出许多压缩/解压缩类,其中最常见的是Zip和GZIP两大类,如ZipOutputStream和GZIPOutputStream等。
GZIP对于单个数据流进行压缩较为方便,Zip由于java.util.zip类库的支持,功能更为强大。其使用方法依旧为将输入流进行包装。
Zip的使用基于ZipEntry类,它是zip压缩包内的元素的类。通过putNextEntry()方法将ZipEntry对象放入ZipOutputStream中,即可将其对应的文件加入到zip文件中。
在Java中内置了基于zip的jar打包程序,可以使用命令行对其进行调用。
序列化
序列化,就是将程序的对象保存为一个字节序列,并且由这个序列可以还原回程序的对象。序列化可以实现对象的储存,也方便了程序的远程对象传输和调用。序列化时会形成一个对象网,将所有相关对象一起打包。
当一个类实现了Serializable接口时,这个类的对象就可以被方便地进行序列化和反序列化。
使用Externalizable()接口,该接口继承了Serializable接口,并增添了writeExternal()和readExternal()方法,在进行序列化和反序列化时可以执行一些额外操作,而且使用该接口会在序列化和反序列化时调用默认构造器。
还可以在继承自Serializable的类中添加特定参数的writeObject()和readObject()方法,在进行序列化和还原时,程序会自动调用这两个方法。在这两个方法中,可以使用defaultReadObject()/defaultWriteObject()来执行默认的Read和WriteObject。
在对static对象进行序列化时,必须自己手动显示
当序列化对象时,首先创建OutputStream对象,然后将其封装到ObjectOutputStream对象内,调用writeObject()就能将对象序列化并输出。反序列化时readObject()然后向下转型即可。
虽然序列化可以保存“对象网”,但是此对象网具有局限性,如果想序列化软件中某个特定时间的对象,应将其作为“原子”操作,否则可能回带来一些问题。
transient(瞬时)关键字
当不希望对象的某一部分被序列化时,可以使用transient对其修饰,那么Java在对该对象进行序列化时,就会跳过这一部分。
序列化格式
现在常见的序列化格式有XML和json两种,可以使用XOM类库创建xml格式的对象序列化文件。
XOM类库使用Element类构建XML节点,然后使用Document类构建XML文档结构,使用Document的toXML()方法可以快速创建XML文件,使用Serializer类可以创建易读的人类友好的XML文档。
Preferences
Java中可以使用Preferences自动储存和读取一些信息,但是只能用于小的有限长度的对象,常见于储存和读取用户偏好以及设置程序配置项。Preferences使用键值对和节点层次的方式储存对象。
Preferences根据操作系统不同储存在不同的位置,开发者不需关心具体储存方式。
使用Preferences.userNodeForPackage()方法获得Preferences对象,然后使用get()和put()方法获取/更改储存的数据。