Skip to content
Ider

沉淀我所学习,累积我所见闻,分享我所体验

Primary Navigation Menu
Menu
  • Home
  • About Ider
    • Who Ider?
    • Why Ider?
    • How Ider?
    • Where Ider?
    • What Ider?

Data Structures(数据结构)

2020-03-12
12 March
On March 12, 2020
In Data Structures(数据结构), Knowledge Base(心得笔库), Language Tips(语言初试), Mobile Development(移动开发)

基础数据类型的包装类的内存大小

谈到 Java 的基础数据类型(Primitive Data Types),可以找到很多关于其种类以及内存大小的文章。基础数据类型对应的包装类(Wrapper Classes)也会介绍自动装箱(Autoboxing)和拆箱(Unboxing),以及带来的效率上的影响。但可能是因为Java一般所使用的环境都有比较充足的内存,似乎从来没有人讨论过每种包装类所占的内存大小以及对内存溢出可能造成的影响。

到了 Android 应用程序开发领域,由于设备硬件本身以及系统对各个程序在资源使用上的约束,就会出现很多特别的使用习惯,比如推荐使用Typedef来替代枚举类型(enum)来减少内存使用和方法数。因此关于基础数据类型的包装类所占内存大小就值得写篇博客来研究一下。

包装类所占内存大小

网上我找了很多计算类内存大小的方法,但是既然我是 Android 工程师,我就用 Android Studio 提供的 Memory Profiler 来查看内存使用情况

下边就是我用来做内存检查的类,它包含了所有基础数据类型和对于的包类型

[codesyntax lang=”java” lines=”normal”]

public class Primitives {
  boolean pBoolean;
  byte pByte;
  char pChar;
  double pDouble;
  float pFloat;
  int pInt;
  long pLong;
  short pShort;

  Boolean cBoolean;
  Byte cByte;
  Character cChar;
  Double cDouble;
  Float cFloat;
  Integer cInt;
  Long cLong;
  Short cShort;

  public Primitives() {}

  public Primitives(Void v) {
    cBoolean = false;
    cByte = 0;
    cChar = '\0';
    cDouble = 0.0;
    cFloat = 0.0f;
    cInt = 0;
    cLong = 0L;
    cShort = 0;
  }
}

[/codesyntax]

接着在 MainActivity 中创建两个 Primitives 类的字段:一个没有初始化对象类型,一个将所有对象类型初始化成默认值。然后运行程序,在 Android Studio 中打开 Profiler 查看内存时”Dump Java heap” 就可以看到这两个 Primitives 对象内存使用情况

从图中的结果可以看出如果对象的值是 null,那他们是不占用空间。在这一点上看,如果字段大部分时间都存的是空值或者以空值指代默认值,那相对比总是会被赋予默认值的基础数据类型在内存使用方面要好一些。

对于包装类,它们都比其对应基础数据类型要占用更多的内存空。仔细对比不难计算出他们的差别都是 8 bytes,正好是一个普通 Object 实例的大小。这也证明的包装类只是对基础数据类型的简单包装。

基础数据类型 包装类型
类型 大小 类型 大小
boolean 1 byte Boolean 9 bytes
byte 1 byte Byte 9 bytes
char 2 bytes Character 10 bytes
double 8 bytes Double 16 bytes
float 4 bytes Float 12 bytes
int 4 bytes Integer 12 bytes
long 8 bytes Long 16 bytes
short 2 bytes Short 10 bytes

所以,使用包装类不仅会有自动装箱和拆箱产生的效率上的影响,对内存的使用也有很大的影响。而 Android 的内存又是稀缺资源,所以 Android 库里多了特殊的数据类型来优化内存的使用,比如 SparseArray 替换以 int 为主键的 HashMap。
Read More →

2019-08-29
29 August
On August 29, 2019
In Data Structures(数据结构), Knowledge Base(心得笔库), Mobile Development(移动开发), Software Engineering(软件工程)

Gradle android-library 的各种坑

软件开发随着项目越来越大,我们会开始拆封出多个子项目(sub-project)或者库(library)来更好地独立维护。Android开发也不例外,我们也会需要创建各种库。如果使用Android Studio和Gradle,他们为我们提供了便捷的方式来创建libray。但是他们也带来了不少的维护问题,本文就来讲讲android-library里的坑来帮助大家避开。

android-library v.s. java-library

对于普通Android手机项目,我们有创建两种不同的Library可以选用: android-library 和 java-library

