Thinking In Java
写这篇文章的主要目的是记录java编程思想这本书的重点内容,一来可以分享给大家一起讨论学习,二来总结知识提升自己。
第一章:对象导论
-
java面向对象的理解
将组成问题的各个元素都看着一个一个的简单对象,然后真对这些简单的对象封装成员,当遇到一个新的类似问题时,就可以通过这些简单对象来描述这个新的问题。这样就可以提高解决问题的效率,减少重复代码的开发。 -
java中对象之间的传递是他们的地址信息,通过地址信息找到对应的对象,才能获取对象的详细信息。
-
单根继承结构
java中所有的类都继承统一的Object类。
单根继承结构的好处:
1)在单根继承结构中所有对象都具有一个共用接口,所以它们归根到底都是相同的基本类型。
2)单根继承结构保证所有对象都具备某些功能。
3)单根继承结构使垃圾回收器的实现变得容易得多,而垃圾回收器正是相对C++的重要改进之一。由于所有对象都保证具有其类型信息,因此不会因无法确定对象的类型而陷入僵尸。这对于系统级操作(如异常处理)显得尤其重要,并且给编程带来了更大的灵活性。 -
对象的创建和生命周期
1)对象数据的存放位置:在被称为堆(heap)
的内存池中动态地创建对象。在这种方式中,直到运行时才知道需要多少对象,它们的生命周期如何,以及它们的具体类型是什么。
2)对象的生命周期:从对象被创建开始,到对象没有被任何地方引用之后被垃圾回收器回收的整个期间。
第二章:一切都是对象
-
特例:基本类型
基本类型不是通过引用的变量,而是直接存在存储在堆栈中,更加高效。 -
数组对象
首先数组对象在使用之前需要被初始化。访问数组的元素的,不能超过其范围。
1)数组对象,实际上就是一个引用数组,它的每个元素的初始值都是null。
2)基本类型的数组,在编译期间,所有的元素就会被初始化为0。
3)在访问数组的元素时,如果超过范围就会报java.lang.ArrayIndexOutOfBoundsException
异常。 -
java对象的作用域
{ String s = new String("java 字符串"); }
引用变量s在作用域之外就消失了,但是创建的对象依然还在内存中存着,需要等着垃圾回收器去回收内存。
-
static
关键字
1)static修饰的变量和对象无关,只存i一份到方法区,所有该类的实例共享它。StaticClass{ static int s = 1; public static void main(String[] args){ StaticClass s1 = new StaticClass(); System.out.println(s1.s); //1 s1.s = s1.s + 1; StaticClass s2 = new StaticClass(); System.out.println(s2.s); //2 //s1.s和s2.s指向同一个地址空间,共享同个变量s } }
2)static修饰的方法,不需要创建对象就可以调用它,通过className.function()形式调用。
第三章:操作符
-
算术操作符
加(+)、减(-)、乘(*)、除(/)、其余(%) -
关系操作符
小于(<)、小于或等于(<=)、大于(>)、大于或等于(>=)、等于(==)、不等于(!=)
其中等于和不等于适用于所有的基本类型,而其他比较符不适用于boolean类型。== 和 != 比较对象时,比的是对象的引用; equals()方法,用于比较对象的内容,但它默认比较的也是对象的引用。
-
逻辑操作符
与(&&)、或(||)、非(!){ //短路:当表达式A可以确认最终的返回结果时,表达式B就不会计算了 表达式A && 表达式B }
未完待续。。。。。。
第十七章:容器的深入研究
-
完整容器的分类方法
-
List
List的元素有序
,可重复
;
ArrayList
:基于动态数组的数据结构
,对于随机访问
(get 和 set),其性能比较好
,但对于新增和删除
操作(add、remove),其性能较差
些(因为要移动数据);
LinkedList
:基于链表的数据结构,其随机访问
能力较弱些(因为要移动指针),但其新增和删除能力较强
。 -
Set
·Set
中的元素必须定义equals()
方法,保证唯一性
。
· 对于hashCode()
方法,只有这个类被加入到hashSet或LinkedHashSet
中时才需要定义它,一般覆盖了equals()方法时也相应的覆盖hashCode()方法。
· 如果是放到排序容器
时,比如SortedSet
(TreeSet
是它的的唯一实现),元素就需要实现Comparable接口
,通过比较函数对元素进行排序。
·LinkedHashSet
按插入顺序
保存元素。 -
队列(Queue)
· Java SE5中的Queue仅仅只有LinkedQueue
和PriorityQueue
两个实现,他们的差异在于排序的方式而不是性能。
· LinkedQueue根据插入的顺序进行排序,而优先级队列PriorityQueue的排序是有Comparable
决定的。 -
Map
· Map,称为映射表
,也叫关联数组
。键和值都是对象。
·HashMap
之所以有很好的查询性能是因为使用了散列码
来替代对键对象的缓慢搜索。
·散列码
,是根据对象的某些信息算出来的一个int
值,用来代替对象。散列码是“相对唯一”
的。
· Map中对键的要求(和Set中的元素要求一样):首先必须具有一个equals()
方法,保证唯一;其次若键被用于散列时,要有适当的hashCode()
方法;最后就是对于TreeMap
,必须实现Comparable
来保证排序。 -
HashMap查询过程(查询快速的原因)
HashMap中查询一个key的过程就是:
· 首先计算散列码
。
· 然后使用散列码查询数组
(散列码作变数组下标)
· 如果没有冲突
,即生成这个散列码的对象只有一个
,则散列码对应的数组下标的位置就是这个要查找的元素。
· 如果有冲突
,则散列码对应的下标所在数组元素保存的是一个list
,然后对list中的值使用equals()
方法进行线性查询。
因此,HashMap是通过散列码快速的跳转到某个位置,只对很少的对象元素进行线性比较。这就是HashMap快的原因
。(JDK1.8之前使用的是这种方式提高性能,但从JDK1.8开始使用红黑树
。) -
HashMap的性能因子
·容量(Capacity)
:表中的桶位数(The number of buckets in the table)。
·初始容量(Initial capacity)
:表在创建时所拥有的桶位数。HashMap和HashSet都具有允许你指定初始容量
的构造器。
·尺寸(Size)
:表中当前存储的项数。
·负载因子(Loadfactor)
:尺寸/容量。负载轻的表产生冲突的可能性小,因此对于插入和查找都是最理想的(但是会减慢使用迭代器进行遍历的过程)。负载太大的话,散列码就容易冲突,于是每个位置下的list的元素就多了,查询比较时就会比较list,导致性能下降。HashMap和HashSet都具有允许你指定负载因子
的构造器,表示当负载情况达到该负载的水平时,容器将自动增加其容量(桶位数)
,实现方式是使容量大致加倍
,并重新将现有对象分布到新的桶位集中(这被称为再散列
)。 -
Map的遍历方式
未完待续。。。。。。
第十八章:Java I/O系统
-
File类
·File
类不仅可以代表一个文件名
,还可以代表一个目录下面的文件集
,可以使用list()
方法获取文件集,该方法返回文件数组。
·File
不仅仅代表已经存在的文件或者目录
,还可以用File对象来创建新的目录或者尚不存在的整个目录
。
·File类还可以查看文件的特性
(如,大小,最后修改的时间,读/写),检查某个File对象代表的是一个文件还是一个目录,并且可以删除文件。{ File f = new File("文件路径"); f.length(); //获取文件的大小 f.lastModified(); //最后一次修改时间 f.canRead(); //文件是否可读 f.canWrite(); //文件是否可写 f.getAbsolutePath(); //获取文件的绝对路径 f.getName(); //获取文件名 f.getPath(); //返回定义时的路径 File f2 = new File("文件2"); f.renameTo(f2); //给文件重新命名 f.delete(); //删除文件 }
-
输入/输出
·流
,代表任何有能力产生出数据的数据源对象或者有能力接收数据的接收端对象,“流”屏蔽了实际的I/O设备中处理数据的细节。
· 任何从InputStream
或者Reader
派生的类中都包含read()
方法,用于读取单个字节或者字节数组;任何从OutputStream
或者Writer
派生的类中都包含write()
方法,用于写单个字节或者字节数组。
· 实际中我们很少使用单一的类来创建流对象,而是叠合多个对象来提供所期望的功能(使用装饰器)
。 -
InputStream类型
InputStream作用是用来表示那些从不同数据源产生输入的类(数据应该从哪获取)。
数据源有:
·字节数组
,ByteArrayInputStream
(允许将内存的缓冲区当作InputStream使用),缓冲区取出字节作为数据源和FilterInputStream对象相连以提供有用接口。
·String对象
,StringBufferInputStream
(将String转换成InputStream),底层使用的是StringBuffer作为数据源和FilterInputStream对象相连以提供有用接口。
·文件
,FileInputStream
(从文件中读取信息),字符串表示文件名、文件或FileDescriptor对象作为数据源和FilterInputStream对象相连以提供有用接口。
·管道
(工作方式,从一端输入,另一端输出),PipedInputStream,和FilterInputStream对象相连以提供有用接口。
·多个其他种类流的序列收集合并到一个流中
,SequenceInputStream
(将多个InputStream对象转换成一个InputStream)。 -
OutputStream类型
该类别的类决定了输出所要去往的目标(数据应该写到何处)。
·字节数组
,ByteArrayOutputStream
(将流中的数据写到缓冲区),和FilterOutputStream对象相连以提供有用接口。
·文件
,FileOutputStream
(将信息写到文件中),和FilterOutputStream对象相连以提供有用接口。
·管道
,PipedOutputStream
,和FilterOutputStream对象相连以提供有用接口。管道流实现多个线程之间的通信,传递信息。
-
FilterInputStream类型
FilterInputStream
,抽象类,作为“装饰器”的接口。“装饰器”
为其他的InputStream类提供有用功能。与其对应的为FilterOutputStream。
·DataInputStream
,用来从流中读取各种基本类型的数据。
·BufferedInputStream
,先将数据读到缓冲区,避免每次读取数据后都实际的执行写操作。
·LineNumberInputStream
,用来跟踪输入流中的行号,可调用getLineNumber()
和setLineNumber(int)
。
·PushbackInputStream
,可以将读到的最后一个字符回退。 -
FileOutputStream类型
·DataOutputStream
,将各种数据类型输出到“流”中,这样任何机器上的任何DataInputStream都能读取他们。
·BufferedOutputStream
,对数据流使用缓冲技术,减少实际的物理写操作,只是先把数据写到缓冲中。 -
Reader和Writer
·InputStream
和OutputStream
是面向字节
的I/O,Reader
和Writer
则是兼容Unicode
(实现国际化)与面向字符
的I/O。
· 有时候我们需要把来自于“字节”层次结构中的类和“字符”层次结构中的类结合起来使用。为了达到这个目的,我们要用到“适配器
”类:InputStreamReader可以把InputStream转换成Reader,而OutputStreamWriter可以把OutputStream转换成Writer。
· 常用的数据来源和去处类:FileReader/FileWriter,StringReader/StringWriter,CharArrayReader/CharArrayWriter,PipedReader/PipedWriter。 -
FilterReader和FilterWriter
·装饰器,用来更改流的行为
,提供有用功能。
· BufferedReader/BufferedWriter不是FilterReader/FilterWriter的子类。 -
RandomAccessFile
·RandomAccessFile
提供了更加灵活操作文件的方法,是一个完全独立
的类。
· RandomAccessFile不是InputStream和OutputStream继承层次结构中的一部分。
· 除了实现了DataInput和DataOutput(DataInputStream和DataOutputStream也实现了这两个接口)接口外,他和这两个继承层次结构没有任何联系。 -
I/O流的典型使用方式
·缓冲输入文件
public class BufferedInputFile { public static String read(String fileName) throws IOException { //可能会有FileNotFoundException文件找不到的异常 FileReader fileReader = new FileReader(fileName); //通过构造器BufferedReader实现按行快速读取操作 BufferedReader in = new BufferedReader(fileReader); String s; StringBuilder sb = new StringBuilder(); //可能会有IOException异常 while((s = in.readLine()) != null){ sb.append(s + "\n"); } //关闭流 in.close(); return sb.toString(); } public static void main(String[] args) throws IOException{ System.out.println(read("文件")); } }
·
从内存输入
public class MemoryInput { public static void main(String[] args) throws IOException{ //BufferedInputFile首先将数据读入到缓冲的内内存 //然后通过StringReader从内存中读数据 StringReader in = new StringReader(BufferedInputFile.read("文件")); int c; while((c=in.read()) != -1){ System.out.println((char)c); } } }
·
格式化的内存输入
public class FormattedMemoryInput { public static void main(String[] args) throws IOException { try { DataInputStream in = new DataInputStream(new ByteArrayInputStream(BufferedInputFile.read("文件").getBytes())); //in.available() //在没有阻塞的情况下所能读取的字节数,对于文件意味着读整个文件 //对于不同类型的流,可能不是这样,因此要谨慎使用。 while(true){ //readByte()一次一个字节的读取 System.out.println((char)in.readByte()); } }catch (EOFException e){ System.out.println("End Of Stream"); } } }
·
文件输出
public class FileOut { public static void main(String[] args) throws IOException { //定义数据源文件名 String fileIn = "BufferedInputFile.java"; //与数据源文件建立FileReader连接 BufferedReader in = new BufferedReader(new FileReader(fileIn)); //定义目标文件名 String fileOut = "BufferedOutputFile.java"; //与目标文件建立FileWriter连接 BufferedWriter out = new BufferedWriter(new FileWriter(fileOut)); //从源文件读取数据到缓冲区 String s; StringBuilder sb = new StringBuilder(); while ((s = in.readLine()) != null){ sb.append(s + "\n"); } //将缓冲区的数据写到目标文件 out.write(sb.toString()); //关闭流 in.close(); out.close(); } }
未完待续。。。。。。