• java-基础


    一、语法

    1.volatile,

    这个果然是个大问题,到处搜搜再理解理解吧,jdk早期版本就不考虑了,针对jdk1.6

    理解下来有两点:

    • 线程加载volatile变量时始终从主存加载
    • 保证编译顺序,引用“在JDK1.5及其后续版本中,扩充了volatile语义,系统将不允许对 写入一个volatile变量的操作与其之前的任何读写操作 重新排序,也不允许将 读取一个volatile变量的操作与其之后的任何读写操作 重新排序”

    查这个时看到通常的双重检查单例模式在java中无效,很是震惊,太深了,有兴趣的再研究。

    另外注意的是如果修饰对象或数组,则作用的是引用的对象的地址,不是作用的变量的值。

    2.final,变量一经初始化就不能指向其它对象。指向的存储地址不可修改,但指向的对象本身是可以修改的。 

    3.保留小数

    double f = 111231.5585;
    public static void m1() {
                BigDecimal bg = new BigDecimal(f);
                double f1 = bg.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
                System.out.println(f1); //111231.56
    }
            
    public static void m2() {
                DecimalFormat df = new DecimalFormat("#.00");//强制2位(绝对值>=1,"#.00";否则,"0.00")
                System.out.println(df.format(f)); //111231.56
    }
           
    public static void m3() {
                System.out.println(String.format("%.2f", f));//强制2位 111231.56
    }
    
    public static void m4() {
                NumberFormat nf = NumberFormat.getNumberInstance();
                nf.setMaximumFractionDigits(2);
                System.out.println(nf.format(f)); //111,231.56
    }

    4.内部类

    • 成员内部类,作为外部类的成员,可以直接使用外部类的所有成员和方法,即使是private的。同时外部类要访问内部类的所有成员变量/方法,则需要通过内部类的对象来获取。
    要注意的是,成员内部类不能含有static的变量和方法。因为成员内部类需要先创建了外部类,才能创建它自己的
    在成员内部类要引用外部类对象时,使用outer.this来表示外部类对象;
    而需要创建内部类对象,可以使用outer.inner  obj = outerobj.new inner();
     1 public class Outer { 
     2     public static void main(String[] args) { 
     3         Outer outer = new Outer(); 
     4         Outer.Inner inner = outer.new Inner(); 
     5         inner.print("Outer.new"); 
     6  
     7         inner = outer.getInner(); 
     8         inner.print("Outer.get"); 
     9     } 
    10  
    11     public Inner getInner() { 
    12         return new Inner(); 
    13     } 
    14  
    15     public class Inner { 
    16         public void print(String str) { 
    17             System.out.println(str); 
    18         } 
    19     } 
    20 } 
    • 局部内部类,是指内部类定义在方法和作用域内。
    定义在方法内:
     1 public class Parcel4 { 
     2     public Destination destination(String s) { 
     3         class PDestination implements Destination { 
     4             private String label; 
     5  
     6             private PDestination(String whereTo) { 
     7                 label = whereTo; 
     8             } 
     9  
    10             public String readLabel() { 
    11                 return label; 
    12             } 
    13         } 
    14         return new PDestination(s); 
    15     } 
    16  
    17     public static void main(String[] args) { 
    18         Parcel4 p = new Parcel4(); 
    19         Destination d = p.destination("Tasmania"); 
    20     } 
    21 } 

    定义在作用域里:

     1 public class Parcel5 { 
     2     private void internalTracking(boolean b) { 
     3         if (b) { 
     4             class TrackingSlip { 
     5                 private String id; 
     6                 TrackingSlip(String s) { 
     7                     id = s; 
     8                 } 
     9                 String getSlip() { 
    10                     return id; 
    11                 } 
    12             } 
    13             TrackingSlip ts = new TrackingSlip("slip"); 
    14             String s = ts.getSlip(); 
    15         } 
    16     } 
    17  
    18     public void track() { 
    19         internalTracking(true); 
    20     } 
    21  
    22     public static void main(String[] args) { 
    23         Parcel5 p = new Parcel5(); 
    24         p.track(); 
    25     } 
    26 } 
    • 嵌套内部类,就是修饰为static的内部类。声明为static的内部类,不需要内部类对象和外部类对象之间的联系,就是说我们可以直接引用outer.inner,即不需要创建外部类,也不需要创建内部类。
          嵌套类和普通的内部类还有一个区别:普通内部类不能有static数据和static属性,也不能包含嵌套类,但嵌套类可以。嵌套类一般声明为public,方便调用
    • 匿名内部类,有时候我为了免去给内部类命名,便倾向于使用匿名内部类,因为它没有名字。 

    5.泛型

    泛型类的定义:public class Test<T>{}

    泛型方法: 

    6.集合

    6.1 List

    • ArrayList,采用数组方式存储数据,索引数据快插入数据慢
    • Vector,采用数组方式存储数据,索引数据快插入数据慢,使用了synchronized方法,性能上比ArrayList要差
    • LinkedList,使用双向链表实现存储,索引数据慢,插入数据快

    6.2 Set

    不允许包含相同的元素,根据hashCode和equals判断相等

    • HashSet,基于HashMap,value是个空Object对象
    public class HashSet<E>
        extends AbstractSet<E>
        implements Set<E>, Cloneable, java.io.Serializable
    {
        private transient HashMap<E,Object> map;
        ...
    }

    有以下特点
    (1).不能保证元素的排列顺序,顺序有可能发生变化
    (2).不是同步的
    (3).集合元素可以是null,但只能放入一个null

    • LinkedHashSet,继承自HashSet,使用LinkedHashMap来保存所有元素,维护元素的次序。这样使得元素看起 来像是以插入顺序保存的,也就是说,当遍历该集合时候,LinkedHashSet将会以元素的添加顺序访问集合的元素。

    LinkedHashSet在迭代访问Set中的全部元素时,性能比HashSet好,但是插入时性能稍微逊色于HashSet

    • TreeSet,基于TreeMap

    6.3 Map

    • HashMap,最多只允许一条记录的键为Null,允许多条记录的值为 Null,存入的键值对在取出的时候是不确定的
    • LinkedHashMap,继承自HashMap,取出顺序和插入顺序是一样的
    • TreeMap,取出来的是排序后的键值对,根据comparator比较器或key的comparable接口
    • Hashtable,不允许记录的键或者值为null

    ps:jdk1.8下,HashMap的数据结构有了很大变化:数组+链表+红黑树,当链表长度到达8时会对数组进行调整,如果数组长度<64,那么进行扩容,否则将链表调整为红黑树

    7.枚举

    • 基本使用:
    public enum ColorEnum {
      red, green, yellow, blue;
    }
    • 使用自定义字符串表示并保留序号:
    public enum ColorEnum {
        red {public String getName(){return "红色";}}, 
        green{public String getName(){return "绿色";}}, 
        yellow{public String getName(){return "黄色";}}, 
        blue{public String getName(){return "蓝色";}};
        public abstract String getName();
    }
    • 使用自定义字符串,不保留序号:
    public enum Gender{
        MAN("MAN"), WOMEN("WOMEN");
    
        private final String value;
    
        //构造器默认也只能是private, 从而保证构造函数只能在内部使用
        Gender(String value) {
            this.value = value;
        }
    
        public String getValue() {
            return value;
        }
        // 覆盖方法 
        public String toString() {
            return String.valueOf (this.value);
        }
    }
        
    • 使用自定义字符串和序号(或字符串):
    public enum MsgStatus{
        UNSEND(0){
            public String getText(){
              return "未发送";
          }
        },
        NEW(1){
          public String getText(){
             return "已发送新增报";
          }
        };
    
        private int v;
        private MsgStatus(int _v) {
            this.v = _v;
        }
        public abstract String getText();
            
        public int getValue(){
            return this.v;
        }
    }          

    8.访问修饰符类型(public,protected,private,friendly)

    • public的类、类属变量及方法,包内及包外的任何类均可以访问;
    • protected的类、类属变量及方法,包内的任何类,及包外的那些继承了此类的子类才能访问。
    • private的类、类属变量及方法,包内包外的任何类均不能访问;
    • 如果一个类、类属变量及方法不以这三种修饰符来修饰,它就是friendly类型的,那么包内的任何类都可以访问它,而包外的任何类都不能访问它(包括包外继承了此类的子类),因此,这种类、类属变量及方法对包内的其他类是友好的,开放的,而对包外的其他类是关闭的

    9.获取系统属性

    1 System.getProperty("os.name"); //操作系统名称
    2 Charset.defaultCharset().name();  //jvm默认字符集
    3 System.getProperty("line.separator"); //行分隔符(windows是"
    ",linux是"
    ")
    4 System.getProperty("file.separator"); //文件目录分隔符(windows是"", linux是"/") 
    5 System.getProperty("path.separator"); //路径分隔符(windows";",linux是":") 
    6 System.getProperty("file.encoding"); //影响文件内容
    7 System.setProperty("sun.jnu.encoding"); //影响文件名的创建

    10.日期格式化

    通常用SimpleDateFormat,我就经常使用new SimpleDateFormat("MM/dd/yy hh:mm a"),没想到有次竟然栽了个大跟头,SimpleDateFormat还有个参数Locale,没太在意,有次使用ftp4j 的FTPClient.list("/pre")才发现这个问题,估计ftp服务器返回"03/05/13 12:00 AM"这样的格式,而ftp4j使用new SimpleDateFormat("MM/dd/yy hh:mm a")来解析,结果出现FTPListParseException异常,修改本机地区语言中文改为:

    就好了,或者改为使用new SimpleDateFormat("MM/dd/yy hh:mm a",Locale.ENGLISH)也可以,通过实验如果是"03/05/13 12:00 AM",要使用Locale.ENGLISH,如果是"03/05/13 12:00 上午",要使用Locale.CHINESE

    11. Integer和int比较

    Integer int1=new Integer(100);
    Integer int2=new Integer(100);
    System.out.println(int1==int2);//false,new出的Integer比较内存地址
    
    Integer int1=Integer.valueOf(100);
    Integer int2=Integer.valueOf(100);//等同Integer int2=Integer.valueOf(100);
    System.out.println(int1==int2);//true,默认下valueOf的[-128,127]被缓存,因此内存地址相同
    
    Integer int1=new Integer(100);
    Integer int2=100;
    System.out.println(int1==int2);//false,和第一种情况类似
    
    Integer int1=new Integer(100);
    int int2=100;
    System.out.println(int1==int2);//true,拆包为int比较值

    12.String和null

    String str=null;
    System.out.println(null instanceof Object);//false
    System.out.println(str instanceof String);//false
    System.out.println((String)null instanceof String);//false

    13.instanceOf,判断一个对象是否是某个特定类或者是它的子类的一个实例,当左操作符为null的时候返回false.  右操作符不能是null

    14.String比较

    String str1="123";
    String str2="123";
    System.out.println(str1==str2);//true,使用常量池,相同内存地址
    
    String str1=new String("123");
    String str2=new String("123");
    System.out.println(str1==str2);//false,不同内存地址
    
    String str1=new String("123");
    String str2="123";
    System.out.println(str1==str2);//false,不同内存地址

    String str1="123";
    String str2="12"+"3";
    System.out.println(str1==str2);//true,使用常量池,相同的地址
     

    再来理解:

    • String.intern():

    补充介绍一点:存在于.class文件中的常量池,在运行期被JVM装载,并且可以扩充。String的intern()方法就是扩充常量池的一个方法;当一个String实例str调用intern()方法时,Java查找常量池中是否有相同Unicode的字符串常量,如果有,则返回其的引用,如果没有,则在常量池中增加一个Unicode等于str的字符串并返回它的引用

    • str=”kv”+”ill”+” “+”ans”;

    就是有4个字符串常量,首先”kv”和”ill”生成了”kvill”存在内存中,然后”kvill”又和” “ 生成 ”kvill “存在内存中,最后又和生成了”kvill ans”;并把这个字符串的地址赋给了str,就是因为String的“不可变”产生了很多临时变量,这也就是为什么建议用StringBuffer的原因了,因为StringBuffer是可改变的

    网上摘得内容(没验证): 

     String str1="abc";

       String str2="abc";

       String str3="abc";

       String str4=new String("abc");

       String str5=new String("abc");

    对于浅蓝色箭头,通过new操作产生一个字符串(“abc”)时,会先去常量池中查找是否有“abc”对象,如果没有则在常量池中创建一个此字符串对象,然后堆中再创建一个常量池中此“abc”对象的拷贝对象,所以,对于String str=new String("abc"),如果常量池中原来没有"abc"则产生两个对象,否则产生一个对象。

    而对于基础类型的变量和常量,变量和引用存储在栈中,常量存储在常量池中。例如:

       

    int a1=1,a2-1,a3=1;
    public static final int INT1=1;
    public static final int INT2=1;
    public static final int INT3=1;


    15.HashMap,底层就是一个数组结构,数组中的每一项又是一个链表

    当程序试图将一个key-value对放入HashMap中时,程序首先根据该 key的 hashCode() 返回值决定该 Entry 的存储位置:如果两个 Entry 的 key 的 hashCode() 返回值相同,那它们的存储位置相同。如果这两个 Entry 的 key 通过 equals 比较返回 true,新添加 Entry 的 value 将覆盖集合中原有Entry 的 value,但key不会覆盖如果这两个 Entry 的 key 通过 equals 比较返回 false,新添加的 Entry 将与集合中原有 Entry 形成 Entry 链,而且新添加的 Entry 位于 Entry 链的头部 

    16.String.format(),有两种重载形式:

    format(String format, Object... args) 新字符串使用本地语言环境,指定字符串格式和参数生成格式化的新字符串。

    format(Locale locale, String format, Object... args) 使用指定的语言环境,指定字符串格式和参数生成格式化的字符串 

    转  换  符

    说    明 

    示    例

    %s

    字符串类型

    "mingrisoft"

    %c

    字符类型

    'm'

    %b

    布尔类型

    true

    %d

    整数类型(十进制)

    99

    %x

    整数类型(十六进制)

    FF

    %o

    整数类型(八进制)

    77

    %f

    浮点类型

    99.99

    %a

    十六进制浮点类型

    FF.35AE

    %e

    指数类型

    9.38e+5

    %g

    通用浮点类型(f和e类型中较短的)

     

    %h

    散列码

     

    %%

    百分比类型

    %n

    换行符

     

    %tx

    日期与时间类型(x代表不同的日期与时间转换符

         

    给几个 %tx 的例子 

    Date date=new Date(); // 创建日期对象
            System.out.printf("全部日期和时间信息:%tc%n", date);//格式化输出日期或时间
            System.out.printf("年-月-日格式:%tF%n", date);
            System.out.printf("月/日/年格式:%tD%n", date);
            System.out.printf("HH:MM:SS PM格式(12时制):%tr%n", date);
            System.out.printf("HH:MM:SS格式(24时制):%tT%n", date);
            System.out.printf("HH:MM格式(24时制):%tR%n", date);

    输出

    全部日期和时间信息:星期三 五月 04 16:50:46 CST 2016-月-日格式:2016-05-04/日/年格式:05/04/16
    HH:MM:SS PM格式(12时制):04:50:46 下午
    HH:MM:SS格式(24时制):16:50:46
    HH:MM格式(24时制):16:50
    /*b或者h: 月份简称,如
            中:十月
            英:Oct
    
            B: 月份全称,如
            中:十月
            英:October
    
            a: 星期的简称,如
            中:星期六
            英:Sat
    
            A: 星期的全称,如:
            中:星期六
            英:Saturday
    
            C: 年的前两位数字(不足两位前面补0),如:20
            y: 年的后两位数字(不足两位前面补0),如:07
            Y: 4位数字的年份(不足4位前面补0),如:2007
            j: 一年中的天数(即年的第几天),如:300
            m: 两位数字的月份(不足两位前面补0),如:10
            d: 两位数字的日(不足两位前面补0),如:27
            e: 月份的日(前面不补0),如:5
            */
            String str=String.format(Locale.US,"英文月份简称:%tb",date);
            System.out.println(str);
            System.out.printf("本地月份简称:%tb%n",date);
            str=String.format(Locale.US,"英文月份全称:%tB",date);
            System.out.println(str);
            System.out.printf("本地月份全称:%tB%n",date);
            str=String.format(Locale.US,"英文星期的简称:%ta",date);
            System.out.println(str);
            System.out.printf("本地星期的简称:%tA%n",date);
            System.out.printf("年的前两位数字(不足两位前面补0):%tC%n",date);
            System.out.printf("年的后两位数字(不足两位前面补0):%ty%n",date);
            System.out.printf("一年中的天数(即年的第几天):%tj%n",date);
            System.out.printf("两位数字的月份(不足两位前面补0):%tm%n",date);
            System.out.printf("两位数字的日(不足两位前面补0):%td%n",date);
            System.out.printf("月份的日(前面不补0):%te",date);

    输出:

    英文月份简称:May
    本地月份简称:五月
    英文月份全称:May
    本地月份全称:五月
    英文星期的简称:Wed
    本地星期的简称:星期三
    年的前两位数字(不足两位前面补0):20
    年的后两位数字(不足两位前面补0):16
    一年中的天数(即年的第几天):125
    两位数字的月份(不足两位前面补0):05
    两位数字的日(不足两位前面补0):04
    月份的日(前面不补0):4
    /*H: 2位数字24时制的小时(不足2位前面补0),如:15
            I: 2位数字12时制的小时(不足2位前面补0),如:03
            k: 2位数字24时制的小时(前面不补0),如:15
            l: 2位数字12时制的小时(前面不补0),如:3
            M: 2位数字的分钟(不足2位前面补0),如:03
            S: 2位数字的秒(不足2位前面补0),如:09
            L: 3位数字的毫秒(不足3位前面补0),如:015
            N: 9位数字的毫秒数(不足9位前面补0),如:562000000
    
            p: 小写字母的上午或下午标记,如:
            中:下午
            英:pm
    
            z: 相对于GMT的RFC822时区的偏移量,如:+0800
            Z: 时区缩写字符串,如:CST
            s: 1970-1-1 00:00:00 到现在所经过的秒数,如:1193468128
            Q: 1970-1-1 00:00:00 到现在所经过的毫秒数,如:1193468128984
             */
            System.out.printf("2位数字24时制的小时(不足2位前面补0):%tH%n",date);
            System.out.printf("2位数字12时制的小时(不足2位前面补0):%tI%n",date);
            System.out.printf("2位数字24时制的小时(前面不补0):%tk%n",date);
            System.out.printf("2位数字12时制的小时(前面不补0):%tl%n",date);
            System.out.printf("2位数字的分钟(不足2位前面补0):%tM%n",date);
            System.out.printf("2位数字的秒(不足2位前面补0):%tS%n",date);
            System.out.printf("3位数字的毫秒(不足3位前面补0):%tL%n",date);
            System.out.printf("9位数字的毫秒数(不足9位前面补0):%tN%n",date);
            String str1=String.format(Locale.US,"小写字母的上午或下午标记(英):%tp",date);
            System.out.println(str);                          // 输出字符串变量str的内容
            System.out.printf ("小写字母的上午或下午标记(中):%tp%n",date);
            System.out.printf("相对于GMT的RFC822时区的偏移量:%tz%n",date);
            System.out.printf("时区缩写字符串:%tZ%n",date);
            System.out.printf("1970-1-1 00:00:00 到现在所经过的秒数:%ts%n",date);
            System.out.printf("1970-1-1 00:00:00 到现在所经过的毫秒数:%tQ%n",date);

    输出:

    2位数字24时制的小时(不足2位前面补0):16
    2位数字12时制的小时(不足2位前面补0):04
    2位数字24时制的小时(前面不补0):16
    2位数字12时制的小时(前面不补0):4
    2位数字的分钟(不足2位前面补0):50
    2位数字的秒(不足2位前面补0):46
    3位数字的毫秒(不足3位前面补0):337
    9位数字的毫秒数(不足9位前面补0):337000000
    英文星期的简称:Wed
    小写字母的上午或下午标记(中):下午
    相对于GMT的RFC822时区的偏移量:+0800
    时区缩写字符串:CST
    1970-1-1 00:00:00 到现在所经过的秒数:1462351846
    1970-1-1 00:00:00 到现在所经过的毫秒数:1462351846337

    二、命令

    1.java和javaw命令 

      相同点:二者都是启用Java的虚拟机,用来执行Java程序
      区别:javaw.exe运行程序时不会输出控制台信息,如果是双击打开jar文件的话(假设已经设置好了打开方式),那么根本就不会出现控制台窗口,主要用来运行带窗体的应用程序,其中的“w”就是window的意思,所以用它来运行控制台程序时虽不会报错,但不会输出任何结果。

    2.用idea跑项目时出现java.lang.OutOfMemoryError: PermGen space错误,就顺便查了下java命令运行时的一些参数

    这个错误可以设置-XX:MaxPermSize=128m,增大持久代内存的值

    还有个参数-XX:PermSize=64m,这个是设置初始持久代内存,网上说是物理内存的1/64,我本机看了下,并不是。我本机8G内存,用jinfo -flag PermSize查出才20多兆,

    3.jvm监控

    • jps,列出java进程,常用jps -l
    • jinfo,观察进程运行环境参数,包括Java System属性和JVM命令行参数,常用jinfo -flag PermSize 9224,jinfo -flag MaxPermSize 9224
    • 超级命令啊,jconsole/jvisualvm,这两个很强大,惊叹了

    三、内存模型

     java虚拟机运行的时候内存分配图如下图:

    • jvm虚拟机栈:线程独有的,每次启动一个线程,就创建一个jvm虚拟机栈,线程退出的时候就销毁。这里面主要保存线程本地变量名和局部变量值。
    • 本地方法栈: 调用本地jni方法的时候而创建的。这里分配的jvm之外的内存空间。方法调用结束之后销毁。
    • 堆:主要保存创建的对象
    • 方法区:保存class相关的信息。主要是class的一个内存结构信息,类型信息包括:

    1)这个类型的完整有效名

    2)这个类型直接父类的完整有效名(除非这个类型是interface或是java.lang.Object,两种情况下都没有父类)

    3)这个类型的修饰符(public,abstract, final的某个子集)

    4)这个类型直接接口的一个有序列表

    除此外,还要为每个类型保存以下信息:

    1)类型的常量池( constant pool)

    2)域(Field)信息

    3)方法(Method)信息

    4)除了常量外的所有静态(static)变量

    • 常量池:方法区的一部分,主要保存class内存结构中常量值 例如String值,public static final 类型的值
    • 线程pc寄存器 : 这个保存线程当前执行的字节码指令,

    由于Java 虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。

    如果线程正在执行的是一个Java 方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Natvie 方法,这个计数器值则为空(Undefined)。

    此内存区域是唯一一个在Java 虚拟机规范中没有规定任何OutOfMemoryError 情况的区域

    四、编码

    涉及到编码一直让人头痛,java的编码要分清楚三个概念:Java采用的编码:unicode,JVM平台默认字符集和外部资源的编码。String 采用UTF-16编码方式存储所有字符,就是unicode编码,JVM平台默认字符集跟操作系统有关

    另外要明确编码解码的概念:由字节到字符是解码,反之是编码

    网上有许多类似new String(input.getBytes("ISO-8859-1"), "GBK") 这样的转换编码方式的代码,害人很深,我也深陷彀中。

    在这种不正常的情况下,我们获取了正确的结果,但是当这种方式在特定情况下才是对的

    应当记住只有在字符与字节转换时才需要指定编码,比如读取磁盘内容,肯定是字节,要想用String标识需要指定正确的编码转为unicode编码的String,字符串之间的转化没有意义

    在网上看了一张图画了java中编码的各种使用

     持续更新中...

    参考文章:

    1. java 垃圾回收总结(1)

    2. JVM系列

    3. 深入分析 Java 中的中文编码问题

    4. JVM中方法区

  • 相关阅读:
    Linux线程信号
    有理想的程序员必须知道的15件事
    Linux下 mplayer 使用手册
    Winxp下 gvim 编程环境搭建
    在WPF中弹出右键菜单时判断鼠标是否选中该项
    F#基本类型——Discriminated Unions
    在WPF的TreeView中实现右键选定
    WPF TreeView tools
    F#基本类型——Structure
    增强了一下DownloaderPlus的视频转换功能
  • 原文地址:https://www.cnblogs.com/yhzh/p/4913515.html
Copyright © 2020-2023  润新知