它们的gradle文件内容分别如下

[codesyntax lang=”groovy” lines=”normal” highlight_lines=”1″]

apply plugin: 'com.android.library'

android {
    // ...
}

dependencies {
    // ...
}

[/codesyntax]

[codesyntax lang=”groovy” lines=”normal” highlight_lines=”1″]

apply plugin: 'java-library'

sourceCompatibility = "7"
targetCompatibility = "7"

dependencies {
    // ...
}

[/codesyntax]

两种库在定义上的根本差别其实就是不同gradle plugin的使用。另一个小的差别就是android-library需要有一个 AndroidManifest.xml 文件,但它可以简单到只有一行内容(其中package的值通常是文件夹的路径)

[codesyntax lang=”xml” lines=”normal”]

<manifest package="com.ider.android.library" />

[/codesyntax]

使用上他们有一些差异,这些差异也是我们决定使用哪种Library的标准:

  • android-library 可以使用 Android API(比如Activity, Service等等),java-library 则不能使用
  • android-library 可以编译android资源文件(resources files),java-library 则无视这样内容
  • android-library 编译出 arr 文件,java-library编译出 jar 文件(本质上他们都是 zip 文件)
  • android-library 的配置可以包含各种编译类型(build variants)来控制,java-library没有那个复杂
  • android-library 可以依赖于另一个android-library 或者 java-library, java-library只能依赖于java-library而不能依赖于android-library

总体而言 android-library 基本是 java-library 的一个超集, 但 android-library 比 java-libray 使用起来要更加复杂一些。从上边的对比来看,对于Android的项目,因为我们不确定什么时候会需要调用 Android 的API,或者定义一下 Android 的资源内容。再者最后一条的约束也决定了创建的 java-library 要保证在整个依赖树(dependency tree)上要在接近叶子节点的位置。所以不如优先选择创建android-library,以免掉入之后改 build.gradle 的坑中。
Read More →

2018-06-29
29 June
On June 29, 2018
In Data Structures(数据结构), Design Patterns(设计模式), Language Tips(语言初试)

隐秘而诡异的Java合成方法

Java程序里其实有很多我们看不到的代码,这些代码由Java编译器在编译过程中生成帮助程序更准确地运行。本文就来深入了解一下由编译器加入到Java代码中的方法(Method),特别是合成方法(Synthetic Method)。

合成方法

合成成员(Synthetic Member)在JVM细则里可以找到简单的定义

A class member that does not appear in the source code must be marked using a Synthetic attribute, or else it must have its ACC_SYNTHETIC flag set.

合成成员包含有合成类(Synthetic Class),合成变量(Synthetic Variable),合成方法(Synthetic Method),本文主要讨论合成方法。

合成方法不出现在.java文件中,不过编译之后会在.class里出现,并可以通过以下几个方法发现它们:

  • 在设置断点进行debug时会在方法栈上看到这些由编译器加入的方法
  • 通过反射(Reflection)查找到方法,通过isSyntehtic()方法来判定是否是合成函数
  • 利用一些反编译工具,比如 javap、JD-GUI、jad,查看.class文件可以找到这些方法

对于一般的Java程序而言并没有什么大的问题,只不过是额外的方法调用,代价比较低。但是像Android,它的Dex文件对于Java的方法有一定的约束,就值得去考虑如何避免额外的方法被生成。下边就来介绍一些常见的会出现合成方法的情况让我们有清楚地了解Java的运行机制。

嵌套类和私有成员

先来看一个代码案例,代码中定义了一个顶层类和嵌套类,互相会方法对方的一些private修饰的方法和字段。

[codesyntax lang=”java” lines=”normal”]

public class Outer {

  private int privateField = 21;
  private Inner inner = new Inner();

  private int privateMethod() {
    return inner.privateMethod();
  }

  private void print() {
    inner.print();
  }

  private static int privateStaticMethod() {
    return 7;
  }

  class Inner {
    private int privateMethod() {
      return privateField;
    }

    private void print() {
      System.out.println(Outer.this.privateMethod());
      System.out.println(privateStaticMethod());
    }
  }

  public static void main(String[] args) {
    Outer outer = new Outer();
    outer.print();
  }
}

[/codesyntax]

