知识点1:
1 String str1 = "abc";// 没有创建任何对象 2 String str2 = new String("abc");// 创建了2个对象 3 String str3 = new String("abc");// 创建了1个对象 4 System.out.println(str1 == str2);// 输出:false 5 System.out.println(str1 == str3);// 输出:false 6 System.out.println(str2 == str3);// 输出:false
分析:
- String str1 = "abc";执行此句时,首先还是在String Pool中查找有没有字符串常量”abc”,有则直接将s1作为String Pool中”abc”的一个引用,因此没有创建任何对象;
- String str2 = new String("abc");执行此句时,首先在String Pool(字符常量池)中查找有没有字符常量”abc”,没有则在String Pool中创建”abc”的对象;而当执行new String(“abc”)时则在java的堆中创建一个”abc”对象,而s则是该对象的引用,因此共创建2个对象。
- String str3 = new String("abc");执行此句时,依旧在String Pool中查找有没有字符串常量”abc”,有则不进行再次创建,由于这里用了new关键字,所有便在java堆中又创建了一个”abc”对象(地址与上一句在堆中创建的地址不同),而s2则是这个对象的引用,因此执行此句时只创建了1个对象。
- 我们知道”==”是判断对象的,因此由于str1指向的则是String Pool中的”abc”对象,而str2指向的是java堆中的”abc”对象,所以输出false。 后两句判断同理。
知识点2:
Java编程如何避免内存溢出?
- 尽早释放无用对象的引用(XX = null;)
- 谨慎使用集合数据类型,如数组,树,图,链表等数据结构,这些数据结构对GC来说回收更复杂。
- 避免显式申请数组空间,不得不显式申请时,尽量准确估计其合理值。
- 尽量避免在类的默认构造器中创建、初始化大量的对象,防止在调用其自类的构造器时造成不必要的内存资源浪费
- 尽量避免强制系统做垃圾内存的回收,增长系统做垃圾回收的最终时间
- 尽量做远程方法调用类应用开发时使用瞬间值变量,除非远程调用端需要获取该瞬间值变量的值。
- 尽量在合适的场景下使用对象池技术以提高系统性能
知识点3:
常用数据结构:
Hashtable:
- 继承自:public class Hashtable extends Dictionary implements Map;
- Hashtable 中的方法是同步的,即线程安全的,在多线程并发的环境下,可以直接使用Hashtable;
- Hashtable中,key和value都不允许出现null值;
- 在遍历方式的内部实现上,使用了 Iterator,但由于历史原因,Hashtable还使用了Enumeration的方式;
- 在使用哈希值时,HashTable直接使用对象的hashCode;
- 关于内部实现方式的数组的初始大小和扩容的方式,HashTable中hash数组默认大小是11,增加的方式是 old*2+1。
HashMap:
- 继承自:public class HashMap extends AbstractMap implements Map
- HashMap中的方法在缺省情况下是非同步的,即非线程安全的,在多线程并发的环境下,要使用HashMap的话就要自己增加同步处理;
- 在HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。当get()方法返回null值时,即可以表示 HashMap中没有该键,也可以表示该键所对应的值为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个键, 而且HashMap把Hashtable的contains方法去掉了,改成containsvalue和containsKey。因为contains方法容易让人引起误解,因此应该用containsKey()方法来判断是否存在某个键;
- 在遍历方式的内部实现上,使用了 Iterator;
- 在使用哈希值时,HashMap重新计算hash值;
- 关于内部实现方式的数组的初始大小和扩容的方式,HashMap中hash数组的默认大小是16,而且一定是2的指数。
HashSet:
- HashSet 的绝大部分方法都是通过调用 HashMap 的方法来实现的,封装了一个 HashMap 对象来存储所有的集合元素,所有放入 HashSet 中的集合元素实际上由 HashMap 的 key 来保存,而 HashMap 的 value 则存储了一个 PRESENT,它是一个静态的 Object 对象,因此 HashSet 和 HashMap 两个集合在实现本质上是相同的
?ArrayList:
- 基于动态数组的实现;
- 有频繁的随机访问操作,使用ArrayList;
- ArrayList在内存不够时默认是扩展50% + 1个。
LinkedList:
- 基于链表的实现;
- 有频繁的新增和删除操作,使用LinkedList;
Vector:
- 基于动态数组的实现;
- 支持线程的同步,即线程安全,但实现同步需要很高的花费,因此,访问它比访问ArrayList慢;
- Vector在内存不够时默认扩展1倍;
- Vector提供indexOf(obj, start)方法。
知识点4:
字节流与和字符流:
所有的文件在硬盘或在传输时都是以字节的方式进行的,包括图片等都是按字节的方式存储的,而字符是只有在内存中才会形成,所以在开发中,字节流使用较为广泛;在实际开发中出现的汉字问题实际上都是在字符流和字节流之间转化不统一而造成的。
从字节流转化为字符流 = byte[] 转化为String
从字符流转化为字节流 = String 转化为byte[]
对应关系如下:
Reader InputStream
├BufferedReader BufferedInputStream
│ └LineNumberReader LineNumberReader
├CharArrayReader ByteArrayInputStream
├InputStreamReader (none)
│ └FileReader FileInputStream
├FilterReader FilterInputStream
│ └PushbackReader PushbackInputStream
├PipedReader PipedInputStream
└StringReader StringBufferInputStream
Write OutputStream
├BufferedWriter BufferedOutputStream
├CharArrayWriter ByteArrayOutputStream
├OutputStreamWriter (none)
│ └FileWriter FileOutputStream
├FilterWriter FilterOutputStream
├PrintWriter PrintStream
├PipedWriter PipedOutputStream
└StringWriter (none)
字节流:
- 抽象类 – InputStream和OutputStream;
- 具体实现 — 如FileInputStream和FileOutputStream;
- 输出时直接操作文件,没有使用缓冲区;
- 字节流处理单元为1个字节,操作字节和字节数组;
- 如果是音频文件、图片、歌曲,用字节流比较好;
- 字节流可用于任何类型的对象,包括二进制对象;
- 字节流提供了处理任何类型的IO操作的功能,但它不能直接处理Unicode字符;
字符流:
- 抽象类 Reader和Writer;
- 具体实现 — 如FileReader和FileWriter;
- 在字符流的操作中,所有的字符都是在内存中形成的,在输出前会将所有的内容暂时保存在内存之中,所以使用了缓冲区暂存数据,再从缓存写入文件;
- 字符流处理的单元为2个字节的Unicode字符,分别操作字符、字符数组或字符串,所以字符流是由Java虚拟机将字节转化为2个字节的Unicode字符为单位的字符而成的;
- 如果是关系到中文(文本)或支持多国语言的,用字符流比较好;
- ?字符流只能处理字符或者字符串;
- ?字符流可以直接处理Unicode字符。
例子:使用字节流时,即使不关闭输出流,也不影响输出:
1 public class FileOutputStreamTest { 2 public static void main(String[] args) throws Exception { 3 File filePath = new File("d:" + File.separator + "test.txt"); 4 OutputStream out = new FileOutputStream(filePath); 5 String str = "Hello World!!!"; 6 byte bytes[] = str.getBytes();// 字符串转byte数组 7 out.write(bytes); 8 // 不关闭输出流 9 // out.close(); 10 } 11 }
例子:使用字符流时,不关闭输出流时,有必要进行强制刷新(flush),否则无法正确写入内容:
1 public class FileWriterTest { 2 public static void main(String[] args) throws Exception { 3 File filePath = new File("d:" + File.separator + "test.txt"); 4 Writer out = new FileWriter(filePath); 5 String str = "Hello World!!!"; 6 out.write(str); 7 // 强制性清空缓冲区中的内容 8 // out.flush(); 9 10 // 不关闭输出流 11 // out.close(); 12 } 13 }
知识点5:
基本数据类型 | 大小 | 最小值 | 最大值 |
boolean | —– | —– | —— |
char | 16-bit | Unicode 0 | Unicode 2^16-1 |
byte | 8-bit | -128 | +127 |
short | 16-bit | -2^15 | +2^15-1 |
int | 32-bit | -2^31 | +2^31-1 |
long | 64-bit | -2^63 | +2^63-1 |
float | 32-bit | IEEE754 | IEEE754 |
double | 64-bit | IEEE754 | IEEE754 |
1 public class BasicTypeTest { 2 public static void main(String[] args) { 3 // byte 4 System.out.println("基本类型:byte 二进制位数:" + Byte.SIZE); 5 System.out.println("包装类:java.lang.Byte"); 6 System.out.println("最小值:Byte.MIN_VALUE=" + Byte.MIN_VALUE); 7 System.out.println("最大值:Byte.MAX_VALUE=" + Byte.MAX_VALUE); 8 System.out.println(); 9 10 // short 11 System.out.println("基本类型:short 二进制位数:" + Short.SIZE); 12 System.out.println("包装类:java.lang.Short"); 13 System.out.println("最小值:Short.MIN_VALUE=" + Short.MIN_VALUE); 14 System.out.println("最大值:Short.MAX_VALUE=" + Short.MAX_VALUE); 15 System.out.println(); 16 17 // int 18 System.out.println("基本类型:int 二进制位数:" + Integer.SIZE); 19 System.out.println("包装类:java.lang.Integer"); 20 System.out.println("最小值:Integer.MIN_VALUE=" + Integer.MIN_VALUE); 21 System.out.println("最大值:Integer.MAX_VALUE=" + Integer.MAX_VALUE); 22 System.out.println(); 23 24 // long 25 System.out.println("基本类型:long 二进制位数:" + Long.SIZE); 26 System.out.println("包装类:java.lang.Long"); 27 System.out.println("最小值:Long.MIN_VALUE=" + Long.MIN_VALUE); 28 System.out.println("最大值:Long.MAX_VALUE=" + Long.MAX_VALUE); 29 System.out.println(); 30 31 // float 32 System.out.println("基本类型:float 二进制位数:" + Float.SIZE); 33 System.out.println("包装类:java.lang.Float"); 34 System.out.println("最小值:Float.MIN_VALUE=" + Float.MIN_VALUE); 35 System.out.println("最大值:Float.MAX_VALUE=" + Float.MAX_VALUE); 36 System.out.println(); 37 38 // double 39 System.out.println("基本类型:double 二进制位数:" + Double.SIZE); 40 System.out.println("包装类:java.lang.Double"); 41 System.out.println("最小值:Double.MIN_VALUE=" + Double.MIN_VALUE); 42 System.out.println("最大值:Double.MAX_VALUE=" + Double.MAX_VALUE); 43 System.out.println(); 44 45 // char 46 System.out.println("基本类型:char 二进制位数:" + Character.SIZE); 47 System.out.println("包装类:java.lang.Character"); 48 System.out.println("最小值:Character.MIN_VALUE=" + (int) Character.MIN_VALUE);//以数值形式而不是字符形式输出 49 System.out.println("最大值:Character.MAX_VALUE=" + (int) Character.MAX_VALUE);//以数值形式而不是字符形式输出 50 } 51 }
知识点6:
常见的final类:
String、Math;Integer 、Long、Character 等包装类。
需要注意的是:
- 对于final修饰的基本类型和String类型,编译器会认为它是稳定态(Immutable Status),所以在编译时就直接把值编译到字节码中了,避免了在运行期引用(Run-time Reference),以提高代码的执行效率;
因此,单纯的修改静态变量是没用的,还要重新编译,不然改动不会生效。 - 对于非final修饰的类(即非基本类型),编译器认为它是不稳定态(Mutable Status),在编译时建立的则是引用关系(该类型也叫做Soft Final);
因此,如果引入的常量是一个类或实例,即使不重新编译也能使改动生效。
知识点7:
按特定的时间格式获取其毫秒数:
1 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 2 Date startDate = sdf.parse("2014-07-11 00:00:00"); 3 Date endDate = sdf.parse("2014-07-11 23:59:59"); 4 long startTime = startDate.getTime();//单位:毫秒 5 long endTime = endDate.getTime();//单位:毫秒
知识点8:
Java常见的两种解析XML的方式:
DOM-基于文档树结构的解析
例子:
1 private static void readXmlByDOM(String filePath)throws ParserConfigurationException, SAXException, IOException { 2 File file = new File(filePath); 3 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();// step 4 // 1:获得DOM解析器工厂,作用是创建具体的解析器 5 DocumentBuilder db = dbf.newDocumentBuilder();// step 2:获得具体的dom解析器 6 Document document = db.parse(file);// step 3:解析一个xml文档,获得Document对象(根节点) 7 // 根据标签名字访问节点 8 NodeList list = document.getElementsByTagName("test"); 9 System.out.println("list length: " + list.getLength()); 10 // 遍历每一个节点 11 for (int i = 0; i < list.getLength(); ++i) { 12 System.out.println("----------------------"); 13 Element element = (Element) list.item(i); 14 String test = element.getElementsByTagName("name").item(0) 15 .getNodeValue(); 16 System.out.println(test);// 此处输出:null 17 // 节点getNodeValue的值永远为null 18 // 解决方法:加上getFirstChild() 19 Node node1 = element.getElementsByTagName("name").item(0) 20 .getFirstChild(); 21 String content = node1.getNodeValue(); 22 System.out.println("name: " + content); 23 24 Node node2 = element.getElementsByTagName("sex").item(0) 25 .getFirstChild(); 26 content = node2.getNodeValue(); 27 System.out.println("author: " + content); 28 29 Node node3 = element.getElementsByTagName("age").item(0) 30 .getFirstChild(); 31 content = node3.getNodeValue(); 32 System.out.println("year: " + content); 33 } 34 }
SAX-基于事件流的解析
例子:
1 // 需导入dom4j包 2 private static void readXmlBySAX(String filePath) throws DocumentException { 3 SAXReader reader = new SAXReader(); 4 File file = new File(filePath); 5 Document doc = (Document) reader.read(file); 6 Element root = doc.getRootElement(); 7 Iterator iterator = root.elementIterator("test"); 8 while (iterator.hasNext()) { 9 Element element = (Element) iterator.next(); 10 String name = element.elementText("name"); 11 String sex = element.elementText("sex"); 12 Integer age = Integer.parseInt(element.elementText("age")); 13 System.out.println("----------------------"); 14 System.out.println("name: " + name); 15 System.out.println("sex: " + sex); 16 System.out.println("age: " + age); 17 } 18 }
知识点9:
Java的算术右移">>"和逻辑右移">>>":
- 算术右移">>"
使用最高位填充移位后左侧的空位,不改变原数的符号;
右移的结果为:每移一位,第一个操作数被2除一次,移动的次数由第二个操作数确定。 - 逻辑右移">>>"
或叫无符号右移,只对位进行操作,没有算术含义,用0填充左侧的空位;
无法保证不改变原数的符号。
例子:
1 System.out.println("=============算术右移 >> ==========="); 2 int i = 0xC0000000; 3 System.out.println("移位前:i= " + i + " = " + Integer.toBinaryString(i) + "(B)"); 4 i = i >> 28; 5 System.out.println("移位后:i= " + i + " = " + Integer.toBinaryString(i) + "(B)"); 6 7 System.out.println("---------------------------------"); 8 int j = 0x0C000000; 9 System.out.println("移位前:j= " + j + " = " + Integer.toBinaryString(j) + "(B)"); 10 j = j >> 24; 11 System.out.println("移位后:j= " + j + " = " + Integer.toBinaryString(j) + "(B)"); 12 13 System.out.println("==============逻辑右移 >>> ============="); 14 int m = 0xC0000000; 15 System.out.println("移位前:m= " + m + " = " + Integer.toBinaryString(m) + "(B)"); 16 m = m >>> 28; 17 System.out.println("移位后:m= " + m + " = " + Integer.toBinaryString(m) + "(B)"); 18 19 System.out.println("---------------------------------"); 20 int n = 0x0C000000; 21 System.out.println("移位前:n= " + n + " = " + Integer.toBinaryString(n) + "(B)"); 22 n = n >> 24; 23 System.out.println("移位后:n= " + n + " = " + Integer.toBinaryString(n) + "(B)"); 24 25 System.out.println("\n"); 26 System.out.println("==============移位符号的取模==============="); 27 int a = 0xCC000000; 28 System.out.println("移位前:a= " + a + " = " + Integer.toBinaryString(a) + "(B)"); 29 int a1 = a >> 32; 30 int a2 = a >>> 32; 31 System.out.println("算术右移32位:a1=" + a1 + " = " + Integer.toBinaryString(a1) + "(B)"); 32 System.out.println("逻辑右移32位:a2=" + a2 + " = " + Integer.toBinaryString(a2) + "(B)");
知识点10:
关于Java的eqauls与= =:
= = 是面向过程的操作符;equals是面向对象的操作符
= =不属于任何类,equals则是任何类(在Java中)都实现(或继承)了的一个方法;
因此,
如果要比较两个基本类型的值是否相等,请用= =;//注:基本类型无equals方法可调用
如果要比较两个对象的地址是不是一样,请用= =;
如果要比较两个对象(非基本类型)的值是否相等,请用equals;
知识点11:
特殊字符及其转义:
1.八进制转义序列:
\ + 1到3位5数字;范围‘\000‘~‘\377‘
\0:空字符
2.Unicode转义字符:
\u + 四个十六进制数字;0~65535
\u0000:空字符
3.特殊字符:3个
\":双引号
\‘:单引号
\\:反斜线
4.控制字符:5个
\‘ 单引号字符
\\ 反斜杠字符
\r 回车
\n 换行
\f 走纸换页
\t 横向跳格
\b 退格
点的转义:. ==> u002E
美元符号的转义:$ ==> u0024
乘方符号的转义:^ ==> u005E
左大括号的转义:{ ==> u007B
左方括号的转义:[ ==> u005B
左圆括号的转义:( ==> u0028
竖线的转义:| ==> u007C
右圆括号的转义:) ==> u0029
星号的转义:* ==> u002A
加号的转义:+ ==> u002B
问号的转义:? ==> u003F
反斜杠的转义: ==> u005C
知识点12:
对象锁:
- 所有对象都自动含有单一的锁;
- JVM负责跟踪对象被加锁的次数。如果一个对象被解锁,其计数变为0。在任务(线程)第一次给对象加锁的时候,计数变为1。每当这个相同的任务(线程)在此对象上获得锁时,计数会递增;
- 只有首先获得锁的任务(线程)才能继续获取该对象上的多个锁;
- 一个线程可以多次对同一个对象上锁。对于每一个对象,都有一个独自的加锁计数器交由JVM维护,线程每获得一次该对象,计数器就加1,每释放一次,计数器就减 1,当计数器值为0时,锁就被完全释放了,此时别的任务就可以使用此资源。
类锁:
- 对于同步静态方法/静态变量互斥体,由于一个class不论被实例化多少次,其中的静态方法和静态变量在内存中都只由一份。
所以,一旦一个静态的方法被申明为synchronized,此类所有的实例化对象在调用此方法,共用同一把锁,我们称之为类锁。 - 一旦一个静态变量被作为synchronized block的互斥体。进入此同步区域时,都要先获得此静态变量的对象锁;
- 其实系统中并不存在什么类锁。当虚拟机装载一个class文件的时候,它就会创建一个java.lang.Class类的实例。当锁住一个对象的时候,实际上锁住的是那个类的Class对象。
因此,当一个同步静态方法被调用时,系统获取的其实就是代表该类的类对象的对象锁。
若要同时获取两种锁,同时获取类锁和对象锁是允许的,并不会产生任何问题,
但使用类锁时一定要注意,一旦产生类锁的嵌套获取的话,就会产生死锁,因为每个class在内存中都只能生成一个Class实例对象。
Java琐碎知识点