之前的文章有介绍了Java的修饰访问符的限制范围,类中由private修饰的成员,只能被该类自身所访问,因此嵌套类的私有成员不应该能被外部类访问。另一方面嵌套类虽然在外部类里面,但其实并不属于外部类的一部分,比如编译上边的代码会得到两个.class文件,Inner类被编译成了package可见的独立类。


Read More →

2015-06-30
30 June
On June 30, 2015
In Data Structures(数据结构), Design Patterns(设计模式), Knowledge Base(心得笔库), Mobile Development(移动开发)

Android自定义View中MeasureSpec浅析

Android的API为开发者提供了很多好用的widget和control,但在开发中依然会需要自定义一些额外的控件来满足特定的需求。当自定义的视图比较复杂时,就会跟onMeasure(int, int)、onLayout(boolean, int, int, int, int)、onDraw(android.graphics.Canvas)这些方法打交道。要在新创建自定义视图类里正确重载实现这些方法,就需要对这些方法的作用,参数意义有所了解。

本文就来对onMeasure(int, int)方法涉及到的MeasureSpec来做个简单介绍,讲讲它的值的含义,每个状态量都是在什么情况下形成,又该如何使用这些值。
Read More →

2015-06-06
06 June
On June 6, 2015
In Data Structures(数据结构), Knowledge Base(心得笔库), Mobile Development(移动开发)

利用Java的注解,维护Android的代码

对于Java这门语言,我的态度应该是讨厌多于喜欢。整个大学我只上过一门Java的选修课算是有了Java的入门知识,之后用得更多的则是C#。在研究生阶段也是以C++和C#为主。直到工作需要做Android开发才拾起Java作为主打开发武器,所以很多Java的知识对我而言可能只是懂一个肤浅的表面。好在身边有很多的大神总是愿意耐心提供帮助,引导我进入Java新的知识领域,再加上网上的一些搜索和补充,让我的知识变得更加全面。

比如WeakReference在一定程度上帮助解决了Activity和Fragment泄露的问题;多线程时要注意的同步问题;内部类调用其外部类的私有方法和变量产生了合成方法(Synthetic Methods)造成的性能影响和Android的dex溢出。

最近又得益大神们的教诲,被反复强调一定要善用Java的注解(annotation)。这让我感觉,当我还在思索要怎么写代码的时候,别人已经在考虑着怎么告诉编译器和其他开发者他们写了什么东西。

不过在坚持使用Annotation数月之后,我也深深体会到了它对写代码带来的好处而停不下来。比如方法上加个@Override就可以大大减少把重载方法名字的或者变量参数写错的几率,同时在浏览文件时一眼就能看出哪些是新增方法,哪些来自父类;在对象变量上放一个@Nullable,IDE就能帮助我们快速指出哪里有可能会出现NullPointerException这个恼人却又简单能解决的问题,不用等到运行时异常被抛出了才去解决;而加上@NonNull就可以随心所意的直接使用变量而无需加if语句了检查null。

还有在Android开发中最常用到的那些指示资源的标示(Resource Id),全部都是整型数,其实有很多的类型如R.id、R.string、R.drawable。如果不小心,就很容易弄错而得到了非所需的结果。特别是R.style和R.styleable两个常常会被IDE的自动补全所迷惑;还有整型的Color值和R.color也很容易出错。现在只要在变量前加上对应类型的Android Support Annotation,就可以大大地规避这些问题出现的几率。

虽然对于有经验的开发者来说这些小问题都会自动避免,再不小心运行时也会发现问题。但不是所有接触和使用这些代码的人都能一样的小心。万一别人的失误落到自己头上修复,也只能无奈抱怨一句:这人怎么这么不小心呢。

简单的加上一些annotation,那么一旦在代码中出现错误的使用,IDE就会高亮显示错误的地方让人一目了然,也能及时修改这些错误,不用等到运行后才去debug:

support_annotation

Read More →

2015-01-30
30 January
On January 30, 2015
In Algorithm Analysis(算法分析), Data Structures(数据结构), Design Patterns(设计模式), Knowledge Base(心得笔库)

树结构遍历的实现汇总

在计算机数据结构中,树(Tree)结构是基础的结构之一,对于其定义、实现以及相关算法在一般的算法与数据结构书中都有详细介绍和分析。不过本文还是想要罗列一下树结构基本的遍历算法,同时简单随机介绍各种遍历算法的使用情境。

本文中将会实现树遍历主要两种方式:深度优先和广度优先。对于深度优先也会包括其三种不同方式:前序遍历、中序遍历、后续遍历。同时对于深度优先的三种方式还会包括最简单直观的递归实现,和相对复杂的迭代实现。

为方便起见文中的算法将基于二叉树(Binary Tree)结构,其树节点的结构也简单定义如下:

[codesyntax lang=”cpp” lines=”normal”]

using namespace std;
class Node {
public:
    Node *left;
    Node *right;
    int value;
};

[/codesyntax]

深度优先遍历

深度优先遍历隐藏着回溯法(Backtracking)应用,因此利用递归自动创建堆栈(stack)的性质,就可以非常好的实现这些遍历算法。虽然递归可以通过创建自定义的栈对象来变成迭代,但对于进栈出栈的控制,三种遍历方式有着不同的条件,因而实现反而变得复杂。

递归实现

对于节点的访问操作,定义了如下的方法,其实只是简单的输出节点的值再以空格分隔。对于空节点也将预留输出特别的符号来指示。

[codesyntax lang=”cpp” lines=”normal”]

#define NULL_NODE "#"

void operate(Node *node) {
    if (node == NULL) {
//        cout << (NULL_NODE" ");
    } else {
        cout << node->value << " ";
    }
}

[/codesyntax]

三种深度优先遍历的算法实现如下

[codesyntax lang=”cpp” lines=”normal”]

void preorder(Node *node) {
    if (node == NULL) {
        operate(NULL);
        return;
    }
    
    // operate node before any children
    operate(node);
    preorder(node->left);
    preorder(node->right);
}

void inorder(Node *node) {
    if (node == NULL) {
        operate(NULL);
        return;
    }
    
    inorder(node->left);
    // operate node in the middle of children
    operate(node);
    inorder(node->right);
}

void postorder(Node *node) {
    if (node == NULL) {
        operate(NULL);
        return;
    }
    
    postorder(node->left);
    postorder(node->right);
    // operate node after all child
    operate(node);
}

[/codesyntax]
Read More →

2014-09-30
30 September
On September 30, 2014
In Data Structures(数据结构), Design Patterns(设计模式), Knowledge Base(心得笔库), Mobile Development(移动开发)

了解Android中的Preference结构的设计与实现

之前的文章讲到Preference和Setting在本质上是相似的,并相信介绍了SharedPreferences的基本使用方式和其代码设计。但SharedPreferences只能算是设备背后的不可见的存储那一部分,而可见的界面其实对于用户来说更加重要,而这一部分就是本文要讲的那些在android.preference包下的各种Preference。

不过本文不会涉及这些Preference的使用方式,比如如何定义XML文件、如何使用PreferenceActivity和PreferenceFragment加载设置,这些都可以在Android Developer的官方指南里了解到详情。本文主要通过分析源代码来分享Preference的设计和实现方式,让开发者们在今后更加顺手地使用和扩展Preference类,或者在设计其他类似的界面和功能时可以提供参考帮助。

Preference概览

Android的设置界面本质上就是ListView:PreferenceActivity是继承了ListActivity;而3.0以后推荐使用的PreferenceFragment虽然没有继承ListFragment,但也定义了ListView字段。

settings

Read More →

2014-04-28
28 April
On April 28, 2014
In Data Structures(数据结构), Design Patterns(设计模式), Language Tips(语言初试)

各种面向对象程序语言的toString方法实现

字符串(String)类型在程序语言中都有非常广泛的使用,因此字符串(String)也同整型、浮点型等一样被作为基本数据类型提供给开发者直接使用。在面向对象程序语言中,String作为对象的类,被赋予了各种方法来方便对String的操作和变换。而其他类,常常也会有默认的方法能让该类的实体对象转变成字符串(String)对象。比如最常见的就是Java和C#的toString()方法了。

将对象转换成字符串有几个好处:可以打印记录方便debug;可以存储为JSON或CSV等类型的文档又具有可读性。另一方面程序语言提供方法会在一些情况被隐式的调用来转换成字符串,比如对象和字符串拼接、String类型强制转换等等。开发者也可以重定义这些方法以得到希望的格式出现在上述情况中。

本文就来总结归纳一下当前流行的面向对象程序语言对“toString()”方法的实现方式。
Read More →

2014-03-31
31 March
On March 31, 2014
In Data Structures(数据结构), English Posts(英文写作), Language Tips(语言初试), Mobile Development(移动开发)

Introduction to Loc (Lua Objective-C)

Recently we were starting to use Lua in our game. Lua is a very popular scripting language in the game industry. It’s written in pure C and is very lightweight. We can simply add Lua source code to our project, and it is very easy to compile and run.

However, in practice we encountered issues with type conversion between Lua and Objective-C. In the beginning, we followed instructions to create C functions, and inside the functions we called Objective-C methods, then registered those functions into Lua State as library. When requirements went more complicated, we have to create a bunch of C functions, which are redundancy in a lot of places. This is really not an ideal to bridge between Lua and Objective-C.

After reading the Lua instructions for two days, and with knowledge about iOS7 JavaScript Framework, I decided to create a similar framework to handle type conversion between these two languages. The good thing is Objective-C is a superclass of C, and Lua is naturally compiled from C, this makes it very easy to construct a bridge and make them communicating with each other. With reflection from Objective-C, it’s also very convenient to call Objective-C methods from Lua.

Loc (Lua Objective-C) is the framework I created to convert type between Lua and Objective-C for our project. It’s as simple as Lua, with only 6 packages. It reduces a lot of time to create functions mapping Lua call, and it works pretty well so far.

With the permission from my company, I shared the slides that I presented to my colleagues about the design and syntax about Loc. I am trying to persuade my managers to make it open source, so more developers could use it to prove the usability and make the framework more mature.

2013-12-26
26 December
On December 26, 2013
In Algorithm Analysis(算法分析), Data Structures(数据结构), Knowledge Base(心得笔库), Language Tips(语言初试), Special Tricks(奇技妙招)

bash中使用printf实现进制转换

最近工作中常涉及到编码转换的问题,比如发送URL的时候需要进行urlencode,上传内容的时候要对MD5进行BASE64的转换。当debug的只能看到8或者16进制的编码而想不到实际的字符是什么,就让人非常烦躁,所以总想有个什么快速的方式能帮助自己进行编码转换 。

最快的方式,应该就像小时候被乘法口诀表一样把他们的对应关系一一记住,但是可能年龄大了记住了东西,外加现在越来越依赖技术带来的便利所以总是不能也不愿意记这些进制关系,一碰到这些问题就Google搜索一下。但这显然这是很 浪费时间的。

如果是用Linux或者Mac系统,用Terminal的话就能带来很多的帮助,比如man ascii就会显出中ASCII在8、10和16进制下所有编码值了。这样子看到一个8或16进制的数,也能很快的找到对应的字符是什么了。不过这还是不过快,毕竟人眼查这样一张有序的表并不会进行二分查找。而且我们希望是能够在O(1)时间复杂度就能得到结果方法。

本文就总结怎么用printf的格式化在Bash环境中实现进制的转换,如果有那个比较好用或常用的,就可以添加到profile中在启动时载入使用。
Read More →

Posts pagination

1 2 Next
Facebook
Twitter
LinkedIn
RSS
ZhiHu

Recent Posts

  • 三年居家工作感受
  • Pixel Watch智能手表和Pixel 5, 6 Pro 及 7 Pro手机
  • 我拥有过的无线耳机
  • 毕业工作一个月,我差点被开除
  • 我拥有过的移动硬盘
  • ProtoBuf 2.0 method count optimization for android development
  • 面过100场行为面试后

Categories

  • Algorithm Analysis(算法分析)
  • Article Collection(聚宝收藏)
  • Data Structures(数据结构)
  • Design Patterns(设计模式)
  • English Posts(英文写作)
  • Front Interface(界面构想)
  • IT Products(数码产品)
  • Knowledge Base(心得笔库)
  • Language Tips(语言初试)
  • Mathematical Theory(数学理论)
  • Mobile Development(移动开发)
  • Programming Life(程序人生)
  • Reading Notes(阅而后知)
  • Software Engineering(软件工程)
  • Special Tricks(奇技妙招)
  • Tangential Speech(漫话杂谈)

Tags

Aero Android API Bash Binary Search Bitwise Operation Book C/C++ Career Chrome Conference CSS Debug Device DOM Extension Framework Game Gradle Hearthstone HTML Initialization Intellij Interview iOS Java JavaScript jQuery Keyword Language Issues Mac Microsoft Mobile Modifier Objective-C PHP Principle Reference Regular Expression Static String Tools Tutorial UI XML

Blogroll

  • Ahmed's Blog
  • Gert Lombard's Blog
  • Gordon Luk
  • Jack & Allison
  • 开发部落

Archives

Designed using Chromatic. Powered by WordPress.